TEI Publisher API

Possible workflow for preparing custom API

Custom API is the preferred way how to extend the functionality of the application generated by TEI Publisher.

Custom API is defined in custom-api.json file within modules collection. It uses OpenAPI Specification.

Design and documentation of the API can be simplified by designing and reusing many components, like parameters, schemas, responses, or examples. These can be declared once and referenced anywhere in the specification using $ref field. Unfortunately, roaster, OpenAPI Router for eXist-db, does not support $ref references from the OpenAPI specification. This is the only one limitation of this module.

If you define path parameter using $ref field, your application throws an error with No definition for required path parameter message. This article is about the solution of keeping concise OpenAPI specification and functional custom API in TEI Publisher.

Example of two reusable components:

  • XmlId value must be of the type string, composed of letters and numbers, minimum length is 4 characters, maximum 80.
  • DictionaryIdParameter is a parameter used in URL path; schema of this parameter is the same as for XmlId. The definition is used in the next example.
{
    "components": { 
        "schemas": {
            "XmlId": {
                "description": "A unique and human readable id",
                "type": "string",
                "pattern": "[a-zA-Z0-9-.]*",
                "minLength": 4,
                "maxLength": 80
            }
        },
        "parameters": {
            "DictionaryIdParameter": {
                "name": "id",
                "in": "path",
                "description": "Identifier for a dictionary. Value of an `@xml:id` attribute of the `<TEI>` element.",
                "required": true,
                "schema": {
                    "$ref": "#/components/schemas/XmlId"
                },
                "example": "FACS"
            }
        }
    }
}

Example of referencing a parameter called id, used within the URL path.

{
    "paths": {
        "/lex/dictionaries/{id}/entries": {
            "get": {
                "summary": "Browse through collection of entries within a dictionary.",
                "operationId": "lapi:dictionary-entries",
                "tags": [
                    "browse",
                    "search"
                ],
                "parameters": [
                    { "$ref": "#/components/parameters/DictionaryIdParameter" }
                ]
            }
        }
    }
}

Prerequisites

  • editor with schema validation or other tools for designing OpenAPI interface, for example, -oXygen XML Editor (see this webinar)
    • pros: one editor for OpenAPI, XQuery, HTML and other stuff needed for TEI Publisher
    • cons: no automated/unit tests for OpenAPI
    • Postman
      • pros: desktop and web application, you can share your API and tests with other people (2 with free plan); testing scenarios and many more tools
      • cons: if you use the free version, you must copy and paste your OpenAPI code to the JSON file manually
  • npm, a JavaScript package manager
    • needed for running Swagger/OpenAPI CLI
  • Swagger/OpenAPI CLI
    • command-line tool for OpenAPI files
    • pros: can replace/expand $ref pointers, can validate OpenAPI file, convert from JSON to YAML and vice versa
    • cons: requires nmp and sometimes doesn’t run (on my laptop)

Updating custom-api.json

  • create the first version of your OpenAPI specification, or edit existing one
  • save updated file on the disk (either outside of the repository of your TEI Publisher’s app or with a different name than custom-api.json), let’s say draft-api.json
  • form command line run (works on Windows) %APPDATA%/npm/swagger-cli bundle -o .\draft-api.json --dereference ./modules/custom-api.json
    • [--dereference] switch is crucial in this case
  • upload custom-api.json on the eXist-db server, for example using existdb-vscode extension for Visual Studio Code

After proceeding with these steps, the above example will look like this:

{
    "components": { 
        "schemas": {
            "XmlId": {
                "description": "A unique and human readable id",
                "type": "string",
                "pattern": "[a-zA-Z0-9-.]*",
                "minLength": 4,
                "maxLength": 80
            }
        },
        "parameters": {
            "DictionaryIdParameter": {
                "name": "id",
                "in": "path",
                "description": "Identifier for a dictionary. Value of an `@xml:id` attribute of the `<TEI>` element.",
                "required": true,
                "schema": {
                    "description": "A unique and human readable id",
                    "type": "string",
                    "pattern": "[a-zA-Z0-9-.]*",
                    "minLength": 4,
                    "maxLength": 80
                },
                "example": "FACS"
            }
        }
    },
    "paths": {
        "/lex/dictionaries/{id}/entries": {
            "get": {
                "summary": "Browse through collection of entries within a dictionary.",
                "operationId": "lapi:dictionary-entries",
                "tags": [
                    "browse",
                    "search"
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "Identifier for a dictionary. Value of an `@xml:id` attribute of the `<TEI>` element.",
                        "required": true,
                        "schema": {
                            "description": "A unique and human readable id",
                            "type": "string",
                            "pattern": "[a-zA-Z0-9-.]*",
                            "minLength": 4,
                            "maxLength": 80
                        },
                        "example": "FACS"
                    }
                ]
            }
        }
    }
}