1. Introduction
In this, the fourth article in our series on RAML – the RESTful API Modeling Language – we demonstrate how to use annotations to define custom properties for a RAML API specification. This process is also referred to as extending the metadata of the specification.
Annotations may be used to provide hooks for RAML processing tools requiring additional specifications that lie outside the scope of the official language.
2. Declaring Annotation Types
One or more annotation types may be declared using the top-level annotationTypes property.
In the simplest of cases, the annotation type name is all that is needed to specify it, in which case the annotation type value is implicitly defined to be a string:
annotationTypes:
simpleImplicitStringValueType:
This is the equivalent to the more explicit annotation type definition shown here:
annotationTypes:
simpleExplicitStringValueType:
type: string
In other cases, an annotation type specification will contain a value object that is considered to be the annotation type declaration.
In these cases, the annotation type is defined using the same syntax as a data type with the addition of two optional attributes: allowedTargets, whose value is either a string or an array of strings limiting the types of target locations to which an annotation may be applied, and allowMultiple, whose boolean value states whether or not the annotation may be applied more than once within a single target (default is false).
Here is a brief example declaring an annotation type containing additional properties and attributes:
annotationTypes:
complexValueType:
allowMultiple: true
properties:
prop1: integer
prop2: string
prop3: boolean
2.1. Target Locations Supporting the Use of Annotations
Annotations may be applied to (used in) several root-level target locations, including the root level of the API itself, resource types, traits, data types, documentation items, security schemes, libraries, overlays, extensions, and other annotation types.
Annotations may also be applied to security scheme settings, resources, methods, response declarations, request bodies, response bodies, and named examples.
2.2. Restricting an Annotation Type’s Targets
To restrict an annotation type to one or more specific target location types, you would define its allowedTargets attribute.
When restricting an annotation type to a single target location type, you would assign the allowedTargets attribute a string value representing that target location type:
annotationTypes:
supportsOnlyOneTargetLocationType:
allowedTargets: TypeDeclaration
To allow multiple target location types for an annotation type, you would assign the allowedTargets attribute an array of string values representing those target location types:
annotationTypes:
supportsMultipleTargetLocationTypes:
allowedTargets: [ Library, Overlay, Extension ]
If the allowedTargets attribute is not defined on an annotation type, then by default, that annotation type may be applied to any of the supporting target location types.
3. Applying Annotation Types
Once you have defined the annotation types at the root level of your RAML API spec, you would apply them to their intended target locations, providing their property values at each instance. The application of an annotation type within a target location is referred to simply as an annotation on that target location.
3.1. Syntax
In order to apply an annotation type, add the annotation type name enclosed in parentheses () as an attribute of the target location and provide the annotation type value properties that the annotation type is to use for that specific target. If the annotation type is in a RAML library, then you would concatenate the library reference followed by a dot (.) followed by the annotation type name.
3.2. Example
Here is an example showing how we might apply some of the annotation types listed in the above code snippets to various resources and methods of our API:
/foos:
type: myResourceTypes.collection
(simpleImplicitStringValueType): alpha
...
get:
(simpleExplicitStringValueType): beta
...
/{fooId}:
type: myResourceTypes.item
(complexValueType):
prop1: 4
prop2: testing
prop3: true
4. Use Case
One potential use case for annotations would be defining and configuring test cases for an API.
Suppose we wanted to develop a RAML processing tool that can generate a series of tests against our API based on annotations. We could define the following annotation type:
annotationTypes:
testCase:
allowedTargets: [ Method ]
allowMultiple: true
usage: |
Use this annotation to declare a test case.
You may apply this annotation multiple times per location.
properties:
scenario: string
setupScript?: string[]
testScript: string[]
expectedOutput?: string
cleanupScript?: string[]
We could then configure a series of tests cases for our /foos resource by applying annotations as follows:
/foos:
type: myResourceTypes.collection
get:
(testCase):
scenario: No Foos
setupScript: deleteAllFoosIfAny
testScript: getAllFoos
expectedOutput: ""
(testCase):
scenario: One Foo
setupScript: [ deleteAllFoosIfAny, addInputFoos ]
testScript: getAllFoos
expectedOutput: '[ { "id": 999, "name": Joe } ]'
cleanupScript: deleteInputFoos
(testCase):
scenario: Multiple Foos
setupScript: [ deleteAllFoosIfAny, addInputFoos ]
testScript: getAllFoos
expectedOutput: '[ { "id": 998, "name": "Bob" }, { "id": 999, "name": "Joe" } ]'
cleanupScript: deleteInputFoos
5. Conclusion
In this tutorial, we have shown how to extend the metadata for a RAML API specification through the use of custom properties called annotations.
First, we showed how to declare annotation types using the top-level annotationTypes property and enumerated the types of target locations to which they are allowed to be applied.
Next, we demonstrated how to apply annotations in our API and noted how to restrict the types of target locations to which a given annotation can be applied.
Finally, we introduced a potential use case by defining annotation types that could potentially be supported by a test generation tool and showing how one might apply those annotations to an API.
For more information about the use of annotations in RAML, please visit the RAML 1.0 spec.
You can view the full implementation of the API definition used for this tutorial in the github project.