Types and Layouts

A TreeLDR document is a collection of types and layouts.

Types

A type, sometimes called class, sort, or schema in other languages, is an object that describes the properties associated with a value instance of this type. In TreeLDR, types are simply declared using the type keyword:

type MyType; // Declares that `MyType` is a type.

In the example above, MyType is an empty type, and it doesn't say anything about the instances of MyType. We can extend this definition by listing all the properties of this type between braces:

type MyType {
	property1: Type1,
	// ...
	propertyN: TypeN
}

This specifies that each instance of MyType can have the above properties. By default those properties are optional. This can be changed using the required keyword:

type MyType {
	property1: required Type1,
	// ...
	propertyN: TypeN
}

Now each instance of MyType must have the property1 property. In this context, required is an attributes to the property1 property. Another useful attribute is multiple that states that a property can be associated with more than one value at once. Without attributes, properties are all optional and can take at most a single value.

Here is an example defining the Person type taking advantage of property attributes:

use <http://www.w3.org/2001/XMLSchema#> as xs;

/// A person.
type Person {
	/// Full name.
	name: required xs:string,

	/// Parents.
	parent: multiple Person,

	/// Age.
	age: xs:nonNegativeInteger
}

Layouts

When writing an application, one may need to represent the same data into multiple formats (one format inside the database, another in memory, transmission, etc.). In TreeLDR, those formats are called layouts. A layout is a particular representation for a type. It is sometimes called a view in other languages.

A layout defines a list of properties to include, how to name them (each property can have different name in different layouts), how to order them, and more. A layout is defined using the layout keyword. A single type can have multiple layouts.

/// A layout for `MyType` that includes every properties.
layout MyLayout for MyType {
	property1 as name1: Layout1,
	// ...
	propertyN as nameN: LayoutN
}

/// Another layout for `MyType` that only includes the first property.
layout MyOtherLayout for MyType {
	property1 as otherName: OtherLayout1
}

The as keyword is used to rename the considered property inside the layout. Without it, a default name is extracted from the identifier of the property. Here is a layout definition for the Person type defined above:

layout PersonLayout for Person {
	name as fullName: required xs:string,
	parent as parents: multiple PersonLayout,
	age: xs:nonNegativeInteger
}

Layouts are used to define a common representation for a given type that can be shared between applications. TreeLDR can export a layout definition into type definitions in different programming languages, and schema definitions for data exchange formats. For instance using the TreeLDR compiler, the PersonLayout schema definition above can be translated into the following:

  • A Rust type

pub struct PersonLayout {
	full_name: String,
	parents: BTreeSet<PersonLayout>,
	age: Option<u32>
}
  • A TypeScript type

type PersonLayout = {
	fullName: string;
	parents?: Array<PersonLayout>,
	age?: number
}
  • A JSON Schema

{
	"type": "object",
	"properties": {
		"fullName": {
			"type": "string"
		},
		"parents": {
			"type": "array",
			"item": {
				"$ref": "https://example.com/PersonLayout.schema.json"
			}	
		},
		"age": {
			"type": "integer"
		}
	},
	"required": [
		"fullName"
	]
}

These define a common representation that can be used to exchange instances of Person between a Rust application and a TypeScript application using JSON as the intermediate format. TreeLDR will also take care of generating the JSON serialization/deserialization routines in both languages, matching the above JSON Schema.

Default Layout

Each type definition is always also an implicit layout definition. If you consider again the previous Person type definition:

use <http://www.w3.org/2001/XMLSchema#> as xs;

/// A person.
type Person {
	/// Full name.
	name: required xs:string,

	/// Parents.
	parent: multiple Person,

	/// Age.
	age: xs:nonNegativeInteger
}

This definition implicitly embeds the following layout definition:

/// A person.
layout Person for Person {
	/// Full name.
	name: required xs:string,

	/// Parents.
	parent: multiple Person,

	/// Age.
	age: xs:nonNegativeInteger
}

It is possible to use layout-specific keywords (such as the as keyword to rename properties inside a layout) to affect the default layout definition. It is also possible to replace the default layout definition using the with keyword:

layout Person for Person {
	name: required xs:string,
	parent: multiple Person,
	age: xs:nonNegativeInteger
} with { // the default layout only includes the `name` property.
	name: required xs:string
}

Last updated