## -----
## This is the schema describing the schema declarations for IPLD Schemas.
## Yes, it's self-describing! :)
## -----

## Type names are a simple alias of string.
##
## There are some additional rules that should be applied. Type names:
##   - *Must* only contain alphanumeric ASCII characters and underscores
##   - *Must* begin with a capital letter
##   - *Should* avoid more than one connected underscore character,
##     multiple-underscores may be used for codegen
##
## Type names are strings meant for human consumption at a local scope.
## When making a Schema, note that the TypeName is the key of the map:
## a TypeName must be unique within the Schema.
##
type TypeName string

## SchemaMap is a complete set of types;
## it is simply a map of TypeName to detailed declaration of that Type.
##
## A simple schema map with one type might look like this:
##
## ```
## {
##   "MyFooType": {
##     "type": "string"
##   }
## }
## ```
##
type SchemaMap {TypeName:Type}

## AdvancedDataLayoutName defines the name of an ADL as a string.
##
## The same constraints and conventions apply as for TypeName.
##
## This identifier is used for keys in the AdvancedDataLayoutMap and also as
## references to ADLs where the "advanced" representation strategy is used for
## the types that support it.
##
type AdvancedDataLayoutName string

## AdvancedDataLayoutMap defines the set of ADLs found within the schema. It
## maps the name (AdvancedDataLayoutName) to the AdvancedDataLayout, which is
## currently an empty map.
##
type AdvancedDataLayoutMap {AdvancedDataLayoutName:AdvancedDataLayout}

## Schema is a single-member union, which can be used in serialization
## to make a form of "nominative type declaration".
##
## A complete (if quite short) Schema might look like this:
##
## ```
## {
##   "schema": {
##     "MyFooType": {
##       "type": "string"
##     }
##   }
## }
## ```
##
type Schema struct {
	types SchemaMap
	advanced AdvancedDataLayoutMap
}

## The types of Type are a union.
##
## The Type union is serialized using "inline" union representation,
## which means all of its members have map representations, and there will be
## an entry in that map called "type" which contains the union discriminant.
##
## Some of the kinds of type are so simple the union discriminant is the only
## content at all, e.g. strings:
##
## ```
## {
##   "type": "string"
## }
## ```
##
## Other types have more content.  Consider this example of a map type:
##
## ```
## {
##   "type": "map",
##   "keyType": "String",
##   "valueType": "Int"
## }
## ```
##
type Type union {
	| TypeBool "bool"
	| TypeString "string"
	| TypeBytes "bytes"
	| TypeInt "int"
	| TypeFloat "float"
	| TypeMap "map"
	| TypeList "list"
	| TypeLink "link"
	| TypeUnion "union"
	| TypeStruct "struct"
	| TypeEnum "enum"
	| TypeCopy "copy"
} representation inline {
	discriminantKey "kind"
}

## TypeKind enumerates all the major kinds of type.
## Notice this enum's members are the same as the set of strings used as
## discriminants in the Type union.
##
## TODO: not actually sure we'll need to declare this.  Only usage is
## in the Type union representation details?
type TypeKind enum {
	| Bool
	| String
	| Bytes
	| Int
	| Float
	| Map
	| List
	| Link
	| Union
	| Struct
	| Enum
}

## RepresentationKind is similar to TypeKind, but includes only those concepts
## which exist at the IPLD *Data Model* level.
##
## In other words, structs, unions, and enumerations are not present:
## those concepts are introduced in the IPLD Schema system, and when serialized,
## all of them must be transformable to one of these representation kinds
## (e.g. a "struct" TypeKind will usually be transformed to a "map"
## RepresentationKind; "enum" TypeKind are always "string" RepresentationKind;
## and so on.)
##
## RepresentationKind strings are sometimes used to to indicate part of the
## definition in the details of Type; for example, they're used describing
## some of the detailed behaviors of a "kinded"-style union type.
type RepresentationKind enum {
	| Bool
	| String
	| Bytes
	| Int
	| Float
	| Map
	| List
	| Link
}

## AnyScalar defines a union of the basic non-complex kinds.
##
## Useful defining usage of IPLD nodes that do compose from other nodes.
##
type AnyScalar union {
	| Bool bool
	| String string
	| Bytes bytes
	| Int int
	| Float float
} representation kinded

## AdvancedDataLayout defines `advanced` definitions which are stored in the
## top-level "advanced" map (AdvancedDataLayoutMap)
##
## Used as `advanced Foo` rather than `type Foo` to indicate an advanced data
## layout (ADL) with that name which can be used as a representation for type
## definitions whose kind the ADL is able to support.
##
## The AdvancedDataLayoutName is currently the only identifier that can be used
## to make a connection with the algorithm/logic behind this ADL. Future
## iterations may formalize this connection by some other means.
##
type AdvancedDataLayout struct {}

## TypeBool describes a simple boolean type.
## It has no details.
##
type TypeBool struct {}

## TypeString describes a simple string type.
## It has no details.
##
type TypeString struct {}

## TypeBytes describes a simple byte array type.
##
type TypeBytes struct {
	representation BytesRepresentation
}

## BytesRepresentation specifies how a TypeBytes is to be serialized. By
## default it will be stored as bytes in the data model but it may be replaced
## with an ADL.
##
type BytesRepresentation union {
	| BytesRepresentation_Bytes "bytes"
	| AdvancedDataLayoutName "advanced"
} representation keyed

## BytesRepresentation_Bytes is the default representation for TypeBytes and
## will be used implicitly if no representation is specified.
##
type BytesRepresentation_Bytes struct {}

## TypeInt describes a simple integer numeric type.
## It has no details.
##
type TypeInt struct {}

## TypeFloat describes a simple floating point numeric type.
## It has no details.
##
type TypeFloat struct {}

## TypeMap describes a key-value map.
## The keys and values of the map have some specific type of their own.
##
type TypeMap struct {
	keyType TypeName # additionally, the referenced type must be reprkind==string.
	valueType TypeTerm
	valueNullable Bool (implicit "false")
	representation MapRepresentation
} representation map

## MapRepresentation describes how a map type should be mapped onto
## its IPLD Data Model representation.  By default a map is a map in the
## Data Model but other kinds can be configured.
##
type MapRepresentation union {
	| MapRepresentation_Map "map"
	| MapRepresentation_StringPairs "stringpairs"
	| MapRepresentation_ListPairs "listpairs"
	| AdvancedDataLayoutName "advanced"
} representation keyed

## MapRepresentation_Map describes that a map should be encoded as
## a map in the Data Model
##
type MapRepresentation_Map struct {}

## MapRepresentation_StringPairs describes that a map should be encoded as a
## string of delimited "k/v" entries, e.g. "k1=v1,k2=v2".
## The separating delimiter may be specified with "entryDelim", and the k/v
## delimiter may be specified with "innerDelim". So a "k=v" naive
## comma-separated form would use an "innerDelim" of "=" and an "entryDelim"
## of ",".
##
## This serial representation is limited: the domain of keys must
## exclude the "innerDelim" and values and keys must exclude ",".
## There is no facility for escaping, such as in escaped CSV.
## This also leads to a further restriction that this representation is only
## valid for maps whose keys and values may all be encoded to string form
## without conflicts in delimiter character. It is recommended, therefore,
## that its use be limited to maps containing values with the basic data
## model kinds that exclude multiple values (i.e. no maps, lists, and therefore
## structs or unions).
##
type MapRepresentation_StringPairs struct {
	innerDelim String
	entryDelim String
}

## MapRepresentation_ListPairs describes that a map should be encoded as a
## list in the IPLD Data Model. This list comprises a sub-list for each entry,
## in the form: [[k1,v1],[k2,v2]].
##
## This representation type is similar to StructRepresentation_Tuple except
## it includes the keys. This is critical for maps since the keys are not
## defined in the schema (hence "tuple" representation isn't available for
## maps).
##
type MapRepresentation_ListPairs struct {}

## TypeList describes a list.
## The values of the list have some specific type of their own.
##
type TypeList struct {
	valueType TypeTerm
	valueNullable Bool (implicit "false")
	representation ListRepresentation
} representation map

## ListRepresentation describes how a map type should be mapped onto
## its IPLD Data Model representation.  By default a list is a list in the
## data model but it may be replaced with an ADL.
##
type ListRepresentation union {
	| ListRepresentation_List "list"
	| AdvancedDataLayoutName "advanced"
} representation keyed

## ListRepresentation_List is the default representation for TypeList and
## will be used implicitly if no representation is specified.
##
type ListRepresentation_List struct {}

## TypeLink describes a hash linking to another object (a CID).
##
## A link also has an "expectedType" that provides a hinting mechanism
## suggesting what we should find if we were to follow the link. This
## cannot be strictly enforced by a node or block-level schema
## validation but may be enforced elsewhere in an application relying on
## a schema.
##
## The expectedType is specified with the `&Any` link shorthand, where
## `Any` may be replaced with a specific type.
##
## Unlike other kinds, we use `&Type` to denote a link Type rather than
## `Link`. In this usage, we replace `Type` the expected Type, with `&Any`
## being shorthand for "a link which may resolve to a type of any kind".
##
## `expectedType` is a String, but it should validate as "Any" or a TypeName
## found somewhere in the schema.
##
type TypeLink struct {
	expectedType String (implicit "Any")
}

## TypeUnion describes a union (sometimes called a "sum type", or
## more verbosely, a "discriminated union").
## A union is a type that can have a value of several different types, but
## unlike maps or structs, in a union only one of those values may be present
## at a time.
##
## Unions can be defined as representing in several different ways: see
## the documentation on the UnionRepresentation type for details.
##
## The set of types which the union can contain are specified in a map
## inside the representation field.  (The key type of the map varies per
## representation strategy, so it's not possible to keep on this type directly.)
##
type TypeUnion struct {
	representation UnionRepresentation
}

## UnionRepresentation is a union of all the distinct ways a TypeUnion's values
## can be mapped onto a serialized format for the IPLD Data Model.
##
## There are five strategies that can be used to encode a union:
## "keyed", "envelope", "inline", "byteprefix", and "kinded".
## The "keyed", "envelope", and "inline" strategies are all ways to produce
## representations in a map format, using map keys as type discriminators
## (some literature may describe this as a "tagged" style of union).
## The "byteprefix" strategy, only available only for unions in which all
## member types themselves represent as bytes in the data model, uses another
## byte as the type discrimination hint (and like the map-oriented strategies,
## may also be seen as a form of "tagged" style unions).
## The "kinded" strategy can describe a union in which member types have
## several different representation kinds, and uses the representation kind
## itself as the type discrimination hint to do so.
##
## Note: Unions can be used to produce a "nominative" style of type declarations
## -- yes, even given that IPLD Schema systems are natively "structural" typing!
##
type UnionRepresentation union {
	| UnionRepresentation_Kinded "kinded"
	| UnionRepresentation_Keyed "keyed"
	| UnionRepresentation_Envelope "envelope"
	| UnionRepresentation_Inline "inline"
	| UnionRepresentation_BytePrefix "byteprefix"
} representation keyed

## "Kinded" union representations describe a bidirectional mapping between
## a RepresentationKind and a Type (referenced by name) which should be the
## union member decoded when one sees this RepresentationKind.
##
## The referenced type must of course produce the RepresentationKind it's
## matched with!
type UnionRepresentation_Kinded {RepresentationKind:TypeName}

## "Keyed" union representations will encode as a map, where the map has
## exactly one entry, the key string of which will be used to look up the name
## of the Type; and the value should be the content, and be of that Type.
##
## Note: when writing a new protocol, it may be wise to prefer keyed unions
## over the other styles wherever possible; keyed unions tend to have good
## performance characteristics, as they have most "mechanical sympathy" with
## parsing and deserialization implementation order.
type UnionRepresentation_Keyed {String:TypeName}

## "Envelope" union representations will encode as a map, where the map has
## exactly two entries: the two keys should be of the exact strings specified
## for this envelope representation.  The value for the discriminant key
## should be one of the strings in the discriminant table.  The value for
## the content key should be the content, and be of the Type matching the
## lookup in the discriminant table.
type UnionRepresentation_Envelope struct {
	discriminantKey String
	contentKey String
	discriminantTable {String:TypeName}
}

## "Inline" union representations require that all of their members encode
## as a map, and encode their type info into the same map as the member data.
## Thus, the map for an inline union may have any number of entries: it is
## however many fields the member value has, plus one (for the discriminant).
##
## All members of an inline union must be struct types and must encode to
## the map RepresentationKind.  Other types which encode to map (such as map
## types themselves!) cannot be used: the potential for content values with
## with keys overlapping with the discriminantKey would result in undefined
## behavior!  Similarly, the member struct types may not have fields which
## have names that collide with the discriminantKey.
##
## When designing a new protocol, use inline unions sparringly; despite
## appearing simple, they have the most edge cases of any kind of union
## representation, and their implementation is generally the most complex and
## is difficult to optimize deserialization to support.
type UnionRepresentation_Inline struct {
	discriminantKey String
	discriminantTable {String:TypeName}
}

## UnionRepresentation_BytePrefix describes a union representation for unions
## whose member types are all bytes. It is encoded to a byte array whose
## first byte is the discriminator and subsequent bytes form the discriminated
## type.
##
## byteprefix is an invalid representation for any union that contains a type
## that does not have a bytes representation.
##
type UnionRepresentation_BytePrefix struct {
	discriminantTable {TypeName:Int}
}

## TypeStruct describes a type which has a group of fields of varying Type.
## Each field has a name, which is used to access its value, similarly to
## accessing values in a map.
##
## The most typical representation of a struct is as a map, in which case field
## names also serve as the the map keys (though this is a default, and details
## of this representation may be configured; and other representation strategies
## also exist).
##
type TypeStruct struct {
	fields {FieldName:StructField}
	representation StructRepresentation
}

## FieldName is an alias of string.
##
## There are some additional rules that should be applied:
##   - Field names should by convention begin with a lower-case letter;
##   - Field names must be all printable characters (no whitespace);
##   - Field names must not contain punctuation other than underscores
##     (dashes, dots, etc.).
##
## Field names are strings meant for human consumption at a local scope.
## When making a Schema, note that the FieldName is the key of the map:
## a FieldName must be unique within the Schema.
##
type FieldName string

## StructField describes the properties of each field declared by a TypeStruct.
##
## StructField contains properties similar to TypeMap -- namely, it describes
## a content type (as a TypeTerm -- it supports inline definitions) -- and
## has a boolean property for whether or not the value is permitted to be null.
##
## In addition, StructField also has a property called "optional".
## An "optional" field is one which is permitted to be absent entirely.
## This is distinct from "nullable": a field can be optional=false and
## nullable=true, in which case it's an error if the key is missing entirely,
## but null is of course valid.  Conversely, if a field is optional=true and
## nullable=false, it's an error if the field is present and assigned null, but
## fine for a map to be missing a key of the field's name entirely and still be
## recognized as this struct.
## (The specific behavior of optionals may vary per StructRepresentation.)
##
## Note that the 'optional' and 'nullable' properties are not themselves
## optional... however, in the IPLD serial representation of schemas, you'll
## often see them absent from the map encoding a StructField.  This is because
## these fields are specified to be implicitly false.
## Implicits in a map representation of a struct mean that those entries may
## be missing from the map encoding... but unlike with "optional" fields, there
## is no "undefined" value; absence is simply interpreted as the value specified
## as the implicit.
## (With implicit fields, an explicitly encoded implicit value is actually an
## error instead!)  "Optional" fields give rise to N+1 cardinality logic,
## just like "nullable" fields; "implicit" fields *do not*.
##
type StructField struct {
	type TypeTerm
	optional Bool (implicit "false")
	nullable Bool (implicit "false")
} representation map

## TypeTerm is a union of either TypeName or an InlineDefn. th It's used for the
## value type in the recursive types (maps, lists, and the fields of structs),
## which allows the use of InlineDefn in any of those positions.
##
## TypeTerm is simply a TypeName if the kind of data is a string; this is the
## simple case.
##
## Note that TypeTerm isn't used to describe *keys* in the recursive types that
## have them (maps, structs) -- recursive types in keys would not lend itself
## well to serialization!
## TypeTerm also isn't used to describe members in Unions -- this is a choice
## aimed to limit syntactical complexity (both at type definition authoring
## time, as well as for the sake of error messaging during typechecking).
##
type TypeTerm union {
	| TypeName string
	| InlineDefn map
} representation kinded

## InlineDefn represents a declaration of an anonymous type of one of the simple
## recursive kinds (e.g. map or list) which is found "inline" in another type's
## definition.  It's the more complex option of the TypeTerm union.
##
## Note that the representation of this union -- `representation inline "kind"`
## -- as well as the keywords for its members -- align exactly with those
## in the Type union.  Technically, this isn't a necessary property (in that
## nothing would break if that sameness was violated) but it's awfully nice for
## sanity; what we're saying here is that the representation of the types in an
## InlineDefn should look *exactly the same* as the top-level Types... it's just
## that we're restricted to a subset of the members.
##
type InlineDefn union {
	| TypeMap "map"
	| TypeList "list"
} representation inline {
	discriminantKey "kind"
}

## StructRepresentation describes how a struct type should be mapped onto
## its IPLD Data Model representation.  Typically, maps are the representation
## kind, but other kinds and details can be configured.
##
type StructRepresentation union {
	| StructRepresentation_Map "map"
	| StructRepresentation_Tuple "tuple"
	| StructRepresentation_StringPairs "stringpairs"
	| StructRepresentation_StringJoin "stringjoin"
	| StructRepresentation_ListPairs "listpairs"
} representation keyed

## StructRepresentation_Map describes a way to map a struct type onto a map
## representation. Field serialization options may optionally be configured to
## enable mapping serialized keys using the 'rename' option, or implicit values
## specified where the field is omitted from the serialized form using the
## 'implicit' option.
##
## See StructRepresentation_Map_FieldDetails for details on the 'rename' and
## 'implicit' options.
##
type StructRepresentation_Map struct {
	fields optional {FieldName:StructRepresentation_Map_FieldDetails}
}

## StructRepresentation_Map_FieldDetails describes additional properties of a
## struct field when represented as a map.  For example, fields may be renamed,
## or implicit values associated.
##
## If an implicit value is defined, then during marshalling, if the actual value
## is the implicit value, that field will be omitted from the map; and during
## unmarshalling, correspondingly, the absence of that field will be interpreted
## as being the implicit value.
##
## Note that fields with implicits are distinct from fields which are optional!
## The cardinality of membership of an optional field is is incremented:
## e.g., the cardinality of "fieldname Bool" is 2; "fieldname optional Bool" is
## membership cardinality *3*, because it may also be undefined.
## By contrast, the cardinality of membership of a field with an implicit value
## remains unchanged; there is serial state which can map to an undefined value.
##
## Note that 'rename' supports exactly one string, and not a list: this is
## intentional.  The rename feature is meant to allow serial representations
## to use a different key string than the schema type definition field name;
## it is not intended to be used for migration purposes.
##
type StructRepresentation_Map_FieldDetails struct {
	rename optional String
	implicit optional AnyScalar
}

## StructRepresentation_Tuple describes a way to map a struct type into a list
## representation.
##
## Tuple representations are less flexible than map representations:
## field order can be specified in order to override the order defined
## in the type, but optionals and implicits are not (currently) supported.
## A `fieldOrder` list must include quoted strings (FieldName is a string
## type) which are coerced to the names of the struct fields. e.g.:
##   fieldOrder ["Foo", "Bar", "Baz"]
##
type StructRepresentation_Tuple struct {
	fieldOrder optional [FieldName]
}

## StructRepresentation_StringPairs describes that a struct should be encoded
## as a string of delimited "k/v" entries, e.g. "k1=v1,k2=v2".
## The separating delimiter may be specified with "entryDelim", and the k/v
## delimiter may be specified with "innerDelim". So a "k=v" naive
## comma-separated form would use an "innerDelim" of "=" and an "entryDelim"
## of ",".
##
## Serialization a struct with stringpairs works the same way as serializing
## a map with stringpairs and the same character limitations exist. See
## MapRepresentation_StringPairs for more details on these limitations.
##
type StructRepresentation_StringPairs struct {
	innerDelim String
	entryDelim String
}

## StructRepresentation_StringJoin describes a way to encode a struct to
## a string in the IPLD Data Model. Similar to tuple representation, the
## keys are dropped as they may be inferred from the struct definition.
## values are concatenated, in order, and separated by a "join" delimiter.
## For example, specifying ":" as the "join": "v1,v2,v3".
##
## stringjoin is necessarily restrictive and therefore only valid for structs
## whose values may all be encoded to string form without conflicts in "join"
## character. It is recommended, therefore, that its use be limited to structs
## containing values with the basic data model kinds that exclude multiple
## values (i.e. no maps, lists, and therefore structs or unions).
##
type StructRepresentation_StringJoin struct {
	join String
	fieldOrder optional [FieldName]
}

## StructRepresentation_ListPairs describes that a struct, should be encoded as
## a list in the IPLD Data Model. This list comprises a sub-list for each
## entry, in the form: [[k1,v1],[k2,v2]].
##
## This representation type encodes in the same way as
## MapStructRepresentation_Tuple. It is also similar to
## StructRepresentation_Tuple except it includes the keys in nested lists.
## A tuple representation for a struct will encode more compact than listpairs.
##
type StructRepresentation_ListPairs struct {}

## TypeEnum describes a type which has a known, pre-defined set of possible
## values. Each of the values must be representable as a string (EnumValue)
## when using the default "string" representation, or when using an "int"
## representation, an integer must also be supplied along with the EnumValue.
##
## Integer and string values (for int and string representations respectively)
## are provided in parens in the DSL. Where the string used in serialization is
## the same as the EnumValue, it may be omitted. For int representation enums,
## all int values are required.
##
type TypeEnum struct {
  members {EnumValue:Null}
	representation EnumRepresentation
}

## EnumValue is a string that has limitations for use as a member of an enum
## set. The rules for EnumValue are the same as for TypeName but without the
## convention of an uppercase first character. Capitalization is left up to
## the user.
##
type EnumValue string

## EnumRepresentation describes how an enum type should be mapped onto
## its IPLD Data Model representation. By default an enum is represented as a
## string kind but it may also be represented as an int kind.
##
type EnumRepresentation union {
	| EnumRepresentation_String "string"
	| EnumRepresentation_Int "int"
} representation keyed

## EnumRepresentation_String describes the way an enum is represented as a
## string in the data model. By default, the strings used as EnumValue will be
## used at the serialization. A custom string may be provided (with `Foo ("x")`
## in the DSL) which will be stored here in the representation block. Missing
## entries in this map will use the default.
##
type EnumRepresentation_String {EnumValue:String}

## EnumRepresentation_Int describes the way an enum is represented as an int
## in the data model. A mapping of names to ints is required to perform the
## conversion from int to enum value. In the DSL, int values _must_ be provided
## for each EnumValue (with `Foo ("100")`, those are stored here.
##
type EnumRepresentation_Int {EnumValue:Int}

## TypeCopy describes a special "copy" unit that indicates that a type name
## should copy the type descriptor of another type. TypeCopy does not redirect a
## name to another type. Instead, it copies the entire type definition and
## assigns it to another type.
##
## The DSL defines a TypeCopy as `type NewThing = CopiedThing`, where
## "CopiedThing" refers to a `type` defined elsewhere in a schema and is not
## one of TypeKind or an inline type descriptor (`{}`, `[]`, `&`).
##
type TypeCopy struct {
	fromType TypeName
}