Describing schemas
Basic Types
Type Hierarchy
Type System Foundation
Core Primitive Types
Logical
Type | Description |
---|---|
Boolean | Represents a value which is either true or false |
Numeric
Type | Description |
---|---|
Int | A signed integer - a whole number (positive or negative), with no decimal places |
Long | A signed long - a whole number (positive or negative), with no decimal places |
Decimal | A signed decimal number - a whole number with decimal places |
Double | A double-precision 64-bit IEEE 754 floating point number |
Text
Type | Description |
---|---|
String | A collection of characters |
Date and Time Types
Time Handling
Type | Description | Default Format | Example |
---|---|---|---|
Date | A date, without time or timezone | yyyy-MM-dd | 2024-03-15 |
Time | Time only, excluding the date part | HH:mm:ss | 14:30:00 |
DateTime | A date and time, without timezone | yyyy-MM-dd'T'HH:mm:ss.SSS | 2024-03-15T14:30:00.000 |
Instant | A point in time with timezone | yyyy-MM-dd'T'HH:mm:ss[.SSS]X | 2024-03-15T14:30:00Z |
Format Symbols:
Symbol | Meaning | Example |
---|---|---|
y | Year | 2024 |
M | Month | 07 or July |
d | Day | 25 |
H | Hour (0-23) | 13 |
m | Minute | 30 |
s | Second | 45 |
S | Millisecond | 678 |
X | Timezone (accepts Z) | Z or +01:00 |
Z | RFC 822 timezone | +0100 |
Example Usage:
// Define types with specific formats
@Format("dd/MM/yyyy")
type BirthDate inherits Date
@Format("yyyy-MM-dd HH:mm")
type AppointmentTime inherits DateTime
@Format("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
type EventTimestamp inherits Instant
Collection Types
Arrays
Arrays can be declared using either bracket notation or generic syntax:
model Person {
// These declarations are equivalent
friends : Person[]
alsoFriends : Array<Person>
}
Maps
Maps are key-value collections:
type Inventory inherits Map<ProductId, Quantity>
Using Collections
- Use arrays for ordered collections of the same type
- Only use maps when you need dynamic key-value relationships
- Consider creating a proper model instead of a map if the structure is known
Union and Intersection types
Taxi has partial (but growing) support for union and intersection types.
Theses are most commonly found in queries, specifically relating to joining streams.
Merging properties
For both Union Types and Intersection Types inherit all the properties of their underlying types:
model Person {
name : Name
}
model LivingThing {
age : Age
}
type PersonOrAge = Person | Age
type PersonWithAge = Person & Age
In both scenarios, the resulting type contains two fields:
name : Name
age : Age
Dealing with name conflicts
If both types declare a type with the same name, this results in a conflict. Conflicts are resolved as follows:
- If both types have the same name and the same type, the resulting type will contain only a single field.
model Person {
name : Name
}
model LivingThing {
name : Name
}
type PersonWithAge = Person & Age // contains only name : Name
- If both models have fields with the same name, but different types, the declaring model’s name is prepended:
model Tweet {
id : TweetId inherits Int
user : UserId
}
model User {
id : UserId inherits Int
}
// Contains fields Tweet_id and User_id
type TweetAndUser = Tweet | User
- If both models have fields with the same name, and both models also have the same name, then the declaring models fully qualified name is prepended:
namespace foo {
model Tweet {
id : TweetId inherits Int
user : UserId
}
}
namespace bar {
model Tweet {
id : UserId inherits Int
}
}
// Contains fields foo_Tweet_id and bar_Tweet_id
type TweetAndUser = Tweet | User
Union types
A union type
is declared using the |
operator, and broadly represents “This or That”
// Subscribe to feeds of both Foo and Bar, and emit
// when either emit a message.
stream { Foo | Bar }
Intersection types
Intersection types are declared using the &
operator, and broadly represent “This and That”
// Subscribe to feeds of both Foo and Bar, and emit
// only after both have emitted a message
stream { Foo & Bar }
A stream of an intersection type is generally stateful, so requires some form of State Store provided by the TaxiQL engine. Read more about this in State Stores.
Limitations
Currently, Union and Intersection types are only supported as discovery types in a stream - as shown above.
Over time, support will expand to cover scenarios like:
- Support for declaring top-level union / intersection types:
// This is currently legal, but not fully supported by query engines
type Baz = Foo & Bar
type BreadRoll = Foo | Bar
- Fields with Union Types (similar to OpenAPI’s
anyof
) find { }
queries, where the behaviour is similar tostream {}
queries:find { A | B }
- Load A and B, return if either produce a resultfind { A & B }
- Load A and B, return only if both produce a result
Special Types
Nothing
Nothing
is a special type indicating that a function or expression never returns normally, typically due to throwing an exception.
As the bottom type in the type hierarchy, Nothing
is a subtype of all other types.
This allows Nothing
expressions to be assigned to any type, useful for handling exceptional cases.
Characteristics:
- No Instances: Represents the absence of a value
- Bottom Type: Subtype of all types, enabling flexible type assignments
- Control Flow: Used for functions that do not complete normally
Any
Any
is the root type in the Taxi type system, representing the most general type. All other types inherit from Any
, making it the universal supertype.
Characteristics:
- Universal Supertype: All types inherit from
Any
- Extensibility: Provides a common base for defining more specific semantic subtypes
Using Any
Void
Represents the absence of a return value in operations that don’t return anything.
Type Nullability
By default, all types are non-nullable. Use the ?
operator to make a type nullable:
model Person {
// Required fields
id : PersonId
name : PersonName
// Optional fields
nickname : Nickname?
middleName : MiddleName?
}
Nullability Enforcement
Enums
Enums in Taxi allow you to define a fixed set of values. Values can be explicitly provided or inferred from the enum name.
// Basic enum - values are same as names
enum BookCategory {
FICTION,
NON_FICTION
}
// Enum with explicit values
enum Country {
NEW_ZEALAND("NZ"),
AUSTRALIA("AUS"),
UNITED_KINGDOM("UK")
}
Value Types and Inference
Taxi automatically infers the enum’s base type based on its values:
// Inferred as Int
enum Numbers {
One(1),
Two(2)
}
// Inferred as String when mixing types
enum Mixed {
One("One"),
Two(2) // Will be converted to string
}
// Boolean values
enum Selected {
Yes(true),
No(false)
}
// String booleans as members
enum Flags {
`true`, // Note the backticks
`false`
}
Type Inference
Generic Enums with Object Bodies
Enums can hold structured data using generic type parameters:
model ErrorDetails {
code : ErrorCode inherits Int
message : ErrorMessage inherits String
}
enum Errors<ErrorDetails> {
BadRequest({ code : 400, message : 'Bad Request' }),
Unauthorized({ code : 401, message : 'Unauthorized' })
}
You can access properties of enum values:
model Response {
// Access a property of an enum value
errorCode: ErrorCode by Errors.BadRequest.code
// Access nested properties
errorMessage: ErrorMessage by Errors.BadRequest.message
}
Generic Constraints
Lenient Matching
Basic Lenient Matching
lenient enum Country {
NZ("New Zealand"),
AUS("Australia")
}
This allows:
- Case-insensitive name matching:
"nz"
matchesCountry.NZ
- Case-insensitive value matching:
"new zealand"
matchesCountry.NZ
Special Characters in Lenient Matching
lenient enum DayCountConvention {
ACT_360("ACT/360")
}
This matches:
"Act/360"
"ACT/360"
"act/360"
Default Values
Enums can specify a default value for unmatched inputs:
enum Country {
NZ("New Zealand"),
AUS("Australia"),
default UNKNOWN("Unknown")
}
Default values work with both names and values:
"UK"
resolves toCountry.UNKNOWN
- Can be combined with lenient matching
- Only one default value is allowed per enum
Synonyms
Basic Synonyms
enum English {
One,
Two
}
enum French {
Un synonym of English.One,
Deux synonym of English.Two
}
Multiple Synonyms
enum English { One }
enum French { Un }
enum Australian {
One synonym of [English.One, French.Un]
}
Synonym Characteristics
Array Support
Enums can be used in array literals:
enum Country {
NZ("NZD"),
AU("AUD")
}
// Arrays can contain enum values
given { countries: Country[] = ["NZD", "AUD"] }
// Arrays can use enum names
given { countries: Country[] = ["NZ", "AU"] }
// Arrays can use enum references
given { countries: Country[] = [Country.NZ, Country.AU] }
Enums - common Patterns
1. Status Enums
enum Status<StatusDetails> {
ACTIVE({ code: "A", description: "Active" }),
INACTIVE({ code: "I", description: "Inactive" }),
default UNKNOWN({ code: "U", description: "Unknown" })
}
2. Code Mappings
enum ExternalMapping {
INTERNAL_A("EXT_1") synonym of Internal.A,
INTERNAL_B("EXT_2") synonym of Internal.B
}
3. Validation Results
model ValidationDetails {
severity: Severity inherits String
code: Code inherits Int
message: Message inherits String
}
enum ValidationResult<ValidationDetails> {
OK({ severity: "INFO", code: 0, message: "Valid" }),
ERROR({ severity: "ERROR", code: 1, message: "Invalid" })
}