Introduction
Some objects share a shape and differ in one type. A page of users and a page of orders have the same fields; only the type of each item changes. This extension lets a package name that varying part and supply a concrete type where the object is used.
This extension is optional. It adds to the type grammar in the package specification. A package that does not use a type parameter or a generic application is unchanged.
Type parameter
Inside an object definition in the package's objects array, the exact string T is a type parameter. A generic object has one type parameter, and that parameter is T.
T MAY appear in any type of that object's attributes or arguments: as a bare type, inside a union, inside an array type, or as the value of a generic application.
T belongs to the object it appears in. Two objects MAY each use T. Applying one of them substitutes only that object's parameter.
T is in scope only inside that object definition. An endpoint's returns, and any type outside an object definition, MUST NOT be the bare string T.
An object whose definition contains T is a generic object.
Application
A generic application is a JSON object with exactly one key:
{ "<target>": <type> }
| Key and type | Description |
|---|---|
target string | Required. The single key. It MUST be a refined object type (object.<name>) or the built-in array constructor array. When the key is object.<name>, <name> MUST name a generic object in the package's objects array. |
type type | Required. The value. It MUST be a type: a base type, any, a refined type, a union, an array type, or another generic application. |
A generic application MAY appear anywhere a type is expected, including as the value of another generic application.
Applying object.<name> substitutes the value for every T in that object's definition. Applying array describes an array whose items are the value. { "object.page": "object.user" } is a page of users. { "array": "object.user" } is an array of users.
The package specification writes an array of users as an array type inside a union, [["object.user"]]. { "array": "object.user" } describes that same array.
Recursion
An object MAY refer to itself with a concrete type. A comment whose replies are an array of comments is valid:
{
"name": "comment",
"attributes": [
{ "name": "id", "type": "string.uuid" },
{ "name": "message", "type": "string" },
{ "name": "replies", "type": { "array": "object.comment" } }
]
}
replies names object.comment. The reference does not apply comment to a type parameter.
A generic object MUST NOT apply itself inside its own definition. The application MAY be direct, or it MAY pass through other generic objects. If page contains T, its definition MUST NOT contain { "object.page": ... }. If page applies box and box applies page, the package is invalid.
Nesting is allowed where the generic object is used. An endpoint MAY return { "object.page": { "object.page": "object.user" } }. That nesting supplies type arguments at the use. The forbidden cycle is an application inside a generic object's own definition.
Substitution
The value supplied for T MAY be a union, written as a JSON array of types. How that value replaces T depends on where T stands.
- When the supplied type is a single type, it replaces
TwhereverTstands. - When the supplied type is a union and
Tstands alone, the union replacesT.{ "array": "T" }is this case: the union replacesT, and the key staysarray. - When the supplied type is a union and
Tis one entry of a JSON array, the entries of the union are spliced into that array in place ofT. This applies to a union such as["T", "string"]and to an array type such as[["T"]].
After substitution, duplicate entries in a union are removed. The first occurrence of each entry is kept. A union of one entry is that entry: [A] is the same type as A.
An attribute of type ["T", "null"] applied to ["string", "number"] becomes ["string", "number", "null"]. The same attribute applied to "string" becomes ["string", "null"]. An attribute of type "T" applied to ["string", "number"] is the union ["string", "number"]. An attribute of type ["T", "string"] applied to ["string", "number"] becomes ["string", "number"] after the duplicate string is removed.
Naming
A generator that can express generic types in its target language MAY emit them. A generator that needs one named type for each application MUST build that name as follows.
The name of { "object.<name>": <type> } is <name> with its first letter capitalized, then Of, then the name of <type>.
The name of { "array": <type> } is ArrayOf followed by the name of <type>.
The name of a base type is that type with its first letter capitalized. string is String.
The name of object.<name> is <name> with its first letter capitalized. object.user is User.
The name of any other refined type is each segment with its first letter capitalized, joined together. string.uuid is StringUuid.
The name of a union is the names of its entries, in order, joined by Or.
| Application | Name |
|---|---|
{ "object.page": "object.user" } | PageOfUser |
{ "object.page": "string" } | PageOfString |
{ "object.page": { "object.page": "object.user" } } | PageOfPageOfUser |
{ "object.page": ["object.user", "string"] } | PageOfUserOrString |
{ "array": "object.user" } | ArrayOfUser |
{ "object.page": { "array": "object.user" } } | PageOfArrayOfUser |
Example
This package defines a page once and applies it to users.
{
"base_url": "https://api.example.com",
"name": "ExamplePackage",
"docs": "get-users returns a page of users.",
"objects": [
{
"name": "user",
"attributes": [
{ "name": "id", "type": "string.uuid" },
{ "name": "email", "type": "string.email" }
]
},
{
"name": "page",
"attributes": [
{ "name": "items", "type": { "array": "T" } },
{ "name": "total", "type": "number.u32" }
]
}
],
"endpoints": [
{
"name": "get-users",
"returns": { "object.page": "object.user" },
"docs": "Returns a page of users.",
"arguments": []
}
]
}
get-users returns a page of users. A generator that emits native generics can use a generic type for that result. A generator that names each application produces PageOfUser.
Nested application
A nested application uses the same object again:
{
"name": "get-grouped-users",
"returns": { "object.page": { "object.page": "object.user" } },
"arguments": []
}
That return type is a page of pages of users. A generator that names each application produces PageOfPageOfUser.