This document is intended to provide an initial knowledge of how to use RAML 1.0 JavaScript parser.
Install the parser: npm install raml-1-parser
Create new file test.js with the following code:
var raml = require("raml-1-parser");
var apiJSON = raml.load(ramlFileName);
console.log(JSON.stringify(apiJSON, null, 2));
where ramlFileName
is a path to RAML file.
Run the test: node ./test.js
NPM installation is recommended, but taking a quick look at the parser can be done via repository cloning.
Run the following command-line instructions:
git clone https://github.com/raml-org/raml-js-parser-2
cd raml-js-parser-2
npm install typescript // This line is temporary and required only to workaround a bug. To remove.
npm install
Quick tests:
node test/test.js //here you should observe JSON representation of XKCD API in your console
node test/testAsync.js //same as above but in asynchronous mode
If there are no any exceptions, RAML JS Parser is installed successfully
Run command line tool and create a folder where all you test files will be stored.
Run npm init
Run npm install raml-1-parser --save
and wait while all the dependencies are downloaded
and properly initialized.
Run node node_modules/raml-1-parser/test/test01.js
. If there are no any exceptions, RAML JS Parser is installed successfully
These instructions assume that JS Parser was installed via NPM.
Use your favorite text editor to create getting_started.js
JS file in root of the module and add require instruction like that:
// step1
var raml = require("raml-1-parser");
Create test.raml
file in the same folder with the following contents:
#%RAML 1.0
title: Pet shop
version: 1
baseUri: /shop
types:
Pet:
properties:
name: string
kind: string
price: number
example:
name: "Snoopy"
kind: "Mammal"
price: 100
/pets:
get:
responses:
200:
body:
application/json:
type: Pet[]
post:
body:
application/json:
type: Pet
/{id}:
put:
body:
application/json:
type: Pet
delete:
responses:
204:
To do this, create your RAML 1.0 file using Atom editor with installed apiworkbench plugin.
Edit your getting_started.js
file to include RAML file loading:
// step2
var fs = require("fs");
var path = require("path");
// Here we create a file name to be loaded
var fName = path.resolve(__dirname, "test.raml");
// Parse our RAML file with all the dependencies
var api = raml.loadApiSync(fName);
console.log(JSON.stringify(api.toJSON(), null, 2));
Run node getting_started.js
again. If you see the JSON reflecting RAML file AST in the output, then RAML was parsed correctly.
toJSON
method is a useful tool for debugging, but should not be relied upon by JS RAML Parser users to actually analyze and modify RAML AST.
api
variable stores the root of AST. Its children and properties can be traversed and analyzed to find out the structure of RAML file.
Open documentation
folder in the root of the cloned repository and open index.html file in web browser.
loadApiSync
function we just used to parse RAML is described there:
As it is a global function of parser module, we are calling it like that:
raml.loadApiSync(fName)
, and is it returns Api
.
Documentation displays, that loadApiSync
returns Api
object, so click it and see the details:
Lets check resources()
method first:
We see that it returns an array of resources, and each resource contains some methods in turn:
Lets try checking resources and printing them in a simple way. Remove console.log(JSON.stringify(api.toJSON(), null, 2));
line from getting_started.js
code and add the following:
var apiResources = api.resources();
apiResources.forEach(function (resource) {
console.log(resource.kind() + " : " + resource.absoluteUri());
});
The output is:
Resource : /shop/pets
Here, the getKind()
method, which most of AST nodes have, will name what you have at hands, and absoluteUri()
can be found in Resource
documentation.
But why only /shop/pets
is listed, and /shop/pets/{id}
is not? Because AST is hierarchical, so API
only has /shop/pets
as a direct child, and /shop/pets/{id}
is a child of /shop/pets
.
Lets see if we can modify our code to print the whole resource tree:
var raml = require("raml-1-parser");
var fs = require("fs");
var path = require("path");
// Here we create a file name to be loaded
var fName = path.resolve(__dirname, "test.raml");
// Parse our RAML file with all the dependencies
var api = raml.loadApiSync(fName);
/**
* Process resource (here we just trace different paramters of URL)
**/
function processResource(res) {
// User-friendly name (if provided)
if (res.displayName()) {
console.log(res.displayName());
}
// Trace resource's relative URI
var relativeUri = res.relativeUri().value();
// Next method returns full relative URI (which is equal with previous one
// for top-level resources, but for subresources it returns full path from the
// resources base URL)
var completeRelativeUri = res.completeRelativeUri();
// trace both of them
console.log(completeRelativeUri, "(", relativeUri, ")");
// Let's enumerate all URI parameters
for (var uriParamNum = 0; uriParamNum < res.allUriParameters().length; ++uriParamNum) {
var uriParam = res.allUriParameters()[uriParamNum];
// Here we trace URI parameter's name and types
console.log("\tURI Parameter:", uriParam.name(), uriParam.type().join(","));
}
// Recursive call this function for all subresources
for (var subResNum = 0; subResNum < res.resources().length; ++subResNum) {
var subRes = res.resources()[subResNum];
processResource(subRes);
}
}
// Enumerate all the resources
for (var resNum = 0; resNum < api.resources().length; ++resNum) {
processResource(api.resources()[resNum]);
}
The output is following:
/pets ( /pets )
/pets/{id} ( /{id} )
URI Parameter: id string
Here, we use recursion to traverse resources, and for each resource print its completeRelativeUri
, relativeUri
, and allUriParameters
. The description of each method can be found in documentation of Resource
type.
Of course if we do not want to traverse the tree manually, we can find all kinds of useful helpers in documentation, like allResources
method of API
, which traverses the tree for you and finds all resources.
Lets print all the methods in that way, but first lets check methods
documentation:
Replace everything under
var api = raml.loadApiSync(fName);
line with the following:
api.allResources().forEach(function (resource) {
console.log(resource.absoluteUri())
resource.methods().forEach(function(method){
console.log("\t"+method.method())
})
})
The output:
/shop/pets
get
post
/shop/pets/{id}
put
delete
method
method of Method
class prints HTTP type of the method.
We can also see the responses
method in Method
documentation:
Lets print the responses too:
api.allResources().forEach(function (resource) {
console.log(resource.absoluteUri())
resource.methods().forEach(function(method){
console.log("\t"+method.method())
method.responses().forEach(function (response) {
console.log("\t\t" + response.code().value())
})
})
})
The output is:
/shop/pets
get
200
post
/shop/pets/{id}
put
delete
204
To be able to print that, we first used code()
method of Response
:
And then found value()
method in StatusCodeString
description:
All in all, the AST tree reflects RAML structure, so starting from loadApiSync
global method, then checking documentation for its return value, proceeding to its methods and doing that recursively allows reaching everything.
This section describes how to analyze resource types. Traits are analyzed in the same way.
Lets change our RAML file to look like this:
#%RAML 1.0
title: Pet shop
version: 1
baseUri: /shop
types:
Pet:
properties:
name: string
kind: string
price: number
example:
name: "Snoopy"
kind: "Mammal"
price: 100
resourceTypes:
Collection:
get:
responses:
200:
body:
application/json:
type: Pet[]
post:
body:
application/json:
type: Pet
Member:
put:
body:
application/json:
type: Pet
delete:
responses:
204:
/pets:
type: Collection
/{id}:
type: Member
It differs from the previous sample by method declarations being moved to resource types, the real resources and methods structure remains the same.
Now change getting_started.js
contents:
var raml = require("raml-1-parser");
var fs = require("fs");
var path = require("path");
// Here we create a file name to be loaded
var fName = path.resolve(__dirname, "test.raml");
// Parse our RAML file with all the dependencies
var api = raml.loadApiSync(fName).expand();
api.allResources().forEach(function (resource) {
console.log(resource.absoluteUri())
resource.methods().forEach(function(method){
console.log("\t"+method.method())
method.responses().forEach(function (response) {
console.log("\t\t" + response.code().value())
})
})
})
And launch it.
Results are:
/shop/pets
get
200
post
/shop/pets/{id}
put
delete
204
So when RAML JS Parser is asked to expand traits and types, you get them applied automatically and have the final structure at hands.
If you change the API loading call to
var api = raml.loadApiSync(fName);
the output will look like:
/shop/pets
/shop/pets/{id}
So without resource type expansion the methods and other contents from resource types are not added to the point of the AST, they are applied to.
We can see the resource type applications itself:
var resourceTypeValue = api.allResources()[0].type().value().valueName()
console.log(resourceTypeValue)
Here we take the /pets
resource, getting its type and printing the value of the type.
The output is:
Collection
This can be done with both expanded and unexpanded AST.
Why did we have to call valueName()
method after calling value()
, why not just make value()
to return the type name?
The answer is: the resource type appliance may also have parameters.
Right now it is hard to obtain the parameters at the top level of AST, but going one level down to high-level AST allows that. This should be exposed at top level AST in the nearest future. The current code below will continue to work, but there will be more suitable helpers available at the top level regarding this matter.
TODO: rewrite this part when structured values are wrapped at top level of AST.
Change the RAML file like following:
#%RAML 1.0
title: Pet shop
version: 1
baseUri: /shop
types:
Pet:
properties:
name: string
kind: string
price: number
example:
name: "Snoopy"
kind: "Mammal"
price: 100
resourceTypes:
Collection:
get:
responses:
200:
body:
application/json:
type: <<item>>[]
post:
body:
application/json:
type: <<item>>
Member:
put:
body:
application/json:
type: <<item>>
delete:
responses:
204:
/pets:
type: { Collection: {item: Pet} }
/{id}:
type: { Member: {item : Pet} }
In this example we made both resource types to have a type parameter.
In case of /pets
resource, which refers Collection
resource type, the item
parameter value is Pet
.
Change getting_started.js
like following:
var raml = require("raml-1-parser");
var fs = require("fs");
var path = require("path");
// Here we create a file name to be loaded
var fName = path.resolve(__dirname, "test.raml");
// Parse our RAML file with all the dependencies
var api = raml.loadApiSync(fName);
var resourceTypeReference = api.allResources()[0].type().value()
var highLevelASTNode = resourceTypeReference.toHighlevel()
highLevelASTNode.attrs().forEach(function (attribute) {
console.log(attribute.name() + " = " + attribute.value())
})
Here we call toHighlevel()
method, which is available for every top-level AST node and get the high-level node, then print its attributes. High-level nodes do not have have its data wrapped to user-friendly helper methods, instead they have attributes and children.
The output is the following:
key = Collection
item = Pet
So the item
parameter value Pet
is available.
As soon as navigation module is published, it will be possible to directly jump from this resource type appliance to Collection
resource type declaration as we do in API Workbench.
For now, lets see how resource type declarations can be access directly from API:
Modify getting_started.js
file contents in the following way:
var raml = require("raml-1-parser");
var fs = require("fs");
var path = require("path");
// Here we create a file name to be loaded
var fName = path.resolve(__dirname, "test.raml");
// Parse our RAML file with all the dependencies
var api = raml.loadApiSync(fName);
api.resourceTypes().forEach(function (resourceType) {
console.log(resourceType.name())
})
Here we ask API for resource type declarations and print type names.
The output:
Collection
Member
And now we will print methods and responses for each resource type the same way we did previously for API:
api.resourceTypes().forEach(function (resourceType) {
console.log(resourceType.name())
resourceType.methods().forEach(function(method){
console.log("\t"+method.method())
method.responses().forEach(function (response) {
console.log("\t\t" + response.code().value())
})
})
})
The output:
Collection
get
200
post
Member
put
delete
204
Consider the following RAML specification:
#%RAML 1.0
title: Pet shop
version: 1
baseUri: /shop
types:
Metrics:
properties:
height: number
width: number
length: number
weight: number
Pet:
properties:
name: string
kind: string
price: number
metrics?: Metrics
color:
enum:
- Black
- White
- Colored
example:
name: "Snoopy"
kind: "Mammal"
price: 100
color: Black
Mammal:
type: Pet
Bird:
type: Pet
properties:
wingLength: number
PetCollection: Pet[]
RandomPet: Mammal|Bird
In order to extract AST nodes, which describe types, we can use Api.types()
method, returning an array of TypeDeclaration
:
TypeDeclaration
is the root of the hierarchy for AST type declarations:
In the hierarchy, ObjectTypeDeclaration
is basically RAML object type, StringTypeDeclaration
is a string, NumberTypeDeclaration
is a number, UnionTypeDeclaration
represents a union type etc.
Now we can print all the type names names using the TypeDeclaration.name
method:
api.types().forEach(function (type) {
console.log(type.name());
});
Output is:
Metrics
Pet
Mammal
Bird
PetCollection
RandomPet
Lets now print types of AST nodes representing the types:
api.types().forEach(function (type) {
console.log(type.name() + " : " + type.kind());
});
Output:
Metrics : ObjectTypeDeclaration
Pet : ObjectTypeDeclaration
Mammal : ObjectTypeDeclaration
Bird : ObjectTypeDeclaration
PetCollection : ArrayTypeDeclaration
RandomPet : UnionTypeDeclaration
Apart from AST, the parser provides one more way of representing types: nominal type system. Nominal type system has several advantages in comparison with AST: it allows to
- explore type hierarchy in a natural way
- obtain consistent object representations for component types of arrays and union types.
Having a TypeDeclaration
instance in hands one can obtain a nominal type by means of TypeDeclaration.localType()
method returning ITypeDefinition
:
ITypeDefinition
has several subtypes:
IAnnotationType
is used to represent annotation typesIArrayType
is used to represent array typesIExternalType
is used to represent types defined by XML and JSON schemasIUnionType
is used to represent union typesIValueTypeDefinition
is used to represent scalar types
ITypeDefinition
has a set of methods which allow to check type is annotation, array type, etc:
isAnnotationType
isArray
isExternal
isObject
isUnion
isValueType
Lets print nominal type names for our types:
api.types().forEach(function(type){
var typeName = type.localType().nameId();
console.log(typeName);
});
output:
Metrics
Pet
Mammal
Bird
PetCollection
RandomPet
The TypeDeclaration.type()
method provides information about extended types. It returns an array os strings which are either names of supertypes or expressions which defines supertypes.
api.types().forEach(function (type) {
console.log(type.name() + " : " + type.kind());
console.log("\t type:", type.type())
});
Output:
Metrics : ObjectTypeDeclaration
type: [ 'object' ]
Pet : ObjectTypeDeclaration
type: [ 'object' ]
Mammal : ObjectTypeDeclaration
type: [ 'Pet' ]
Bird : ObjectTypeDeclaration
type: [ 'Pet' ]
PetCollection : ArrayTypeDeclaration
type: [ 'Pet[]' ]
PetCollection2 : ArrayTypeDeclaration
type: [ 'array' ]
RandomPet : UnionTypeDeclaration
type: [ 'Mammal|Bird' ]
One need a special type registry in order to explore type hierarchy using AST as it provides only names of supertypes or expressions which define them.
In contrast to the AST, the nominal type system allows to explore hierarchy without involving auxiliary services. In order to obtain all direct supertypes of the type you should call the ITypeDefinition.superTypes()
method. It returns an array of ITypeDefinition
instances which can be treated just the same way as the one the method has been called for.
Let's list complete hierarchy for each of our types:
function printHierarchy(nominalType,indent){
indent = indent || "";
var typeName = nominalType.nameId();
console.log(indent + typeName);
nominalType.superTypes().forEach(function(st){
printHierarchy(st, indent + " ");
});
}
api.types().forEach(function(type){
printHierarchy(type.localType());
console.log("---");
});
output:
Metrics
object
any
---
Pet
object
any
---
Mammal
Pet
object
any
---
Bird
Pet
object
any
---
PetCollection
array
any
---
RandomPet
union
any
---
In case you need a complete set of supertypes including direct and indirect ones, you should call the ITypeDefinition.allSuperTypes()
method.
The ITypeDefinition.isAssignableFrom()
method is used to check if one type inherits another or coincides with it. Lets check each of our type whether it inherits or coincides with Pet
type:
console.log("Is the type assignable from \"Pet\" type?")
api.types().filter(function(type){
console.log(type.name(),":",type.localType().isAssignableFrom("Pet"));
});
output:
Is the type assignable from "Pet" type?
Metrics : false
Pet : true
Mammal : true
Bird : true
PetCollection : false
RandomPet : false
In order to obtain direct subtypes of the type, you should call the ITypeDefinition.subTypes()
method which returns an array of ITypeDefinition
instances. Lets list all the direct subtypes of the Pet
type:
api.types().filter(function(type){
return type.name()=="Pet"
}).forEach(function(type){
console.log("Subtypes of", type.name(), ":");
type.localType().subTypes().forEach(function(st){
console.log(st.nameId());
});
});
output:
Subtypes of Pet :
Mammal
Bird
In case you need a complete set of subtypes including direct and indirect ones, you should call the ITypeDefinition.allSubTypes()
method.
Properties of ObjectTypeDeclaration
can be obtained by means of properties
method, which returns an array of TypeDeclaration
:
So we can print property names and their own types for each type:
api.types().forEach(function (type) {
console.log(type.name() + " : " + type.kind());
if(type.kind()=="ObjectTypeDeclaration"){
type.properties().forEach(function(prop) {
console.log("\t", prop.name() + " : " + prop.kind());
});
}
});
Output:
Metrics : ObjectTypeDeclaration
height : NumberTypeDeclaration
width : NumberTypeDeclaration
length : NumberTypeDeclaration
weight : NumberTypeDeclaration
Pet : ObjectTypeDeclaration
name : StringTypeDeclaration
kind : StringTypeDeclaration
price : NumberTypeDeclaration
metrics : ObjectTypeDeclaration
color : StringTypeDeclaration
Mammal : ObjectTypeDeclaration
Bird : ObjectTypeDeclaration
wingLength : NumberTypeDeclaration
PetCollection : ArrayTypeDeclaration
RandomPet : UnionTypeDeclaration
The color
property has enum facet defined. We can detect and print that:
api.types().forEach(function (type) {
console.log(type.name() + " : " + type.kind());
if(type.kind()=="ObjectTypeDeclaration"){
type.properties().forEach(function(prop) {
console.log("\t", prop.name() + " : " + prop.kind());
if (prop.kind()=="StringTypeDeclaration" && prop.enum()) {
prop.enum().forEach(function(enumValue){
console.log("\t\t-", enumValue);
})
}
});
}
});
Output:
Metrics : ObjectTypeDeclaration
height : NumberTypeDeclaration
width : NumberTypeDeclaration
length : NumberTypeDeclaration
weight : NumberTypeDeclaration
Pet : ObjectTypeDeclaration
name : StringTypeDeclaration
kind : StringTypeDeclaration
price : NumberTypeDeclaration
metrics : ObjectTypeDeclaration
color : StringTypeDeclaration
- White
- Black
- Colored
Mammal : ObjectTypeDeclaration
Bird : ObjectTypeDeclaration
wingLength : NumberTypeDeclaration
PetCollection : ArrayTypeDeclaration
RandomPet : UnionTypeDeclaration
As AST does not allow to directly retrieve full information about object property types, it's one more cases of nominal type system being useful.
Let print hierarchy and properties for nominal type of the Pet.metrics
property:
function printHierarchyAndProperties(nominalType,indent){
indent = indent || "";
var typeName = nominalType.nameId();
console.log(indent + "type: " + typeName + " (" + nominalType.kind() + ")");
if(nominalType.isAssignableFrom("object") && nominalType.properties().length>0) {
console.log(indent + " properties:");
nominalType.properties().forEach(function (prop) {
console.log(indent + " " + prop.nameId() + ": " + prop.range().nameId());
});
}
if(nominalType.superTypes().length>0) {
console.log(indent + " supertypes:");
nominalType.superTypes().forEach(function (st) {
printHierarchyAndProperties(st, indent + " ");
});
}
}
api.types().filter(function(type){return type.name()=="Pet"})
.forEach(function (type) {
type.properties().filter(function(prop){return prop.name()=="metrics"})
.forEach(function(prop) {
var localType = prop.localType();
printHierarchyAndProperties(localType);
});
});
output:
type: metrics (object)
supertypes:
type: Metrics (object)
properties:
height: NumberType
width: NumberType
length: NumberType
weight: NumberType
supertypes:
type: object (object)
supertypes:
type: any (object)
The nominal type has the same name as the property and has property type as supertype. The picture looks more natural, if we switch to nominal type system directly from AST node of the type itself:
api.types().filter(function(type){return type.name()=="Pet"})
.forEach(function (type) {
var localType = type.localType();
var nominalProp = localType.property("metrics");
var propertyRange = nominalProp.range();
console.log(nominalProp.domain().nameId()+"."+nominalProp.nameId()+" range details:")
printHierarchyAndProperties(propertyRange);
});
output:
Pet.metrics range details:
type: Metrics (object)
properties:
height: NumberType
width: NumberType
length: NumberType
weight: NumberType
supertypes:
type: object (object)
supertypes:
type: any (object)
Thus, the ITypeDefinition.properties()
returns a set of properties declared by the type itself, represented as an array of IProperty
. In order to obtain a set of properties declared by a type and all its supertypes, you should use the ITypeDefinition.allProperties()
method.
IProperty.nameId()
method returns name of the property, IProperty.range()
returns property value type represented as ITypeDefinition
instance, and IProperty.domain()
returns type declaring the property represented as ITypeDefinition
instance.
Component type of ArrayTypeDeclaration
can be obtained by means of the items
method:
Let's print component type of the PetCollection array type:
api.types().forEach(function (type) {
if(type.kind() == 'ArrayTypeDeclaration') {
console.log(type.name() + " : " + type.kind());
console.log("\t", "items : ", type.items().kind());
}
});
output:
PetCollection : ArrayTypeDeclaration
items : ObjectTypeDeclaration
As in case with property types, AST does not allow to directly inspect component type details. Thus, we have to switch to nominal type system.
Nominal type of array types is represented as IArrayType
instance.
The code below prints details for array type component types:
api.types().filter(function(type){return type.kind()=="ArrayTypeDeclaration"})
.forEach(function (type) {
var localType = type.items().localType();
console.log(type.name() + " component details:");
printHierarchyAndProperties(localType);
});
output:
PetCollection component details:
type: Pet (object)
properties:
name: StringType
kind: StringType
price: NumberType
metrics: Metrics
color: null
supertypes:
type: object (object)
supertypes:
type: any (object)
The reason of the color
property type having null
type name is that its type is anonymous.
Once again, the same result can be achieved by switching to nominal type system right from the types AST node. The method for retrieving
nominal array type component is IArrayType.componentType()
.
api.types().filter(function(type){return type.kind()=="ArrayTypeDeclaration"})
.forEach(function (type) {
var localType = type.localType().componentType();
console.log(type.name() + " component details:");
printHierarchyAndProperties(localType);
});
output:
PetCollection component details:
type: Pet (object)
properties:
name: StringType
kind: StringType
price: NumberType
metrics: Metrics
color: null
supertypes:
type: object (object)
supertypes:
type: any (object)
Nominal type of union types is represented as IUnionType
instance. The IUnionType.leftType()
and IUnionType.rightType()
methods allow inspection
union type components:
api.types().filter(function(type){return type.localType().isUnion()})
.forEach(function (type) {
var localType = type.localType();
console.log(localType.nameId() + " components:");
console.log("left:");
//See "Properties of Object Types" section for "printHierarchyAndProperties" definition
printHierarchyAndProperties(localType.leftType(), " ");
console.log("right:");
printHierarchyAndProperties(localType.rightType(), " ");
});
output:
RandomPet components:
left:
type: Mammal (object)
supertypes:
type: Pet (object)
properties:
name: StringType
kind: StringType
price: NumberType
metrics: Metrics
color: null
supertypes:
type: object (object)
supertypes:
type: any (object)
right:
type: Bird (object)
properties:
wingLength: NumberType
supertypes:
type: Pet (object)
properties:
name: StringType
kind: StringType
price: NumberType
metrics: Metrics
color: null
supertypes:
type: object (object)
supertypes:
type: any (object)
Consider the following method:
/pets:
post:
body:
application/json:
type: Pet
properties:
tailLength: number
Method body can be obtained as follows:
api.resources().filter(function(resource){return resource.relativeUri().value()=="/pets"})
.forEach(function(resource){
resource.childMethod("post").forEach(function(method) {
var bodyTypes = method.body();
bodyTypes.forEach(function (body) {
console.log("name:", body.name());
console.log("type:", body.type());
console.log("properties:");
body.properties().forEach(function (prop) {
console.log(" ", prop.name(), ":", prop.type());
});
});
});
});
output:
name: application/json
type: [ 'Pet' ]
properties:
tailLength : [ 'number' ]
Thus, body type has body media type as name and inherits type specified in type
property. Lets see it again with the help of nominal type system:
api.resources().filter(function(resource){return resource.relativeUri().value()=="/pets"})
.forEach(function(resource){
resource.childMethod("post").forEach(function(method) {
var bodyTypes = method.body();
bodyTypes.forEach(function (body) {
//See "Properties of Object Types" section for "printHierarchyAndProperties" definition
printHierarchyAndProperties(body.nominalType());
});
});
});
output:
type: application/json (object)
properties:
tailLength: NumberType
supertypes:
type: Pet (object)
properties:
name: StringType
kind: StringType
price: NumberType
metrics: Metrics
color: null
supertypes:
type: object (object)
supertypes:
type: any (object)
Facets can be interpreted as static properties of type. If a type defines a facet, each of its direct subtypes must fix the facet, i.e. provide a value for it. Facets are defined just the same way as RAML types. Example of string facet definition and fixing can be seen in the following RAML specification:
#%RAML 1.0
title: Facets
version: 1
baseUri: /facets
types:
User:
properties:
id: number
name:
facets:
#definition of string facet "role"
role:
Manager:
type: User
#fixing the "role" facet
role: manager
properties:
team: User[]
Admin:
type: User
#fixing the "role" facet
role: admin
properties:
phoneNumber: string
Facets defined by the type can be obtained by TypeDeclaration.facets()
method returning an array of TypeDeclaration
instances, and a set of fixed facet values can be obtained by TypeDeclaration.fixedFacets()
method returning a TypeInstance
. This TypeInstance
can be called the toJSON()
method in order to be turned to object which map facet names to their values.
api.types().forEach(function(type){
console.log(type.name(), ":");
if(type.facets().length>0){
console.log("defined facets:");
type.facets().forEach(function(t){
console.log(t.name(),":",t.type());
});
}
if(type.fixedFacets()){
console.log("fixeded facets:",
JSON.stringify(type.fixedFacets().toJSON(),null,2));
}
console.log();
});
output:
User :
defined facets:
role : [ 'string' ]
Manager :
fixeded facets: {
"role": "manager"
}
Admin :
fixeded facets: {
"role": "admin"
}
The same result can be achieved by means of nominal type system.
A set of facets declared by the type itself can be retrieved by the ITypeDefinition.facets()
returning an array of IProperty
.
If you need a set of facets declared by type and all of its supertypes, you should use the ITypeDefinition.allFacets()
method.
A set of type fixed facet values can be retrieved by the ITypeDefinition.getFixedFacets()
method returning actual values used to fix facets.
api.types().forEach(function(type){
var nominalType = type.localType();
console.log(nominalType.nameId(), ":");
if(nominalType.facets().length>0){
console.log("defined facets:");
nominalType.facets().forEach(function(f){
console.log(f.nameId(),":",f.range().nameId());
});
}
if(Object.keys(nominalType.getFixedFacets()).length>0){
console.log("fixeded facets:",
JSON.stringify(nominalType.getFixedFacets(),null,2));
}
console.log();
});
output:
User :
defined facets:
role : StringType
Manager :
fixeded facets: {
"role": "manager"
}
Admin :
fixeded facets: {
"role": "admin"
}
All the above sets of facets contain user defined facets, but not the built in facets.
In order to extract AST nodes, which describe annotaton types, we can use Api.annotationTypes()
method, returning an array of AnnotationTypeDeclaration
:
AnnotationTypeDeclaration
is the root of the hierarchy for AST type declarations:
Each AnnotationTypeDeclaration
subtype inherits corresponding TypeDeclaration
subtype. For example, ObjectAnnotationTypeDeclaration
inherits ObjectTypeDeclaration
, ArrayAnnotationTypeDeclaration
inherits ArrayTypeDeclaration
etc. Thus annotation type AST nodes can be treated just the same way as AST nodes of types.
Consider the following RAML specification:
#%RAML 1.0
title: Annotations
version: v1
baseUri: /annotations
annotationTypes:
MyStringAnnotation:
enum: [ value1, value2, value3 ]
MyObjectAnnotation:
properties:
property1: string
property2: boolean
Lets print some details about each of our annotation types:
api.annotationTypes().filter(function(aType){
return aType.name()=="MyStringAnnotation"
}).forEach(function(aType){
console.log("annotation type");
console.log(" name:",aType.name());
console.log(" type:",aType.type());
console.log(" enum:",aType.enum());//enum() method is inherited from StringTypeDeclaration
});
api.annotationTypes().filter(function(aType){
return aType.name()=="MyObjectAnnotation"
}).forEach(function(aType){
console.log("annotation type");
console.log(" name:",aType.name());
console.log(" type:",aType.type());
console.log(" properties:");
aType.properties().forEach(function(prop){//properties() method is inherited from ObjectTypeDeclaration
console.log(" ",prop.name(),":",prop.type());
});
});
output:
annotation type
name: MyStringAnnotation
type: [ 'string' ]
enum: [ 'value1', 'value2', 'value3' ]
annotation type
name: MyObjectAnnotation
type: [ 'object' ]
properties:
property1 : [ 'string' ]
property2 : [ 'string' ]
The same operation can be performed by means of nominal type system:
api.annotationTypes().forEach(function(aType){
//see "Supertypes and Subtypes" section of the "Types" chapter
//for "printHierarchyAndProperties" definition
printHierarchyAndProperties(aType.localType());
console.log();
});
output:
type: MyStringAnnotation (annotation)
supertypes:
type: MyStringAnnotation (value)
supertypes:
type: StringType (value)
supertypes:
type: ValueType (value)
type: scalar (value)
supertypes:
type: any (object)
type: MyObjectAnnotation (annotation)
supertypes:
type: MyObjectAnnotation (object)
properties:
property1: StringType
property2: StringType
supertypes:
type: object (object)
supertypes:
type: any (object)
Each annotatable RAML element inherits RAMLLanguageElement
, and it provides its annotations as RAMLLanguageElement.annotations()
method value. The method returns an array of annotation references represented as AnnotationRef
instances.
The AnnotationRef.annotations()
method is used to retrieve AST node of the referenced annotation. The AnnotationRef.structuredValue()
method is used to obtain reference value represented as TypeInstance
which can be called toJSON()
method in order to obtain actual value.
Consider the following resource:
/resource:
(MyStringAnnotation): value2
(MyObjectAnnotation):
property1: property 1 value
property2: property 2 value
Lets iterate through these annotation references and print their referenced annotations and values:
api.childResource("/resource").annotations().forEach(function(aRef){
console.log("referenced annotation:");
//see "Supertypes and Subtypes" section of the "Types" chapter
//for "printHierarchyAndProperties" definition
printHierarchyAndProperties(aRef.annotation().localType());
console.log("value:", JSON.stringify(aRef.structuredValue().toJSON(),null,2));
console.log();
});
output:
referenced annotation:
type: MyStringAnnotation (annotation)
supertypes:
type: MyStringAnnotation (value)
supertypes:
type: StringType (value)
supertypes:
type: ValueType (value)
type: scalar (value)
supertypes:
type: any (object)
value: "value2"
referenced annotation:
type: MyObjectAnnotation (annotation)
supertypes:
type: MyObjectAnnotation (object)
properties:
property1: StringType
property2: StringType
supertypes:
type: object (object)
supertypes:
type: any (object)
value: {
"property1": "property 1 value",
"property2": "property 2 value"
}