230 lines
6.8 KiB
Perl
230 lines
6.8 KiB
Perl
#!/usr/bin/perl
|
|
#
|
|
# DW::API::Method
|
|
#
|
|
# Defines Method objects and provides helper functions
|
|
# for use in DW::Controller::API::REST resources.
|
|
#
|
|
# 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::API::Method;
|
|
|
|
use strict;
|
|
use warnings;
|
|
use JSON;
|
|
use Carp qw/ croak /;
|
|
|
|
use DW::API::Parameter;
|
|
use DW::Request;
|
|
|
|
my @ATTRIBUTES = qw(name desc handler responses);
|
|
my @HTTP_VERBS = qw(GET POST DELETE PUT);
|
|
|
|
# Usage: define_method ( action, desc, handler )
|
|
# Creates and returns a new method object for use
|
|
# in DW::Controller::API::REST resource definitions.
|
|
|
|
sub define_method {
|
|
my ( $class, $action, $handler, $config ) = @_;
|
|
|
|
my $method = {
|
|
name => $action,
|
|
summary => $config->{summary},
|
|
desc => $config->{description},
|
|
handler => $handler,
|
|
tags => [],
|
|
responses => {},
|
|
};
|
|
|
|
bless $method, $class;
|
|
$method->_responses( $config->{responses} );
|
|
return $method;
|
|
}
|
|
|
|
# Usage: param ( @args )
|
|
# Creates a new DW::API::Parameter object and
|
|
# adds it to the parameters hash of the calling
|
|
# method object
|
|
|
|
sub param {
|
|
my ( $self, @args ) = @_;
|
|
|
|
my $param = DW::API::Parameter->define_parameter(@args);
|
|
my $name = $param->{name};
|
|
$self->{params}{$name} = $param;
|
|
}
|
|
|
|
# Usage: body ( @args )
|
|
# Creates a special instance of DW::API::Parameter object and
|
|
# adds it as the requestBody definition for the calling method
|
|
sub body {
|
|
my ( $self, $config ) = @_;
|
|
$self->{requestBody}->{required} = $config->{required};
|
|
for my $ct ( keys( %{ $config->{content} } ) ) {
|
|
my $param = DW::API::Parameter->define_body( $config->{content}->{$ct}, $ct );
|
|
$self->{requestBody}{content}{$ct} = $param;
|
|
}
|
|
}
|
|
|
|
# Usage: success ( desc, schema )
|
|
# Adds a 200 response description and optional schema
|
|
# to the responses hash of the calling method object
|
|
# FIXME: In the future, we may want 'successes' that aren't
|
|
# 200 responses. This will need to be changed accordingly.
|
|
|
|
# sub success {
|
|
# my ($self, $desc, $schema) = @_;
|
|
|
|
# $self->{responses}{200} = { desc => $desc, schema => $schema};
|
|
# }
|
|
|
|
# Usage: _responses ( method, config )
|
|
# Registers various response types, and validates any with a schema.
|
|
|
|
sub _responses {
|
|
my ( $self, $resp_config ) = @_;
|
|
|
|
# add response descriptions
|
|
for my $code ( keys %$resp_config ) {
|
|
my $desc = $resp_config->{$code}->{description};
|
|
$self->{responses}{$code} = { desc => $desc };
|
|
|
|
# for every content type we provide as response, see if we have a valid schema
|
|
for my $content_type ( keys %{ $resp_config->{$code}->{content} } ) {
|
|
my $content = $resp_config->{$code}->{content}->{$content_type};
|
|
DW::Controller::API::REST::schema($content);
|
|
$self->{responses}{$code}{content}->{$content_type} = $content;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
# Usage: _validate ( Method object )
|
|
# Does some simple validation checks for method objects
|
|
# Makes sure required fields are present, and that the
|
|
# HTTP action is a valid one.
|
|
|
|
sub _validate {
|
|
my $self = $_[0];
|
|
|
|
for my $field (@ATTRIBUTES) {
|
|
die "$self is missing required field $field" unless defined $self->{$field};
|
|
}
|
|
my $action = $self->{name};
|
|
die "$action isn't a valid HTTP action" unless grep( $action, @HTTP_VERBS );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
# Usage: return rest_ok( response, content-type, status code )
|
|
# takes a scalar or scalar ref to a response, an
|
|
# optional content-type, and optional status code - default
|
|
# content-type is JSON if not specified, and default status is
|
|
# Returns a response object with the given content, content-type,
|
|
# and status code.
|
|
sub rest_ok {
|
|
croak 'too many arguments to api_ok!'
|
|
unless scalar @_ <= 4;
|
|
|
|
my ( $self, $response, $content_type, $status_code ) = @_;
|
|
my $r = DW::Request->get;
|
|
|
|
$content_type ||= 'application/json';
|
|
$status_code ||= defined $response && length $response ? 200 : 204;
|
|
my $validator = $self->{responses}{$status_code}{content}{$content_type}{validator};
|
|
|
|
# guarantee that we're returning what we say we return.
|
|
if ( defined $validator ) {
|
|
my @errors = $validator->validate($response);
|
|
if (@errors) {
|
|
croak "Invalid response format! Validator errors: @errors";
|
|
}
|
|
}
|
|
|
|
if ( defined $response ) {
|
|
|
|
# if we have JSON, call the formatter to pretty-print it. Otherwise, we assume
|
|
# other content-types have already been properly formatted for us.
|
|
if ( $content_type eq "application/json" ) {
|
|
$r->print( to_json( $response, { convert_blessed => 1, latin1 => 1, pretty => 1 } ) );
|
|
}
|
|
else {
|
|
$r->print($response);
|
|
}
|
|
}
|
|
|
|
$r->status($status_code);
|
|
$r->content_type($content_type);
|
|
return;
|
|
}
|
|
|
|
# Usage: return rest_error( $status_code, $msg )
|
|
# Returns a standard format JSON error message.
|
|
# The first argument is the status code, the second optional
|
|
# argument is an error message to be returned. If no message is
|
|
# provided, it will pull from the route configuration instead,
|
|
# and if there's no route configuration, will return a generic error.
|
|
sub rest_error {
|
|
my ( $self, $status_code, $msg ) = @_;
|
|
my $status_desc = $self->{responses}{$status_code}{desc};
|
|
my $default_msg = defined $status_desc ? $status_desc : 'Unknown error.';
|
|
$msg = defined $msg ? $msg : $default_msg;
|
|
|
|
my $res = {
|
|
success => 0,
|
|
error => $msg,
|
|
};
|
|
|
|
my $r = DW::Request->get;
|
|
$r->content_type("application/json");
|
|
$r->print( to_json($res) );
|
|
$r->status($status_code);
|
|
return;
|
|
}
|
|
|
|
# Formatter method for the JSON package to output method objects as JSON.
|
|
|
|
sub TO_JSON {
|
|
my $self = $_[0];
|
|
|
|
my $json = { description => $self->{desc} };
|
|
|
|
if ( defined $self->{params} ) {
|
|
$json->{parameters} = [ values %{ $self->{params} } ];
|
|
}
|
|
|
|
if ( defined $self->{requestBody} ) {
|
|
$json->{requestBody} = $self->{requestBody};
|
|
if ( defined $self->{requestBody}{required} && $self->{requestBody}{required} ) {
|
|
$json->{requestBody}{required} = $JSON::true;
|
|
}
|
|
else {
|
|
delete $json->{requestBody}{required};
|
|
}
|
|
}
|
|
|
|
my $responses = $self->{responses};
|
|
|
|
for my $key ( keys %{ $self->{responses} } ) {
|
|
$json->{responses}{$key} = { description => $responses->{$key}{desc} };
|
|
for my $return_type ( keys %{ $self->{responses}{$key}{content} } ) {
|
|
$json->{responses}{$key}{content}{$return_type}{schema} =
|
|
$responses->{$key}{content}{$return_type}{schema}
|
|
if defined $responses->{$key}{content}{$return_type}{schema};
|
|
}
|
|
}
|
|
|
|
return $json;
|
|
}
|
|
|
|
1;
|
|
|