RSDL Semantics
DRAFT Initial Draft. July 2020
The semantic of RSDL (RAPID Schema Definition Language) can be described by mapping syntactical constructs described in rapid-rsdl-abnf to equivalent runtime Service Definition constructs.
Please refer to rapid-rsdl-abnf for the syntactical constructs of RSDL.
Model
A model is mapped to a CSDL Schema named "Model", that has an entity container named "Service".
- JSON
- XML
{
"$Version": "4.01",
"$EntityContainer": "Model.Service",
"Model": {
"Service": { "$Kind": "EntityContainer" }
}
}
<Schema Namespace="Model" xmlns="http://docs.oasis-open.org/odata/ns/edm">
<EntityContainer Name="Service">
</EntityContainer>
</Schema>
The model's service, structured types, and enumeration types are mapped to the respective constructs below and added to the schema (or container respectively)
Structured Types
A structured type is mapped to either an entity type or a complex type when converting from RSDL to CSDL.
A structured type with one or more properties marked with key
is mapped to an entity type.
Correspondingly, a structured type without key
properties is mapped to a complex type.
Structured Types with key
Properties
Here, we can see an example of how an RSDL structured type with a key
id property maps to a CSDL entity type.
type Employee {
key id: Integer
name : Name
}
- JSON
- XML
"Employee": {
"$Kind": "EntityType",
"$Key": [ "id" ],
"id": { "$Type": "Edm.Int32" },
"name": { "$Type": "Model.Name" }
}
<EntityType Name="Employee">
<Key>
<PropertyRef Name="id" />
</Key>
<Property Name="id" Type="Edm.Int32" Nullable="false" />
<Property Name="name" Type="Model.Name" Nullable="false" />
</EntityType>
Structured Types without key
Properties
On the other hand, an RSDL structured type without a key
property maps to a CSDL complex type.
Such types don't have keys because they are intended for organizing related properties into a group that is non-unique and does not stand on its own.
type Name {
firstName : String
lastName: String
}
- JSON
- XML
"Name": {
"$Kind": "ComplexType",
"firstName": { "$Type": "Edm.String" },
"lastName": { "$Type": "Edm.String" }
}
<ComplexType Name="Name">
<Property Name="firstName" Type="Edm.String" Nullable="false" />
<Property Name="lastName" Type="Edm.String" Nullable="false" />
</ComplexType>
Abstract Structured Types
A type may be defined as abstract
. An abstract type cannot be instantiated, but can be used as a base type for other type definitions.
abstract type Robot {
model: String
}
- JSON
- XML
"Robot": {
"$Kind": "ComplexType",
"$Abstract": true,
"model": { "$Type": "Edm.String" }
}
<ComplexType Name="Robot" Abstract="true">
<Property Name="model" Type="Edm.String" Nullable="false" />
</ComplexType>
Structured Type Inheritance
Types may extend other types through inheritance. The type derived from the base type inherits all properties, relationships, and operations bound to the base type (type inheritance) and can be used anywhere the base type is used (polymorphism).
type Android extends Robot {
name: String
}
- JSON
- XML
"Android": {
"$Kind": "ComplexType",
"$BaseType": "Model.Robot",
"name": { "$Type": "Edm.String" }
}
<ComplexType Name="Android" BaseType="Model.Robot">
<Property Name="name" Type="Edm.String" Nullable="false" />
</ComplexType>
Properties
The properties of a structured type are mapped to either a Property or a NavigationProperty depending on the property's type.
In the following example, let's assume name
is mapped to a complex type and employee
is mapped to an entity type.
type Company {
key stockSymbol: String
name: Name
employees: [Employee]
}
- JSON
- XML
"Company": {
"$Kind": "EntityType",
"$Key": [ "stockSymbol" ],
"stockSymbol": { "$Type": "Edm.String" },
"name": { "$Type": "Edm.String" },
"employees": {
"$Kind": "NavigationProperty",
"$Type": "Model.Employee",
"$Collection": true,
"$ContainsTarget": true
}
}
<EntityType Name="Company">
<Key>
<PropertyRef Name="stockSymbol" />
</Key>
<Property Name="stockSymbol" Type="Edm.String" Nullable="false" />
<Property Name="name" Type="Edm.String" Nullable="false" />
<NavigationProperty Name="employees" Type="Collection(Model.Employee)" ContainsTarget="true" />
</EntityType>
Property Types
The type of a property is one of:
- the built-in types defined in the
builtInType
syntax rule - the primitive EDM types listed in OData CSDL JSON Representation and OData CSDL XML Representation
- the structured or enumeration types defined in the model
Each of these named types can be marked as
- optional via a question mark
?
- multi-valued via enclosing it in brackets
[
]
type Foo {
test1: Integer
test2: Integer?
test3: [Integer]
test4: [Integer?]
test5: String
test6: String(80)
test7: Decimal
test8: Decimal(15,2)
}
- JSON
- XML
"Foo": {
"$Kind": "EntityType",
"test1": { "$Type": "Edm.Int32" },
"test2": { "$Nullable": true, "$Type": "Edm.Int32" },
"test3": { "$Collection": true, "$Type": "Edm.Int32" },
"test4": { "$Collection": true, "$Nullable": true, "$Type": "Edm.Int32" },
"test5": {},
"test6": { "$MaxLength": 80 },
"test7": { "$Type": "Edm.Decimal", "$Scale": "variable" },
"test8": { "$Type": "Edm.Decimal", "$Precision": 15, "$Scale": 2 },
}
<EntityType Name="Foo">
<Property Name="test1" Type="Edm.Int32" Nullable="false" />
<Property Name="test2" Type="Edm.Int32" Nullable="true" />
<Property Name="test3" Type="Collection(Edm.Int32)" Nullable="false"/>
<Property Name="test4" Type="Collection(Edm.Int32)" Nullable="true"/>
<Property Name="test5" Type="Edm.String" Nullable="false" />
<Property Name="test6" Type="Edm.String" Nullable="false" MaxLength="80" />
<Property Name="test7" Type="Edm.Decimal" Nullable="false" Scale="variable" />
<Property Name="test8" Type="Edm.Decimal" Nullable="false" Precision="15" Scale="2" />
</EntityType>
Actions and Functions
The syntactical production rule operation
is mapped to a bound action or a bound function in CSDL.
- operations with an
action
modifier are mapped to a CSDL Action - operations without a modifier are mapped to CSDL Function
The binding parameter of the function is inferred from the containing type production rule and named it
type Employee {
key id: Integer
foo()
}
- JSON
- XML
"foo": [
{
"$Kind": "Function",
"$IsBound": true,
"$IsComposable": true,
"$Parameter": [ { "$Name": "it", "$Type": "Model.Employee" } ]
}
]
<Function Name="foo" IsBound="true" IsComposable="true">
<Parameter Name="it" Type="Model.Employee" Nullable="false" />
</Function>
Function Return Types
The return type of a function is mapped similar to a property type with the same semantic for [
]
and ?
.
type Employee {
key id: Integer
foo() : Integer
bar() : [Integer]
}
- JSON
- XML
"foo": [
{
"$Kind": "Function",
"$IsBound": true,
"$IsComposable": true,
"$Parameter": [ { "$Name": "it", "$Type": "Model.Employee" } ],
"$ReturnType": { "$Type": "Edm.Int32" }
}
],
"bar": [
{
"$Kind": "Function",
"$IsBound": true,
"$IsComposable": true,
"$Parameter": [ { "$Name": "it", "$Type": "Model.Employee" } ],
"$ReturnType": { "$Collection": true, "$Type": "Edm.Int32" }
}
]
<Function Name="foo" IsBound="true" IsComposable="true">
<Parameter Name="it" Type="Model.Employee" Nullable="false" />
<ReturnType Type="Edm.Int32" Nullable="false" />
</Function>
<Function Name="bar" IsBound="true" IsComposable="true">
<Parameter Name="it" Type="Model.Employee" Nullable="false" />
<ReturnType Type="Collection(Edm.Int32)" Nullable="false" />
</Function>
Functions Parameters
Parameters are similar to properties in that they have a name and reference a type.
type Employee {
key id: Integer
foo(a: Integer, b: [Integer?])
}
- JSON
- XML
"foo": [
{
"$Kind": "Function",
"$IsBound": true,
"$IsComposable": true,
"$Parameter": [
{ "$Name": "it", "$Type": "Model.Employee" },
{ "$Name": "a", "$Type": "Edm.Int32" },
{ "$Name": "b", "$Collection": true, "$Type": "Edm.Int32", "$Nullable": true }
]
}
]
<Function Name="foo" IsBound="true" IsComposable="true">
<Parameter Name="it" Type="Model.Employee" Nullable="false" />
<Parameter Type="Edm.Int32" Nullable="false" />
<Parameter Type="Collection(Edm.Int32)" Nullable="true" />
</Function>
TODO: decide on optional parameters, how they are different from nullable required parameters, and if that is a feature required now or too much for RAPID
Enumeration Types
An Enumeration Type is mapped to a CSDL EnumType. The enumeration members' values are automatically assigned.
enum employmentType { salaried hourly }
- JSON
- XML
"employmentType": {
"$Kind": "EnumType",
"salaried": 0,
"hourly": 1
}
<EnumType Name="employmentType">
<Member Name="salaried" Value="0" />
<Member Name="hourly" Value="1" />
</EnumType>
Flag Types
A Flags Type, i.e an enumeration that starts with the keyword flags, is mapped to an CSDL EnumType with the IsFlags
property set to true
.
The members' values are automatically assigned to powers of 2.
flags PhoneService { LandLine Cell Fax Internet Other }
- JSON
- XML
"PhoneService": {
"$Kind": "EnumType",
"$IsFlags": true,
"LandLine": 1,
"Cell": 2,
"Fax": 4,
"Internet": 8,
"Other": 16
}
<EnumType Name="PhoneService" IsFlags="true">
<Member Name="LandLine" Value="1" />
<Member Name="Cell" Value="2" />
<Member Name="Fax" Value="4" />
<Member Name="Internet" Value="8" />
<Member Name="Other" Value="16" />
</EnumType>
Service
As mentioned above, every RAPID service model creates a CSDL entity container named "Service"
service {
}
- JSON
- XML
"Service": {
"$Kind": "EntityContainer"
}
<EntityContainer Name="Service">
Multi-Valued Service Member
A service member of a multi-valued type is mapped to a CSDL entity set.
service {
employees: [Employee]
}
- JSON
- XML
"Service": {
"$Kind": "EntityContainer",
"employees": { "$Collection": true, "$Type": "Model.Employee" }
}
<EntityContainer Name="Service">
<EntitySet Name="employees" EntityType="Model.Employee" />
</EntityContainer>
If the type is used as a type on a multi-value and as the type of a property of a structured type (i.e. a navigation property in CSDL),
the appropriate navigation property bindings get created.
The Company
type from the Properties section has an employees
property of the type Employee
.
The binding in CSDL defines that objects of these properties are bound to the employees
entity set.
service {
competitors: [Company]
}
- JSON
- XML
"Service": {
"$Kind": "EntityContainer",
"competitors": {
"$Collection": true,
"$Type": "Model.Company",
"$NavigationPropertyBinding": {
"employees": "employees"
}
}
}
<EntityContainer Name="Service">
<EntitySet Name="competitors" EntityType="Model.Company">
<NavigationPropertyBinding Path="employees" Target="employees" />
</EntitySet>
</EntityContainer>
Single-Valued Service Member
A service member of a single-value type gets mapped to a CSDL singleton.
service {
company: Company
}
- JSON
- XML
"Service": {
"$Kind": "EntityContainer",
"company": { "$Type": "Model.Company" }
}
<EntityContainer Name="Service">
<Singleton Name="company" Type="Model.Company" />
</EntityContainer>
Descriptions and Comments
All elements of a model can be described by lines starting with ##
preceding the model element.
These descriptions are mapped to annotations with term Core.Description in the corresponding CSDL construct.
In addition anything after a single #
character will be treated as a comment and ignored when mapping to CSDL.
## The Employees Service
# still needs some work
service {
## List of all employees
employees: [Employee]
}
- JSON
- XML
"Service": {
"$Kind": "EntityContainer",
"@Core.Description": "The Employees Service",
"employees": { "$Collection": true, "$Type": "Model.Employee", "@Core.Description": "List of all employees" }
}
<EntityContainer Name="Service">
<Annotation Term="Core.Description" String="The Employees Service" />
<EntitySet Name="employees" EntityType="Model.Employee">
<Annotation Term="Core.Description" String="List of all employees" />
</EntitySet>
</EntityContainer>