github.com/ipld/go-ipld-prime@v0.21.0/schema/dmt/compile.go (about) 1 package schemadmt 2 3 import ( 4 "fmt" 5 6 "github.com/ipld/go-ipld-prime/datamodel" 7 "github.com/ipld/go-ipld-prime/schema" 8 ) 9 10 // Compile transforms a description of a schema in raw data model ("dmt") form 11 // into a compiled schema.TypeSystem, which is the ready-to-use form. 12 // 13 // The first parameter is mutated by this process, 14 // and the second parameter is the data source. 15 // 16 // The compilation process includes first inserting the "prelude" types into the 17 // schema.TypeSystem -- that is, the "type Bool bool" and "type String string", etc, 18 // which are generally presumed to be present in any type system. 19 // 20 // The compilation process attempts to check the validity of the schema at a logical level as it goes. 21 // For example, references to type names not present elsewhere in the same schema are now an error 22 // (even though that has been easily representable in the dmt.Schema form up until this point). 23 // 24 // Note that this API is EXPERIMENTAL and will likely change. 25 // It supports many features of IPLD Schemas, 26 // but it may yet not support all of them. 27 // It supports several validations for logical coherency of schemas, 28 // but may not yet successfully reject all invalid schemas. 29 func Compile(ts *schema.TypeSystem, node *Schema) error { 30 // Prelude; probably belongs elsewhere. 31 { 32 ts.Accumulate(schema.SpawnBool("Bool")) 33 ts.Accumulate(schema.SpawnInt("Int")) 34 ts.Accumulate(schema.SpawnFloat("Float")) 35 ts.Accumulate(schema.SpawnString("String")) 36 ts.Accumulate(schema.SpawnBytes("Bytes")) 37 38 ts.Accumulate(schema.SpawnAny("Any")) 39 40 ts.Accumulate(schema.SpawnMap("Map", "String", "Any", false)) 41 ts.Accumulate(schema.SpawnList("List", "Any", false)) 42 43 // Should be &Any, really. 44 ts.Accumulate(schema.SpawnLink("Link")) 45 46 // TODO: schema package lacks support? 47 // ts.Accumulate(schema.SpawnUnit("Null", NullRepr)) 48 } 49 50 for _, name := range node.Types.Keys { 51 defn := node.Types.Values[name] 52 53 // TODO: once ./schema supports anonymous/inline types, remove the ts argument. 54 typ, err := spawnType(ts, name, defn) 55 if err != nil { 56 return err 57 } 58 ts.Accumulate(typ) 59 } 60 61 // TODO: if this fails and the user forgot to check Compile's returned error, 62 // we can leave the TypeSystem in an unfortunate broken state: 63 // they can obtain types out of the TypeSystem and they are non-nil, 64 // but trying to use them in any way may result in panics. 65 // Consider making that less prone to misuse, such as making it illegal to 66 // call TypeByName until ValidateGraph is happy. 67 if errs := ts.ValidateGraph(); errs != nil { 68 // Return the first error. 69 for _, err := range errs { 70 return err 71 } 72 } 73 return nil 74 } 75 76 // Note that the parser and compiler support defaults. We're lacking support in bindnode. 77 func todoFromImplicitlyFalseBool(b *bool) bool { 78 if b == nil { 79 return false 80 } 81 return *b 82 } 83 84 func anonTypeName(nameOrDefn TypeNameOrInlineDefn) string { 85 if nameOrDefn.TypeName != nil { 86 return *nameOrDefn.TypeName 87 } 88 defn := *nameOrDefn.InlineDefn 89 switch { 90 case defn.TypeDefnMap != nil: 91 defn := defn.TypeDefnMap 92 return fmt.Sprintf("Map__%s__%s", defn.KeyType, anonTypeName(defn.ValueType)) 93 case defn.TypeDefnList != nil: 94 defn := defn.TypeDefnList 95 return fmt.Sprintf("List__%s", anonTypeName(defn.ValueType)) 96 case defn.TypeDefnLink != nil: 97 return anonLinkName(*defn.TypeDefnLink) 98 default: 99 panic(fmt.Errorf("%#v", defn)) 100 } 101 } 102 103 func anonLinkName(defn TypeDefnLink) string { 104 if defn.ExpectedType != nil { 105 return fmt.Sprintf("Link__%s", *defn.ExpectedType) 106 } 107 return "Link__Link" 108 } 109 110 func parseKind(s string) datamodel.Kind { 111 switch s { 112 case "map": 113 return datamodel.Kind_Map 114 case "list": 115 return datamodel.Kind_List 116 case "null": 117 return datamodel.Kind_Null 118 case "bool": 119 return datamodel.Kind_Bool 120 case "int": 121 return datamodel.Kind_Int 122 case "float": 123 return datamodel.Kind_Float 124 case "string": 125 return datamodel.Kind_String 126 case "bytes": 127 return datamodel.Kind_Bytes 128 case "link": 129 return datamodel.Kind_Link 130 default: 131 return datamodel.Kind_Invalid 132 } 133 } 134 135 func spawnType(ts *schema.TypeSystem, name schema.TypeName, defn TypeDefn) (schema.Type, error) { 136 switch { 137 // Scalar types without parameters. 138 case defn.TypeDefnBool != nil: 139 return schema.SpawnBool(name), nil 140 case defn.TypeDefnString != nil: 141 return schema.SpawnString(name), nil 142 case defn.TypeDefnBytes != nil: 143 return schema.SpawnBytes(name), nil 144 case defn.TypeDefnInt != nil: 145 return schema.SpawnInt(name), nil 146 case defn.TypeDefnFloat != nil: 147 return schema.SpawnFloat(name), nil 148 149 case defn.TypeDefnList != nil: 150 typ := defn.TypeDefnList 151 tname := "" 152 if typ.ValueType.TypeName != nil { 153 tname = *typ.ValueType.TypeName 154 } else if tname = anonTypeName(typ.ValueType); ts.TypeByName(tname) == nil { 155 anonDefn := TypeDefn{ 156 TypeDefnMap: typ.ValueType.InlineDefn.TypeDefnMap, 157 TypeDefnList: typ.ValueType.InlineDefn.TypeDefnList, 158 TypeDefnLink: typ.ValueType.InlineDefn.TypeDefnLink, 159 } 160 anonType, err := spawnType(ts, tname, anonDefn) 161 if err != nil { 162 return nil, err 163 } 164 ts.Accumulate(anonType) 165 } 166 switch { 167 case typ.Representation == nil || 168 typ.Representation.ListRepresentation_List != nil: 169 // default behavior 170 default: 171 return nil, fmt.Errorf("TODO: support other list repr in schema package") 172 } 173 return schema.SpawnList(name, 174 tname, 175 todoFromImplicitlyFalseBool(typ.ValueNullable), 176 ), nil 177 case defn.TypeDefnMap != nil: 178 typ := defn.TypeDefnMap 179 tname := "" 180 if typ.ValueType.TypeName != nil { 181 tname = *typ.ValueType.TypeName 182 } else if tname = anonTypeName(typ.ValueType); ts.TypeByName(tname) == nil { 183 anonDefn := TypeDefn{ 184 TypeDefnMap: typ.ValueType.InlineDefn.TypeDefnMap, 185 TypeDefnList: typ.ValueType.InlineDefn.TypeDefnList, 186 TypeDefnLink: typ.ValueType.InlineDefn.TypeDefnLink, 187 } 188 anonType, err := spawnType(ts, tname, anonDefn) 189 if err != nil { 190 return nil, err 191 } 192 ts.Accumulate(anonType) 193 } 194 switch { 195 case typ.Representation == nil || 196 typ.Representation.MapRepresentation_Map != nil: 197 // default behavior 198 case typ.Representation.MapRepresentation_Stringpairs != nil: 199 return nil, fmt.Errorf("TODO: support stringpairs map repr in schema package") 200 default: 201 return nil, fmt.Errorf("TODO: support other map repr in schema package") 202 } 203 return schema.SpawnMap(name, 204 typ.KeyType, 205 tname, 206 todoFromImplicitlyFalseBool(typ.ValueNullable), 207 ), nil 208 case defn.TypeDefnStruct != nil: 209 typ := defn.TypeDefnStruct 210 var fields []schema.StructField 211 for _, fname := range typ.Fields.Keys { 212 field := typ.Fields.Values[fname] 213 tname := "" 214 if field.Type.TypeName != nil { 215 tname = *field.Type.TypeName 216 } else if tname = anonTypeName(field.Type); ts.TypeByName(tname) == nil { 217 // Note that TypeDefn and InlineDefn aren't the same enum. 218 anonDefn := TypeDefn{ 219 TypeDefnMap: field.Type.InlineDefn.TypeDefnMap, 220 TypeDefnList: field.Type.InlineDefn.TypeDefnList, 221 TypeDefnLink: field.Type.InlineDefn.TypeDefnLink, 222 } 223 anonType, err := spawnType(ts, tname, anonDefn) 224 if err != nil { 225 return nil, err 226 } 227 ts.Accumulate(anonType) 228 } 229 fields = append(fields, schema.SpawnStructField(fname, 230 tname, 231 todoFromImplicitlyFalseBool(field.Optional), 232 todoFromImplicitlyFalseBool(field.Nullable), 233 )) 234 } 235 var repr schema.StructRepresentation 236 switch { 237 case typ.Representation.StructRepresentation_Map != nil: 238 rp := typ.Representation.StructRepresentation_Map 239 if rp.Fields == nil { 240 repr = schema.SpawnStructRepresentationMap2(nil, nil) 241 break 242 } 243 renames := make(map[string]string, len(rp.Fields.Keys)) 244 implicits := make(map[string]schema.ImplicitValue, len(rp.Fields.Keys)) 245 for _, name := range rp.Fields.Keys { 246 details := rp.Fields.Values[name] 247 if details.Rename != nil { 248 renames[name] = *details.Rename 249 } 250 if imp := details.Implicit; imp != nil { 251 var sumVal schema.ImplicitValue 252 switch { 253 case imp.Bool != nil: 254 sumVal = schema.ImplicitValue_Bool(*imp.Bool) 255 case imp.String != nil: 256 sumVal = schema.ImplicitValue_String(*imp.String) 257 case imp.Int != nil: 258 sumVal = schema.ImplicitValue_Int(*imp.Int) 259 default: 260 panic("TODO: implicit value kind") 261 } 262 implicits[name] = sumVal 263 } 264 265 } 266 repr = schema.SpawnStructRepresentationMap2(renames, implicits) 267 case typ.Representation.StructRepresentation_Tuple != nil: 268 rp := typ.Representation.StructRepresentation_Tuple 269 if rp.FieldOrder == nil { 270 repr = schema.SpawnStructRepresentationTuple() 271 break 272 } 273 return nil, fmt.Errorf("TODO: support for tuples with field orders in the schema package") 274 case typ.Representation.StructRepresentation_Stringjoin != nil: 275 join := typ.Representation.StructRepresentation_Stringjoin.Join 276 if join == "" { 277 return nil, fmt.Errorf("stringjoin has empty join value") 278 } 279 repr = schema.SpawnStructRepresentationStringjoin(join) 280 case typ.Representation.StructRepresentation_Listpairs != nil: 281 repr = schema.SpawnStructRepresentationListPairs() 282 default: 283 return nil, fmt.Errorf("TODO: support other struct repr in schema package") 284 } 285 return schema.SpawnStruct(name, 286 fields, 287 repr, 288 ), nil 289 case defn.TypeDefnUnion != nil: 290 typ := defn.TypeDefnUnion 291 var members []schema.TypeName 292 for _, member := range typ.Members { 293 if member.TypeName != nil { 294 members = append(members, *member.TypeName) 295 } else { 296 tname := anonLinkName(*member.UnionMemberInlineDefn.TypeDefnLink) 297 members = append(members, tname) 298 if ts.TypeByName(tname) == nil { 299 anonDefn := TypeDefn{ 300 TypeDefnLink: member.UnionMemberInlineDefn.TypeDefnLink, 301 } 302 anonType, err := spawnType(ts, tname, anonDefn) 303 if err != nil { 304 return nil, err 305 } 306 ts.Accumulate(anonType) 307 } 308 } 309 } 310 remainingMembers := make(map[string]bool) 311 for _, memberName := range members { 312 remainingMembers[memberName] = true 313 } 314 validMember := func(memberName string) error { 315 switch remaining, known := remainingMembers[memberName]; { 316 case remaining: 317 remainingMembers[memberName] = false 318 return nil 319 case !known: 320 return fmt.Errorf("%q is not a valid member of union %q", memberName, name) 321 default: 322 return fmt.Errorf("%q is duplicate in the union repr of %q", memberName, name) 323 } 324 } 325 326 var repr schema.UnionRepresentation 327 switch { 328 case typ.Representation.UnionRepresentation_Kinded != nil: 329 rp := typ.Representation.UnionRepresentation_Kinded 330 table := make(map[datamodel.Kind]schema.TypeName, len(rp.Keys)) 331 for _, kindStr := range rp.Keys { 332 kind := parseKind(kindStr) 333 member := rp.Values[kindStr] 334 switch { 335 case member.TypeName != nil: 336 memberName := *member.TypeName 337 if err := validMember(memberName); err != nil { 338 return nil, err 339 } 340 table[kind] = memberName 341 case member.UnionMemberInlineDefn != nil: 342 tname := anonLinkName(*member.UnionMemberInlineDefn.TypeDefnLink) 343 if err := validMember(tname); err != nil { 344 return nil, err 345 } 346 table[kind] = tname 347 } 348 } 349 repr = schema.SpawnUnionRepresentationKinded(table) 350 case typ.Representation.UnionRepresentation_Keyed != nil: 351 rp := typ.Representation.UnionRepresentation_Keyed 352 table := make(map[string]schema.TypeName, len(rp.Keys)) 353 for _, key := range rp.Keys { 354 member := rp.Values[key] 355 switch { 356 case member.TypeName != nil: 357 memberName := *member.TypeName 358 if err := validMember(memberName); err != nil { 359 return nil, err 360 } 361 table[key] = memberName 362 case member.UnionMemberInlineDefn != nil: 363 tname := anonLinkName(*member.UnionMemberInlineDefn.TypeDefnLink) 364 if err := validMember(tname); err != nil { 365 return nil, err 366 } 367 table[key] = tname 368 } 369 } 370 repr = schema.SpawnUnionRepresentationKeyed(table) 371 case typ.Representation.UnionRepresentation_StringPrefix != nil: 372 prefixes := typ.Representation.UnionRepresentation_StringPrefix.Prefixes 373 for _, key := range prefixes.Keys { 374 if err := validMember(prefixes.Values[key]); err != nil { 375 return nil, err 376 } 377 } 378 repr = schema.SpawnUnionRepresentationStringprefix("", prefixes.Values) 379 case typ.Representation.UnionRepresentation_Inline != nil: 380 rp := typ.Representation.UnionRepresentation_Inline 381 if rp.DiscriminantKey == "" { 382 return nil, fmt.Errorf("inline union has empty discriminantKey value") 383 } 384 if rp.DiscriminantTable.Keys == nil || rp.DiscriminantTable.Values == nil { 385 return nil, fmt.Errorf("inline union has empty discriminantTable") 386 } 387 for _, key := range rp.DiscriminantTable.Keys { 388 if err := validMember(rp.DiscriminantTable.Values[key]); err != nil { 389 return nil, err 390 } 391 } 392 repr = schema.SpawnUnionRepresentationInline(rp.DiscriminantKey, rp.DiscriminantTable.Values) 393 default: 394 return nil, fmt.Errorf("TODO: support other union repr in schema package") 395 } 396 for memberName, remaining := range remainingMembers { 397 if remaining { 398 return nil, fmt.Errorf("%q is not present in the union repr of %q", memberName, name) 399 } 400 } 401 return schema.SpawnUnion(name, 402 members, 403 repr, 404 ), nil 405 case defn.TypeDefnEnum != nil: 406 typ := defn.TypeDefnEnum 407 var repr schema.EnumRepresentation 408 409 // TODO: we should probably also reject duplicates. 410 validMember := func(name string) bool { 411 for _, memberName := range typ.Members { 412 if memberName == name { 413 return true 414 } 415 } 416 return false 417 } 418 switch { 419 case typ.Representation.EnumRepresentation_String != nil: 420 rp := typ.Representation.EnumRepresentation_String 421 for memberName := range rp.Values { 422 if !validMember(memberName) { 423 return nil, fmt.Errorf("%q is not a valid member of enum %q", memberName, name) 424 } 425 } 426 repr = schema.EnumRepresentation_String(rp.Values) 427 case typ.Representation.EnumRepresentation_Int != nil: 428 rp := typ.Representation.EnumRepresentation_Int 429 for memberName := range rp.Values { 430 if !validMember(memberName) { 431 return nil, fmt.Errorf("%q is not a valid member of enum %q", memberName, name) 432 } 433 } 434 repr = schema.EnumRepresentation_Int(rp.Values) 435 default: 436 return nil, fmt.Errorf("TODO: support other enum repr in schema package") 437 } 438 return schema.SpawnEnum(name, 439 typ.Members, 440 repr, 441 ), nil 442 case defn.TypeDefnLink != nil: 443 typ := defn.TypeDefnLink 444 if typ.ExpectedType == nil { 445 return schema.SpawnLink(name), nil 446 } 447 return schema.SpawnLinkReference(name, *typ.ExpectedType), nil 448 case defn.TypeDefnAny != nil: 449 return schema.SpawnAny(name), nil 450 default: 451 panic(fmt.Errorf("%#v", defn)) 452 } 453 }