Day 7: MetaCPAN, Mojolicious and OpenAPI
What is it?
OpenAPI is a specification for designing, documenting, validating and driving your RESTful API. It can be used to provide documentation to an existing API, or when creating a new one.
The OpenAPI Specification originated as the Swagger specification and was renamed to separate the API description format (OpenAPI) from the open source tooling (Swagger). The specification moved into a new GitHub repository, but did not change.
In the case of the MetaCPAN API, we set out to provide documentation to the existing API, but quickly moved into supporting validation to API calls as well.
OpenAPI has many tools available to help, including discovery tools that will assist in writing the specification. We chose to write the definition by hand (in vim of course) and use tools to generate the documentation and to integrate the specification into MetaCPAN.
- ReDoc — OpenAPI/Swagger-generated API Reference Documentation
ReDoc creates an interactive page providing documentation and examples based on the details provided in the OpenAPI specification file. ReDoc includes a HTML template to be served as a static file for customizing how the documentation is displayed.
- Mojolicious::Plugin::OpenAPI — OpenAPI / Swagger plugin for Mojolicious
Reads the OpenAPI specification file and adds the appropriate routes and validations for your Mojolicious based application.
- JSON::Validator — Validate data against a JSON schema
Integrated into the Mojolicious::Plugin::OpenAPI module, provides the input and output validation, as well as providing validation for the specification file itself.
The following strategy was used when implementing the MetaCPAN OpenAPI specification.
The OpenAPI Specification File
With support for multiline attribute values making it much easier to read and write with less formatting, we chose YAML. JSON is also supported.
# Define the version of the OpenAPI spec to use. Version 2.0 still uses # swagger as the key swagger: "2.0" # general information about the API info: version: "1.0.0" title: "MetaCPAN API" # common path shared throughout the API basePath: "/v1"
Defining an Endpoint
Each of the paths available to the API are defined within the paths object.
paths: # The path to the endpoint /search/web: # The HTTP method that the endpoint accepts get: # A unique identifier for the method operationId: search_web # This attribute points to the name of the class in the appliction # and the method to call separated by `#` x-mojo-to: Search#web # A description of the API Endpoint summary: Perform API search in the same fashion as the Web UI
Each method can define its own parameters.
# The parameters that the HTTP method accepts parameters: # The name of the parameter - name: q # The location to parse the parameter from in: query # Document what the parameter is. This example uses the YAML HEREDOC # syntax to make the description easier to read and write. description: | The query search term. If the search term contains a term with the tags `dist:` or `module:` results will be in expanded form, otherwise collapsed form. See also `collapsed` # The type of the value that the API accepts type: string # Define the attribute as required required: true # The rest of the parameters that the API accepts - name: from in: query description: The offset to use in the result set type: integer # If the API applies a default to an attribute if it isn't specified. # Let the us know what it is. default: 0 - name: size in: query description: Number of results per page type: integer default: 20 - name: collapsed in: query description: | Force a collapsed even when searching for a particular distribution or module name. type: boolean
Defining the Response
The OpenAPI specification allows you to define each response to a method call, this includes both specific and generic error handling. Definitions are defined per HTTP status code.
responses: # HTTP 200 response 200: description: Search response # The schema defines what the result will look like schema: type: object properties: total: type: integer took: type: number collapsed: type: boolean results: title: Results type: array items: # While items can be further broken into properties per item, # type `object` is a catch all type: object
Reusing definitions through references
The specification allows for reuse by means of JSON references. The
$ref attribute is a relative pointer to the file and section (again separated by
#) to include at the indicated point.
results: title: Results type: array items: $ref: "../definitions/results.yml#/search_result_items"
The v2.0 specification does have restrictions on where references can be use, which does cause repetition in the specification file. The v3.0 specification has corrected these issues, and also allows for
There are times that a property of an object might be
null. In the MetaCPAN API the favourite count may either be an integer representing how many people have favourited the distribution, or
null. Using a list for the property
type allows the property to contain both.
favorites: type: - "integer" - "null"
The MetaCPAN Specification
The entire specification doesn’t need to be complete in order to get OpenAPI up and running. When documenting an existing API, it’s possible to with one portion of the API. With MetaCPAN we started with the search endpoints.
The OpenAPI Specification repository includes full documentation and many examples of varying levels of details.
The OpenAPI Map is an interactive site to aid in working with the OpenAPI Specification.
Photo by Cytonn Photography on Unsplash
Shawn is a long time Perl developer, with a tendency toward infrastructure, tooling, databases and devops.