447 lines
12 KiB
Perl
447 lines
12 KiB
Perl
package Peace::Swagger;
|
|
|
|
use v5.30.0;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use Data::Dumper;
|
|
|
|
use Params::ValidationCompiler qw/validation_for/;
|
|
use Types::Standard qw/ArrayRef Str HashRef Int/;
|
|
|
|
use Const::Fast;
|
|
use Email::Valid;
|
|
|
|
const my $EMAIL_VALIDATOR => Type::Tiny->new(
|
|
name => 'Email',
|
|
constraint => sub {
|
|
return Email::Valid->rfc822($_);
|
|
},
|
|
message => sub { return "$_ is not an email address."; },
|
|
);
|
|
|
|
const my $UUID_VALIDATOR => Type::Tiny->new(
|
|
name => 'Uuid',
|
|
constraint => sub {
|
|
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
},
|
|
message => sub { return "$_ is not an uuid."; },
|
|
);
|
|
|
|
const my $DEVELOPER_IDENTIFIER_VALIDATOR => Type::Tiny->new(
|
|
name => 'DeveloperIdentifier',
|
|
constraint => sub {
|
|
return 1 if $EMAIL_VALIDATOR->check($_);
|
|
return 1 if $UUID_VALIDATOR->check($_);
|
|
return 0;
|
|
},
|
|
message => sub { return "$_ is not a developer identifier."; },
|
|
);
|
|
|
|
sub new {
|
|
my $class = shift;
|
|
my $self = bless {}, $class;
|
|
}
|
|
|
|
sub schema {
|
|
return {
|
|
openapi => "3.1.0",
|
|
info => {
|
|
title => 'Peace API',
|
|
version => '0.0.1',
|
|
license => {
|
|
name => 'AGPLv3',
|
|
url => 'https://www.gnu.org/licenses/agpl-3.0.en.html',
|
|
}
|
|
},
|
|
paths => { developer(), }
|
|
};
|
|
}
|
|
|
|
{
|
|
my $validator = validation_for(
|
|
params => {
|
|
type => { type => Str },
|
|
}
|
|
);
|
|
|
|
sub _type_to_type_tiny {
|
|
my $self = shift;
|
|
my %params = $validator->(@_);
|
|
my ($type) = $params{type};
|
|
return Str if $type eq 'string';
|
|
return Int if $type eq 'integer';
|
|
die 'No such type declared.';
|
|
}
|
|
}
|
|
|
|
{
|
|
my $validator = validation_for(
|
|
params => {
|
|
format => { type => Str },
|
|
}
|
|
);
|
|
|
|
sub _format_to_type_tiny {
|
|
my $self = shift;
|
|
my %params = $validator->(@_);
|
|
my ($format) = $params{format};
|
|
return $EMAIL_VALIDATOR if $format eq 'email';
|
|
return $DEVELOPER_IDENTIFIER_VALIDATOR
|
|
if $format eq 'developer_identifier';
|
|
die "No such format declared.";
|
|
}
|
|
}
|
|
|
|
sub developer {
|
|
return (
|
|
"/developer" => {
|
|
summary => "Allows to work with developers.",
|
|
post => developer_post(),
|
|
},
|
|
"/developer/{identifier}/application" => {
|
|
summary =>
|
|
"Allows to work with applications with developer's permissions.",
|
|
post => developer_application_post(),
|
|
}
|
|
);
|
|
}
|
|
|
|
{
|
|
my $validator = validation_for(
|
|
params => {
|
|
json => { type => HashRef },
|
|
spec => { type => HashRef },
|
|
headers => { type => HashRef },
|
|
stash => { type => HashRef },
|
|
}
|
|
);
|
|
|
|
sub validate_request {
|
|
my $self = shift;
|
|
my %params = $validator->(@_);
|
|
my $json = $params{json};
|
|
my $spec = $params{spec};
|
|
my $headers = $params{headers};
|
|
my $stash = $params{stash};
|
|
my $spec_parameters = $spec->{parameters};
|
|
my $validated_json_keys = {};
|
|
|
|
for my $parameter_spec (@$spec_parameters) {
|
|
$self->_validate_parameter(
|
|
parameter_spec => $parameter_spec,
|
|
json => $json,
|
|
headers => $headers,
|
|
stash => $stash,
|
|
validated_json_keys => $validated_json_keys,
|
|
);
|
|
}
|
|
## Avoiding fancy exploits.
|
|
my @keys_to_remove =
|
|
grep { !defined $validated_json_keys->{$_} } keys %$json;
|
|
for my $key (@keys_to_remove) {
|
|
delete $json->{$key};
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
my $validator = validation_for(
|
|
params => {
|
|
parameter_spec => { type => HashRef },
|
|
json => { type => HashRef },
|
|
headers => { type => HashRef },
|
|
stash => { type => HashRef },
|
|
validated_json_keys => { type => HashRef },
|
|
}
|
|
);
|
|
|
|
sub _validate_parameter {
|
|
my $self = shift;
|
|
my %params = $validator->(@_);
|
|
my ( $parameter_spec, $json, $headers, $stash, $validated_json_keys ) =
|
|
@params{
|
|
qw/
|
|
parameter_spec json headers stash validated_json_keys/
|
|
};
|
|
my $name = $parameter_spec->{name};
|
|
my $schema = $parameter_spec->{schema};
|
|
my $required = $parameter_spec->{required};
|
|
my $in = $parameter_spec->{in};
|
|
my $type = $schema->{type};
|
|
my $format = $schema->{format};
|
|
my $pattern = $schema->{pattern};
|
|
|
|
my $value;
|
|
if ( $in eq 'query' ) {
|
|
$value = $json->{$name};
|
|
$validated_json_keys->{$name} = 1;
|
|
}
|
|
print Data::Dumper::Dumper $headers;
|
|
$value = $headers->{$name} if $in eq 'header';
|
|
$value = $stash->{$name} if $in eq 'path';
|
|
|
|
if ( !defined $value ) {
|
|
die "$name is required." if $required;
|
|
return;
|
|
}
|
|
|
|
unless ( $self->_type_to_type_tiny( type => $type )->check($value) ) {
|
|
die "$value is not $type.";
|
|
}
|
|
|
|
if ( defined $format ) {
|
|
my $type_format = $self->_format_to_type_tiny( format => $format );
|
|
unless ( $type_format->check($value) ) {
|
|
die "$value is not $format.";
|
|
}
|
|
}
|
|
|
|
if ( defined $pattern ) {
|
|
die "$value doesn\'t match $pattern." if $value !~ /^$pattern$/;
|
|
}
|
|
}
|
|
}
|
|
|
|
sub developer_application_post {
|
|
return {
|
|
summary => 'Creates a application.',
|
|
parameters => [
|
|
{
|
|
name => 'identifier',
|
|
description => 'Developer\'s email or uuid.',
|
|
required => 1,
|
|
in => 'path',
|
|
schema => {
|
|
type => "string",
|
|
format => "developer_identifier",
|
|
}
|
|
},
|
|
{
|
|
name => 'api_key',
|
|
description => '<secret>',
|
|
required => 1,
|
|
in => 'header',
|
|
schema => {
|
|
type => "string",
|
|
}
|
|
},
|
|
{
|
|
name => 'name',
|
|
description => 'Project\'s name.',
|
|
required => 1,
|
|
in => 'query',
|
|
schema => {
|
|
type => "string",
|
|
}
|
|
},
|
|
{
|
|
name => 'description',
|
|
required => 1,
|
|
in => 'query',
|
|
description => 'Application\'s description.',
|
|
schema => {
|
|
type => "string",
|
|
}
|
|
},
|
|
{
|
|
name => 'url',
|
|
required => 1,
|
|
in => 'query',
|
|
description => 'Project\'s url.',
|
|
pattern => '^https?://',
|
|
schema => {
|
|
type => "string",
|
|
}
|
|
},
|
|
{
|
|
name => 'price',
|
|
required => 1,
|
|
in => 'query',
|
|
description => 'Price in dollar cents.',
|
|
schema => {
|
|
type => "integer",
|
|
}
|
|
},
|
|
{
|
|
name => 'git_repo',
|
|
required => 1,
|
|
in => 'query',
|
|
description => 'Project\'s git repository.',
|
|
schema => {
|
|
type => "string",
|
|
pattern => 'https?://.*'
|
|
}
|
|
},
|
|
{
|
|
name => 'app_id',
|
|
required => 1,
|
|
in => 'query',
|
|
description => 'Project\'s application id in flatpak format.',
|
|
schema => {
|
|
type => "string",
|
|
}
|
|
},
|
|
{
|
|
name => 'flatpak_builder_file',
|
|
required => 1,
|
|
in => 'query',
|
|
description => 'Project\'s path to the flatpak builder file.',
|
|
schema => {
|
|
type => "string",
|
|
}
|
|
},
|
|
{
|
|
name => 'flatpak_repo',
|
|
required => 1,
|
|
in => 'query',
|
|
description =>
|
|
'Flatpak repo to be attached to the .flatpak file.',
|
|
schema => {
|
|
type => "string",
|
|
}
|
|
}
|
|
]
|
|
};
|
|
}
|
|
|
|
sub developer_post {
|
|
return {
|
|
summary => 'Creates a developer',
|
|
parameters => [
|
|
{
|
|
name => 'secret',
|
|
required => 1,
|
|
in => 'query',
|
|
description =>
|
|
'Access credential for Authenticate latter as the created developer.',
|
|
schema => {
|
|
type => "string",
|
|
|
|
# Bcrypt limit is 72 characters.
|
|
pattern => ".{10,72}"
|
|
}
|
|
},
|
|
{
|
|
name => 'name',
|
|
required => 1,
|
|
in => 'query',
|
|
description => 'Developer\'s real name',
|
|
schema => {
|
|
type => "string",
|
|
}
|
|
},
|
|
{
|
|
name => 'surname',
|
|
required => 1,
|
|
in => 'query',
|
|
description => 'Developer\'s real surname',
|
|
schema => {
|
|
type => "string",
|
|
}
|
|
},
|
|
{
|
|
name => 'surname',
|
|
required => 1,
|
|
in => 'query',
|
|
description => 'Developer\'s real surname',
|
|
schema => {
|
|
type => "string",
|
|
}
|
|
},
|
|
{
|
|
name => 'email',
|
|
required => 1,
|
|
in => 'query',
|
|
description => 'Developer\'s email',
|
|
schema => {
|
|
type => "string",
|
|
format => 'email',
|
|
}
|
|
},
|
|
{
|
|
name => 'country',
|
|
description => 'Developer\'s country in iso3166format',
|
|
required => 1,
|
|
in => 'query',
|
|
schema => {
|
|
type => "string",
|
|
pattern => '[A-Z]{2}',
|
|
}
|
|
}
|
|
]
|
|
};
|
|
|
|
}
|
|
1;
|
|
|
|
=encoding utf8
|
|
|
|
=head1 NAME
|
|
|
|
Peace::Swagger - OpenAPI definitions for the Peace API.
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
my $swagger = Peace::Swagger->new;
|
|
|
|
my $spec = $swagger->schema;
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
This module aims to help in the programmatic description
|
|
of all API endpoints in a way that makes possible to
|
|
reuse those schemas in the code and in the documentation.
|
|
|
|
=head1 INSTANCE METHODS
|
|
|
|
Peace::Swagger implements the following instance methods:
|
|
|
|
=head2 new
|
|
|
|
my $swagger = Peace::Swagger->new;
|
|
|
|
Instances a Peace::Swagger.
|
|
|
|
=head1 METHODS
|
|
|
|
Peace::Swagger implements the following methods:
|
|
|
|
=head2 schema
|
|
|
|
my $schema = $swagger->schema;
|
|
|
|
Returns the complete openapi schema.
|
|
|
|
=head2 validate_request
|
|
|
|
$swagger->validate_request(json => $json, spec => $spec);
|
|
|
|
Validates the spec for a specific endpoint say
|
|
developer_post against the json got from the
|
|
user and dies if the check is unsuccesful.
|
|
|
|
=head2 developer
|
|
|
|
my $developer_schema = $swagger->developer;
|
|
|
|
Returns the schemas associated with the
|
|
L<Peace::Model::Developer> object.
|
|
|
|
=head2 developer_post
|
|
|
|
my $developer_post = $swagger->developer_post;
|
|
|
|
Returns the schema of the post request
|
|
to the /developer enpoint.
|
|
|
|
=head2 developer_application_post
|
|
|
|
my $developer_application_post = $swagger->developer_application_post;
|
|
|
|
Returns the schema of the post request
|
|
to the /developer/:identifier/application endpoint.
|
|
|
|
=cut
|