Adding API Routes

From Dreamwidth Notes
Jump to: navigation, search

(Note, this is about the new OpenAPI, not the old XML-RPC API)

The Definition File

This is a .yml file that goes in $LJHOME/api/, with a name and (optionally) sub-folder path that reflects the path it contains. Each file contains one OpenAPI 3.0-compliant Path Item Object. The official spec is available here, or you can look at existing definition files to get a sense of how they're laid out. Note the following caveats/gotchas:

  • paths with different path parameters are distinct from each other, even if it's a trailing path parameter (so /users/{username}/icons and /users/{username}/icons/{picid} are different paths)
  • only one path per file! Otherwise the router won't know which path to register a handler for.
  • There is no support right now for $refs/external definitions, because the paths are split into multiple files (and it's unlikely there will ever be support)
  • Less common items/syntax may not be supported properly right now (though any esoteric JSON schema items should still validate properly)


The Handlers File

This is a .pm file that goes in $LJHOME/cgi-bin/Controller/API/REST/, again with a sensible name and subfolder. Here's an example of an extremely simple path, which just returns the character codes for comment screening settings and their meanings:

#!/usr/bin/perl
#
# DW::Controller::API::REST::Comments
#
# API controls for the comment system
#
# Authors:
#      Ruth Hatch <ruth.s.hatch@gmail.com>
#
# Copyright (c) 2017 by Dreamwidth Studios, LLC.
#
# This program is free software; you may redistribute it and/or modify it under
# the same terms as Perl itself. For a copy of the license, please reference
# 'perldoc perlartistic' or 'perldoc perlgpl'.
#
 
package DW::Controller::API::Comments;
use DW::Controller::API::REST;
 
use strict;
use warnings;
use JSON;
 
 
################################################
# /comments/screening
#
# Get a list of possible comment screening options.
################################################
# Define route and associated params
my $screening = DW::Controller::API::REST->path('comments/screening.yaml', 1, {'get' => \&get_screening});
 
 
sub get_screening {
    my $self = $_[0];
 
    my $settings = {""   =>   "Journal Default",
                	"N"  =>   "No comments are screened.",
                	"R"  =>   "Screen anonymous comments",
                	"F"  =>   "Screen comments from journals without access granted.",
                	"A"  =>   "All comments are screened."};
 
    return $self->rest_ok( $settings );
}
 
1;

The big important line is DW::Controller::API::REST->path('comments/screening.yaml', 1, {'get' => \&get_screening}); which is in the format DW::Controller::API::REST->path(definition file path, version number, hashref of method handlers) or in more human-readable terms, 'create a new path object with the definition found in this file, for this API version, and it supports the following HTTP protocol methods, using these subroutines'. The version number is so that we can support more than one API version at once, to gracefully phase old code out - at the moment, all API handlers should be version 1.

The API routing system will handle validating your definition file, registering your path in the global routing table, adding the definition to our API spec, and validating input and output to that spec. The handler methods you write will recieve two items when they're called - first is a Method object, which contains the fuctions rest_error and rest_ok to return responses to the user. rest_ok takes a repsonse, and optionally a content-type and status code - the default content-type is 'application/json' and the default status code is '200' if none are provided. rest_error takes a status code and an optional message. If no message is provided, the handler will look for the description in the definition file and use that, and if that's not provided, it will use a generic error instead.

The second item the handler will recieve is a hashref which contains a reference to the user making the call, and all the user-provided parameters, indexed first by location, and then within that, by name (this is because having parameters with the same name but different locations is valid under OpenAPI 3.0). All methods other than the API spec path aren't available without authentication, and thus are guaranteed a user. The dispatcher does not, however, check that the user has the authority to do whatever they're trying to do - that's up to your handler to verify. Likewise, the dispatcher will check that required parameters are provided, and that parameters are in the correct format, but does not check for the safety or meaningfulness of contents - the handler must do this.

Unless it's necessary for some reason (like handling media), it's best to only accept and return JSON-formatted or plain-text data, as they're widely supported and relatively easy to parse.