Coding standards
Security in ResourceSpace
Developer reference
Database
Action functions
Admin functions
Ajax functions
Annotation functions
API functions
Collections functions
Comment functions
Config functions
CSV export functions
Dash functions
Debug functions
Encryption functions
Facial recognition functions
File functions
General functions
Language functions
Log functions
Login functions
Message functions
Migration functions
Node functions
PDF functions
Plugin functions
Render functions
Reporting functions
Request functions
Research functions
Slideshow functions
Theme permission functions
User functions
Video functions
Database functions
Metadata functions
Resource functions
Search functions
Map functions
Job functions
Tab functions
Test functions

OpenAPI documentation

From version 11, to make our API easier to understand, integrate with and maintain, we use the OpenAPI specification (OAS). This article shows you how to document ResourceSpace API using the OpenAPI spec, validate and test it.

Learning about OpenAPI

Versioning

The OpenAPI info: version will use the "major.minor" notation (e.g. 1.0) where:

  • the major part will remain set to 1 because our API doesn't support versioning <=> we can't break backwards-compatibility (BC) to existing bindings;
  • the minor part will increment every time we add a new binding or change an existing one (BC safe);

Please note the version is required by the spec.

Our OpenAPI specification is available at /api/openapi.php which always generates the most up-to-date version of ResourceSpace API spec.

Implementation overview

Given ResourceSpace API is leaning more towards a custom JSON-RPC, our OAS approach is to have one API endpoint (/api/index.php) which accepts both GET and POST requests.

In order to document what each request parameters should be for each function, we're using OAS polymorphism and using a discriminator on the function parameter.

Each API binding function is defined as static function under the ResourceSpaceApiBindings.

The schema is documented using zircote/swagger-php so it's highly recommended you refer to its documentation if unsure how to do something.

Example

#[OAT\Schema(
    schema: 'get_collection',
    allOf: [
        new OAT\Schema(ref: BindingFunctionDiscriminator::class),
        new OAT\Schema(ref: '#/components/schemas/Ref', description: 'Collection ID'),
    ],
)]
#[OAT\Schema(
    schema: 'GetCollectionResponse',
    type: 'object',
    allOf: [
        new OAT\Schema(ref: Collection::class),
        new OAT\Schema(ref: WithUsernameAndFullname::class),
    ],
    properties: [
        new OAT\Property(property: 'users', type: 'string'),
        new OAT\Property(property: 'groups', type: 'string'),
        new OAT\Property(property: 'request_feedback', type: 'integer'),
    ],
)]
public static function get_collection()
{
}

Note: when documenting both the request and response objects like seen above, make sure that their properties are defined within each object and not using the attribute on the method (e.g. `#[OAT\Property(property: 'name', type: 'string')]`) as that will mean both the request and response will have a name property (which in most cases you meant only for one of them).

Once you've defined the request and response (if not covered by the general responses already defined) you need to mention it for the appropriate request method. In our example that'd be a GET:

#[OAT\Parameter(
        name: 'rpc',
        in: 'query',
        required: true,
        style: 'form',
        explode: true,
        schema: new OAT\Schema(
            discriminator: new OAT\Discriminator(propertyName: 'function'),
            oneOf: [
                # [...] beside the others defined here
                new OAT\Schema(ref: '#/components/schemas/get_collection'), <------ HERE
            ],
        ),
    )]

You have to reference the GetCollectionResponse too, in a similar fashion under the appropriate response:

#[OAT\Response(
        response: 200,
        description: 'Response (varies by function). Note: fails may also return a 200 status.',
        content: new OAT\JsonContent(
            oneOf: [
                # [...] beside the others defined here
                new OAT\Schema(ref: '#/components/schemas/GetCollectionResponse'), <------ HERE
            ]
        )
    )]

The GetCollectionResponse is a good example of using model composition. You can see how this objects' properties is made up of all of the Collection and WithUsernameAndFullname (which is an object that only adds a username and fullname as that was common) and extended with three properties needed only by this response object.

Common parameters you can use to create a request/response object (e.g. ref: '#/components/schemas/ResourceParam'):

  • Ref - provides the "ref" property as an int;
  • ResourceParam - provides the "resource" property as an int;
  • RefsListParamValue - provides the "refs" property as a list (array) of numbers;
  • CollectionParam - provides the "collection" property as an int;
  • BitParamValue - provides the type of a property as an int that only takes 0 and 1;
  • JsonEncodedParamValue - provides the type of a property as a string that is essentially a JSON;
  • CsvParamValue - provides the type of a property as a string that is essentially a CSV (usually of numbers);