1.12
2022-03-02
The Routing Information Modeling Language is a compact YAML-based dialect for defining routing information and API documentation.
Kind of inspired by RAML and a few other similar descriptive schemas, but was designed with some specific hybrid use-cases in mind (specifically one schema that could be used to define routes in a web app, generate API documentation, and generate automated tests.)
Language | Package | Description |
---|---|---|
PHP | riml/riml-parser | A pure parser library. See lum/lum-core for an example of a package that uses RIML to build routing configuration files. |
Node.js | riml | A pure-JS parser library. |
Global keywords can appear anywhere in a RIML document, and the closest defined item will be used if it's not found in a Route.
Keyword | Description | Valid values |
---|---|---|
version |
The API version. | number , string |
title |
A short name for documentation. | string |
description |
A full description for documentation. | string |
controller |
The backend controller that handles this route. | string |
method |
The controller method that handles this route. | string |
apiType |
The API type used. Set to false for none, or true for auto. |
false , "json" , "xml" , true |
authType |
The Auth system used. Set to false for none, or true for any. |
false ,true ,string ,array |
The authType
in addition to the pre-defined boolean values, may also be a string or array of strings referring to which plugin(s) which may be supported by the specific implementation in use. We don't define any predetermined types in the specification.
These keywords must be found inside a Route definition itself.
The path
is usually defined as a property name itself, rather than explicitly setting a path
property.
Keyword | Description | Valid values |
---|---|---|
name |
The name used by the Router class to refer to this route. | string |
path |
The path we are matching against, with any placeholders. | string |
http |
The HTTP Method(s) used by this route. | array,string : "GET" , "PUT" , "POST" , "DELETE" , "PATCH" , "HEAD" |
virtual |
This is not a real Route, but child Routes inherit it's properties. | false , true |
noPath |
Only useful on virtual Routes, this Route does not set a path. |
false , true |
contentType |
The content-type. If excluded defaults will be used based on apiType . |
string , see Content Type |
requestSchema |
A reference to a JSON Schema or XML Schema that the PUT, POST, or PATCH data must validate against. | string |
responseSchema |
A reference to a JSON Schema or XML Schema that the returned data must validate against. | string |
pathParams |
A list of supported path parameters (marked by placeholders.) | See Parameters |
queryParams |
A list of supported query parameters. | See Parameters |
headers |
A list of supported headers. | See Parameters |
responseCodes |
A list of possible HTTP response codes. | See Response Codes |
examples |
A list of examples for documentation. | See Examples |
tests |
A set of parameters for automated tests. | See Tests |
defaultRoute |
This route is the default if no others match. Only one Route should ever be default. | false , true |
redirect |
A URL (or route name) this Route redirects to. | string |
redirectRoute |
The redirect is the name of a Route, not a URL. |
false , true |
Note: while most keywords apply traits to the current Route and any child routes, virtual
and noPath
only applies to the current Route itself. Those particular keywords are not passed along in the dependency tree.
If it is not specified, the contentType
assumes the following defaults based on the apiType
:
apiType | contentType |
---|---|
null |
"text/html" |
"json" |
"application/json" |
"xml" |
"application/xml" |
"text" |
"text/plain" |
Each property is an HTTP Response code that may be returned (just the number, i.e. 200
), and should contain the following properties:
Keyword | Description | Valid values |
---|---|---|
description |
The full description of the HTTP code for documentation. | string |
success |
The method call was a success if this code is returned. | false , true |
bodySchema |
A reference to a JSON Schema or XML Schema that the body must validate against if this code is returned. | string |
The bodySchema
is an optional feature that if used will override the responseSchema
and allows for different schemata for different response codes.
Each property in pathParams
, queryParams
, or headers
is the name of a the parameter.
The value must be an object, and supports the following properties:
Keyword | Description | Valid values |
---|---|---|
title |
A short name of the parameter for documentation. | string |
description |
A full description of the parameter for documentation. | string |
type |
A simple hint of what kind of value is supported. | string : "string" , "number" , "integer" |
required |
Is this query parameter mandatory? | false , true |
multiple |
Can this parameter be included more than once? | false , true |
When defining an example or test using multiple parameters, just use an array value.
The multiple
option does not work on pathParams
.
The examples
property is an array of objects, where each object supports the following properties:
Keyword | Description | Valid values |
---|---|---|
title |
A short name of the example. | string |
description |
A full description of the example. | string |
request |
An object describing the request for this example. | See Request Properties |
response |
An object describing the response from the example. | See Response Properties |
The tests
property is an array of objects. The Test
objects are an extension of the Example
objects.
They contain all of the same properties as the above Example Properties
documentation, plus the following additional properties:
Keyword | Description | Valid values |
---|---|---|
validateRequest |
Should the request body be validated before submitting? | false , true |
validateResponse |
Should the response be validated after returning? | true , false , array : "body" , "code" , "type" |
authOptions |
Options specific to the authType being used. |
object |
If validateResponse
is an array, it lists the parts of the response which must be validated.
The response
property is treated differently in a Test
as it would be in an Example
.
If it is specified, it represents the expected response from the test service call.
Keyword | Description | Valid values |
---|---|---|
http |
For Routes that support multiple http methods, force the use of this one in the example. |
string |
body |
A reference to a JSON or XML file containing an example PUT, POST, or PATCH body. | string |
queryParams |
An object where each key is the name of a query parameter, and each value is the parameter value. | object |
pathParams |
An object where each key is the name of a path parameter, and each value is the parameter value. | object |
headers |
An object where each key is the name of a header, and each value is the header value. | object |
authType |
If multiple authType plugins are supported, this forces the use of a specific one for a test. |
string |
apiType |
If multiple apiType formats are supported, this forces the use of a specific one for a test. |
string |
Keyword | Description | Valid values |
---|---|---|
code |
The HTTP status code that should be received for this response. | number |
body |
A reference to a JSON or XML file containing the response body. | string |
type |
The content-type returned in this response (will use defaults based on apiType or contentType .) |
string |
class |
A custom plugin for validation (only useful in tests ) |
string |
The property name should be either the path
(in which case you don't have to set a path inside the definition), or a placeholder value.
If the method is a simple word with no slashes, the path will be assumed to have no URL parameters.
Nested Routes prepend their parent Route's path to their own.
If the path property has a child propery called .controller
set to true
,
the name of the path property is also the name of the controller.
---
/my/app: !virtual
controller: default # Everything in `/my/app`, unless overridden, will
GET: # use the `default` controller.
method: handle_get
POST:
method: handle_post
sub_section: # Everything in `/my/app/sub_section` will use
.controller: true # the `sub_section` controller.
method: handle_foo_bar
...
If the property has the !controller
tag, it will automatically have the
.controller
property set to true
. So the following example does exactly
the same thing as the one above.
/my/app: !virtual
controller: default
# other defs here
sub_section: !controller
method: handle_foo_bar
...
If the path property has a child property called .method
set to true
,
the name of the the path property will be used to determine the name of the
method. By default it will have handle_
prepended to the name, but this
should be configurable (see the Predefined Options section below.)
The !method
tag when used without a string parameter will be like setting
the .method
property to true
.
---
/my/app: !virtual
controller: default
GET: !method # Use Default::handle_get()
POST: !method # Use Default::handle_post()
DELETE: !method # Use Default::handle_delete()
sub_section: !method # Use Foo\Bar::handle_sub_section()
controller: foo.bar
...
If the !method
tag is used with a string parameter (in this case you can have
no further child properties, only defaults) then the parameter will be used as
the method name:
---
/my/app: !virtual
controller: default
GET: !method handle_list_docs
POST: !method handle_create_doc
DELETE: !method handle_delete_doc
...
The !virtual
tag can be used instead of setting a virtual: true
property.
Within a Route (typically one marked as virtual
) any property with the name of an HTTP Method (see http
keyword values above) will be considered a child route specific to that method. This is an easy way to have a single path with multiple actions depending on the HTTP Method used.
If a child route has the name "json"
or "xml"
with no other characters, it will set apiType
to it's own value, and assume the parent path.
Any property starting with a dot will be considered an option. There's not too many pre-defined options, but options can be used for all kinds of implementation-specific purposes.
All of the following options are document-level, which means they can only be set at the very top level of a given document.
Option | Default | Description |
---|---|---|
.includePoly |
false |
If true , the file can be included more than once. |
.methodPrefix |
"handle_" |
A prefix for using with .method and !method . |
.methodSuffix |
"" |
A suffix for using with .method and !method . |
.methodCamelCase |
false |
If true then when generating a method name from a path property, the method will replace _ characters with camelCase naming. So for example a path of some_path would use handleSomePath . |
.controllerPrefix |
"" |
A prefix for using with .controller and !controller . |
.controllerSuffix |
"" |
A suffix for using with .controller and !controller . |
.controllerCamelCase |
false |
The same as .methodCamelCase , but for .controller and !controller . |
A property in a file can include another file by using a YAML processing statement:
title: An example file
common_routes: !include common_routes.yaml
If the filename does not start with a slash, the filename will be considered to be relative to the directory of the calling file. It is valid to use filenames
like subdirectory/something.yaml
or ../includes/common_file.yaml
just
remembering that subsequent include statements in those files will be relative
as well.
If the top-level definition in the included file does not include a virtual
property, it will be set to true
when included.
If the top-level definition in the included file does not include a noPath
property, it will be set to true
when included.
This is because generally the top level definition in a file is a group of routes, not a route in and of itself.
If the included document does not have a .includePoly
property set to true
it can only be included once in a complete RIML data structure.
Note: While YAML supports multiple streams in a single document, the RIML specification does not. How to treat multiple streams is not defined in the specification.
The !includePath
statement works exactly the same as !include
except that it will not set the noPath
property to true
.
If you have a lot of routes that use a very similar structure with just a few differences, traits are handy helpers.
Traits can be included in any section of data, and will add their own properties into that section as long as the property does not exist. This means the order you add traits in is important, as later traits will not be able to override properties from earlier ones.
Traits may contain placeholder values, which if used, will be replaced with real values when the trait is used in a section.
The !define
statement defines either a trait, and any placeholder values it supports.
The properties used to define traits will not be included in the final routing structure (they are metadata only, like Options.)
When defining a trait, it requires the .trait
property, which is the name of the trait.
my_trait: !define
.trait: my_trait
http:
- GET
- POST
apiType: json
responseSchema: src/schemata/api/json/standard_response.json
If your trait contains placeholder values, then you must define a property named .placeholders
which contains a list of placeholder variables and the paths they are found in the trait.
The path delimiter is the pipe | character. This was chosen because / and . are both often used in the actual property names (something not common in programming languages, but essential in a routing information map.)
The paths may be deeply nested, and the last item in the path name may refer to placeholder strings in the template properties, or a property name itself.
my_var_trait: !define
.trait: my_var_trait
.placeholders:
section:
- path|:section
- method|<section>
- .reference|test|name
path: /appname/:section/document.json
method: handle_<section>_document
.reference:
test:
name: ~
The !use
statement applies previously defined traits.
You must have a .traits
property that lists any traits you want to include.
If placeholder values are used by any of the traits, you must also include a .vars
property, which will contain the values to use for each placeholder.
Traits themselves may contain a .vars
property which will be handled differently than regular trait properties, in that the contents of it will be merged with the existing .vars
property, again only adding ones not already defined. This can be used to create traits that define common values for placeholders in other traits.
another_section: !use
.traits:
- my_trait
- my_var_trait
.vars:
section: hello_world
controller: my_controller
The !define
and !use
examples above combined would generate the following structure when compiled:
a_section:
controller: my_controller
http:
- GET
- POST
apiType: json
responseSchema: src/schemata/api/json/standard_response.json
path: /appname/hello_world/document.json
method: handle_hello_world_document
.reference:
test:
name: hello_world
Note that .reference
is an option, and therefore wouldn't be included in the final routing structure.
Here is an example showing off some of the features currently defined. Note we are currently using the :placeholder
style that the Nano.php Router plugin uses rather than the {placeholder}
style, which may or may not be supported depending on the implementation.
title: Foobar
description: Routing for the Foobar controller
controller: foobar
"/:pid/foobar/":
description: View the current Foobar document in the browser
apiType: false
authType: userOnly
method: handle_default
upload_logo: !method
description: Upload a logo file asynchronously
import:
description: Use the Import Foobar service
controller: foobar.import
name: foobar.import
method: handle_default
new: !method
description: Create a new Foobar request
"/:rid":
description: View a Foobar report.
method: handle_view_report
POKE:
description: Set the report as the current Foobar document.
method: handle_set_report
DELETE:
apiType: json
description: Delete a Foobar report.
method: handle_delete_report
"/:pid/foobar.json": !virtual
apiType: json
authType: true
GET:
description: Get the contents of the Foobar document.
method: handle_get_doc
returnSchema: src/schemata/api/json/foobar_get.json
PUT:
description: Replace the document.
method: handle_put_doc
bodySchema: src/schemata/models/foobar.json
returnSchema: src/schemata/api/json/foobar_put.json
tests:
- body: src/schemata/api/examples/json/foobar_put.json
PATCH:
description: Update the document using a patch.
method: handle_patch_doc
returnSchema: src/schemata/api/json/foobar_patch.json
tests:
- body: src/schemata/api/examples/json/foobar_patch.json
NOTE: Some YAML parsers do not like property names with colons and/or slashes in them, so for the sake of the example, they are surrounded with "quotes". The riml.js and Nano.php parsers have no problems without the quotes. Your milage may vary.
The above example would generate the following Path/HTTP method combinations.
Route Name | URL Path | HTTP Method | Controller | Controller Method |
---|---|---|---|---|
foobar |
/:pid/foobar/ |
GET , POST |
foobar |
handle_default |
foobar_upload_logo |
/:pid/foobar/upload_logo |
GET , POST |
foobar |
handle_upload_logo |
foobar.import |
/:pid/foobar/import |
GET , POST |
foobar.import |
handle_default |
foobar.import_new |
/:pid/foobar/import/new |
GET , POST |
foobar.import |
handle_new |
foobar.import_view_report |
/:pid/foobar/import/:rid |
GET , POST |
foobar.import |
handle_view_report |
foobar.import_set_report |
/:pid/foobar/import/:rid |
POKE |
foobar.import |
handle_set_report |
foobar.import_delete_report |
/:pid/foobar/import/:rid |
DELETE |
foobar.import |
handle_delete_report |
foobar_get_docs |
/:pid/foobar.json |
GET |
foobar |
handle_get_doc |
foobar_put_doc |
/:pid/foobar.json |
PUT |
foobar |
handle_put_doc |
foobar_patch_doc |
/:pid/foobar.json |
PATCH |
foobar |
handle_patch_doc |