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 }