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  }