github.com/ipld/go-ipld-prime@v0.21.0/node/bindnode/generate.go (about)

     1  package bindnode
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"go/format"
     7  	"io"
     8  	"strings"
     9  
    10  	"github.com/ipld/go-ipld-prime/schema"
    11  )
    12  
    13  // TODO(mvdan): deduplicate with inferGoType once reflect supports creating named types
    14  
    15  func produceGoType(goTypes map[string]string, typ schema.Type) (name, src string) {
    16  	if typ, ok := typ.(interface{ IsAnonymous() bool }); ok {
    17  		if typ.IsAnonymous() {
    18  			panic("TODO: does this ever happen?")
    19  		}
    20  	}
    21  
    22  	name = string(typ.Name())
    23  
    24  	switch typ.(type) {
    25  	case *schema.TypeBool:
    26  		return goTypeBool.String(), ""
    27  	case *schema.TypeInt:
    28  		return goTypeInt.String(), ""
    29  	case *schema.TypeFloat:
    30  		return goTypeFloat.String(), ""
    31  	case *schema.TypeString:
    32  		return goTypeString.String(), ""
    33  	case *schema.TypeBytes:
    34  		return goTypeBytes.String(), ""
    35  	case *schema.TypeLink:
    36  		return goTypeLink.String(), "" // datamodel.Link
    37  	case *schema.TypeAny:
    38  		return goTypeNode.String(), "" // datamodel.Node
    39  	}
    40  
    41  	// Results are cached in goTypes.
    42  	if src := goTypes[name]; src != "" {
    43  		return name, src
    44  	}
    45  
    46  	src = produceGoTypeInner(goTypes, name, typ)
    47  	goTypes[name] = src
    48  	return name, src
    49  }
    50  
    51  func produceGoTypeInner(goTypes map[string]string, name string, typ schema.Type) (src string) {
    52  	// Avoid infinite cycles.
    53  	// produceGoType will fill in the final type later.
    54  	goTypes[name] = "WIP"
    55  
    56  	switch typ := typ.(type) {
    57  	case *schema.TypeEnum:
    58  		// TODO: also generate named constants for the members.
    59  		return goTypeString.String()
    60  	case *schema.TypeStruct:
    61  		var b strings.Builder
    62  		fmt.Fprintf(&b, "struct {\n")
    63  		fields := typ.Fields()
    64  		for _, field := range fields {
    65  			fmt.Fprintf(&b, "%s ", fieldNameFromSchema(field.Name()))
    66  			ftypGo, _ := produceGoType(goTypes, field.Type())
    67  			if field.IsNullable() {
    68  				fmt.Fprintf(&b, "*")
    69  			}
    70  			if field.IsOptional() {
    71  				fmt.Fprintf(&b, "*")
    72  			}
    73  			fmt.Fprintf(&b, "%s\n", ftypGo)
    74  		}
    75  		fmt.Fprintf(&b, "\n}")
    76  		return b.String()
    77  	case *schema.TypeMap:
    78  		ktyp, _ := produceGoType(goTypes, typ.KeyType())
    79  		vtyp, _ := produceGoType(goTypes, typ.ValueType())
    80  		if typ.ValueIsNullable() {
    81  			vtyp = "*" + vtyp
    82  		}
    83  		return fmt.Sprintf(`struct {
    84  			Keys []%s
    85  			Values map[%s]%s
    86  		}`, ktyp, ktyp, vtyp)
    87  	case *schema.TypeList:
    88  		etyp, _ := produceGoType(goTypes, typ.ValueType())
    89  		if typ.ValueIsNullable() {
    90  			etyp = "*" + etyp
    91  		}
    92  		return fmt.Sprintf("[]%s", etyp)
    93  	case *schema.TypeUnion:
    94  		var b strings.Builder
    95  		fmt.Fprintf(&b, "struct{\n")
    96  		members := typ.Members()
    97  		for _, ftyp := range members {
    98  			ftypGo, _ := produceGoType(goTypes, ftyp)
    99  			fmt.Fprintf(&b, "%s ", fieldNameFromSchema(string(ftyp.Name())))
   100  			fmt.Fprintf(&b, "*%s\n", ftypGo)
   101  		}
   102  		fmt.Fprintf(&b, "\n}")
   103  		return b.String()
   104  	}
   105  	panic(fmt.Sprintf("%T\n", typ))
   106  }
   107  
   108  // ProduceGoTypes infers Go types from an IPLD schema in ts
   109  // and writes their Go source code type declarations to w.
   110  // Note that just the types are written,
   111  // without a package declaration nor any imports.
   112  //
   113  // This gives a good starting point when wanting to use bindnode with Go types,
   114  // but users will generally want to own and modify the types afterward,
   115  // so they can add documentation or tweak the types as needed.
   116  func ProduceGoTypes(w io.Writer, ts *schema.TypeSystem) error {
   117  	goTypes := make(map[string]string)
   118  	var buf bytes.Buffer
   119  	for _, name := range ts.Names() {
   120  		schemaType := ts.TypeByName(string(name))
   121  		if name != schemaType.Name() {
   122  			panic(fmt.Sprintf("%s vs %s", name, schemaType.Name()))
   123  		}
   124  		_, src := produceGoType(goTypes, schemaType)
   125  		if src == "" {
   126  			continue // scalar type used directly
   127  		}
   128  		fmt.Fprintf(&buf, "type %s %s\n", name, src)
   129  	}
   130  
   131  	src, err := format.Source(buf.Bytes())
   132  	if err != nil {
   133  		return err
   134  	}
   135  	if _, err := w.Write(src); err != nil {
   136  		return err
   137  	}
   138  	return nil
   139  }