formerly known as swagger already working: collection GET/POST, item GET Change-Id: Icd6ba3f60fd51fb948a957801a963d134fe6e127changes/71/12771/7
parent
3becbebe1b
commit
c7021aa568
@ -1,68 +1,331 @@
|
||||
package NGCP::Panel::Utils::API;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Find::Rule;
|
||||
|
||||
|
||||
sub get_collections {
|
||||
my @files = @{get_collections_files()};
|
||||
my(@collections, @packages, @modules);
|
||||
foreach my $mod(@files) {
|
||||
# extract file base from path (e.g. Foo from lib/something/Foo.pm)
|
||||
$mod =~ s/^.+\/([a-zA-Z0-9_]+)\.pm$/$1/;
|
||||
my $package = 'NGCP::Panel::Controller::API::'.$mod;
|
||||
my $rel = lc $mod;
|
||||
$mod = 'NGCP::Panel::Controller::API::'.$mod;
|
||||
push @modules, $mod;
|
||||
push @packages, $package;
|
||||
push @collections, $rel;
|
||||
}
|
||||
return \@files, \@packages, \@collections, \@modules;
|
||||
}
|
||||
sub get_collections_files {
|
||||
my($library,$libpath) = @_;
|
||||
if(!$libpath){
|
||||
# figure out base path of our api modules
|
||||
$library ||= "NGCP/Panel/Controller/API/Root.pm";
|
||||
$libpath = $INC{$library};
|
||||
$libpath =~ s/\/[^\/]+$/\//;
|
||||
}
|
||||
# find all modules not called Root.pm and *Item.pm
|
||||
# (which should then be just collections)
|
||||
my $rootrule = File::Find::Rule->new->name('Root.pm');
|
||||
my $itemrule = File::Find::Rule->new->name('*Item.pm');
|
||||
my $rule = File::Find::Rule->new
|
||||
->mindepth(1)
|
||||
->maxdepth(1)
|
||||
->name('*.pm')
|
||||
->not($rootrule)
|
||||
->not($itemrule);
|
||||
my @colls = $rule->in($libpath);
|
||||
|
||||
return \@colls;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
NGCP::Panel::Utils::API
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
A helper to manipulate REST API related data
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Irina Peshinskaya
|
||||
|
||||
=head1 LICENSE
|
||||
|
||||
This library is free software. You can redistribute it and/or modify
|
||||
it under the same terms as Perl itself.
|
||||
|
||||
=cut
|
||||
# vim: set tabstop=4 expandtab:
|
||||
package NGCP::Panel::Utils::API;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Find::Rule;
|
||||
use JSON qw();
|
||||
|
||||
|
||||
sub get_collections {
|
||||
my @files = @{get_collections_files()};
|
||||
my(@collections, @packages, @modules);
|
||||
foreach my $mod(@files) {
|
||||
# extract file base from path (e.g. Foo from lib/something/Foo.pm)
|
||||
$mod =~ s/^.+\/([a-zA-Z0-9_]+)\.pm$/$1/;
|
||||
my $package = 'NGCP::Panel::Controller::API::'.$mod;
|
||||
my $rel = lc $mod;
|
||||
$mod = 'NGCP::Panel::Controller::API::'.$mod;
|
||||
push @modules, $mod;
|
||||
push @packages, $package;
|
||||
push @collections, $rel;
|
||||
}
|
||||
return \@files, \@packages, \@collections, \@modules;
|
||||
}
|
||||
|
||||
sub get_collections_files {
|
||||
my($library,$libpath) = @_;
|
||||
if(!$libpath){
|
||||
# figure out base path of our api modules
|
||||
$library ||= "NGCP/Panel/Controller/API/Root.pm";
|
||||
$libpath = $INC{$library};
|
||||
$libpath =~ s/\/[^\/]+$/\//;
|
||||
}
|
||||
# find all modules not called Root.pm and *Item.pm
|
||||
# (which should then be just collections)
|
||||
my $rootrule = File::Find::Rule->new->name('Root.pm');
|
||||
my $itemrule = File::Find::Rule->new->name('*Item.pm');
|
||||
my $rule = File::Find::Rule->new
|
||||
->mindepth(1)
|
||||
->maxdepth(1)
|
||||
->name('*.pm')
|
||||
->not($rootrule)
|
||||
->not($itemrule);
|
||||
my @colls = $rule->in($libpath);
|
||||
|
||||
return \@colls;
|
||||
}
|
||||
|
||||
sub generate_swagger_datastructure {
|
||||
my ($collections, $user_role) = @_;
|
||||
|
||||
my %paths;
|
||||
my %schemas;
|
||||
my %responses = (
|
||||
ErrorResponse => {
|
||||
description => 'An error',
|
||||
content => {
|
||||
"application/json" => {
|
||||
"schema" => {
|
||||
type => "object",
|
||||
properties => {
|
||||
code => { type => "integer" },
|
||||
message => { type => "string" },
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
my %parameters = (
|
||||
PageParameter => {
|
||||
name => 'page',
|
||||
in => 'query',
|
||||
description => 'Pagination page which should be displayed (default: 1)',
|
||||
example => 1,
|
||||
},
|
||||
RowsParameter => {
|
||||
name => 'rows',
|
||||
in => 'query',
|
||||
description => 'Number of rows in one pagination page (default: 10)',
|
||||
example => 10,
|
||||
},
|
||||
ItemIdParameter => {
|
||||
"name" => "id",
|
||||
"in" => "path",
|
||||
"required" => JSON::true,
|
||||
"schema" => { type => "integer" },
|
||||
},
|
||||
);
|
||||
|
||||
my @chapters = sort (keys %{ $collections });
|
||||
|
||||
for my $chapter (@chapters) {
|
||||
my $col = $collections->{$chapter};
|
||||
my $p = {}; # Path Item Object
|
||||
my $item_p = {}; # Path Item Object for "NGCP Item"
|
||||
my $title = $col->{name};
|
||||
my $entity = $col->{entity_name};
|
||||
|
||||
if (grep {m/^GET$/} @{ $col->{actions} }) {
|
||||
$p->{get} = {
|
||||
summary => "Get $entity items",
|
||||
tags => ['Collection Get'],
|
||||
responses => {
|
||||
"200" => {
|
||||
description => "$title",
|
||||
content => {
|
||||
"application/json" => {
|
||||
schema => {
|
||||
type => "array", # I want an Array to $entity objects here
|
||||
items => {
|
||||
'$ref' => "#/components/schemas/$entity",
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for my $query_param (@{ $col->{query_params} // [] }) {
|
||||
push @{$p->{get}{parameters} }, {
|
||||
name => $query_param->{param},
|
||||
description => $query_param->{description},
|
||||
in => 'query',
|
||||
};
|
||||
}
|
||||
if ($col->{sorting_cols} && @{ $col->{sorting_cols} }) {
|
||||
push @{$p->{get}{parameters} }, {
|
||||
name => 'order_by',
|
||||
description => 'Order collection by a specific attribute.',
|
||||
in => 'query',
|
||||
schema => {
|
||||
type => 'string',
|
||||
enum => [ @{ $col->{sorting_cols} } ],
|
||||
}
|
||||
},{
|
||||
name => 'order_by_direction',
|
||||
description => 'Direction which the collection should be ordered by. Possible values are: asc (default), desc.',
|
||||
in => 'query',
|
||||
example => 'asc',
|
||||
schema => {
|
||||
type => 'string',
|
||||
enum => [ 'asc', 'desc' ],
|
||||
}
|
||||
};
|
||||
}
|
||||
push @{ $p->{get}{parameters} }, {
|
||||
'$ref' => '#/components/parameters/PageParameter',
|
||||
},{
|
||||
'$ref' => '#/components/parameters/RowsParameter',
|
||||
};
|
||||
}
|
||||
|
||||
if (grep {m/^POST$/} @{ $col->{actions} }) {
|
||||
$p->{post} = {
|
||||
# description => "Creates a new item of $title",
|
||||
summary => "Create a new $entity",
|
||||
tags => ['Collection Post'],
|
||||
requestBody => {
|
||||
required => JSON::true,
|
||||
content => {
|
||||
"application/json" => {
|
||||
schema => {
|
||||
'$ref' => "#/components/schemas/$entity",
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
responses => {
|
||||
"201" => {
|
||||
description => "The newly created item or empty",
|
||||
content => {
|
||||
"application/json" => {
|
||||
schema => {
|
||||
type => "array",
|
||||
items => {
|
||||
'$ref' => "#/components/schemas/$entity",
|
||||
}
|
||||
}
|
||||
},
|
||||
# "*/*" => {
|
||||
# schema => { type => "string", maxLength => 0 }
|
||||
# }
|
||||
},
|
||||
headers => {
|
||||
"Location" => {
|
||||
"description" => "Location of the newly created item (as a relative path)",
|
||||
"schema" => {
|
||||
"type" => "string",
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422" => { '$ref' => "#/components/responses/ErrorResponse" },
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (grep {m/^GET$/} @{ $col->{item_actions} }) {
|
||||
$item_p->{get} = {
|
||||
summary => "Get a specific $entity",
|
||||
tags => ['Item Get'],
|
||||
responses => {
|
||||
"200" => {
|
||||
description => "$title",
|
||||
content => {
|
||||
"application/json" => {
|
||||
schema => {
|
||||
'$ref' => "#/components/schemas/$entity",
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
# common description for all methods
|
||||
$p->{description} = $col->{description};
|
||||
|
||||
#push @paths, $p;
|
||||
$paths{'/'.$chapter.'/'} = $p;
|
||||
if (keys %{ $item_p }) {
|
||||
$item_p->{description} = $col->{description};
|
||||
$item_p->{parameters} = [
|
||||
{ '$ref' => '#/components/parameters/ItemIdParameter' },
|
||||
];
|
||||
$paths{'/'.$chapter.'/{id}'} = $item_p;
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------
|
||||
|
||||
my $e = {
|
||||
type => "object",
|
||||
properties => {},
|
||||
required => [],
|
||||
};
|
||||
|
||||
# possible values for types: null, (select options), Number, Boolean, Array, Object, String
|
||||
for my $f (@{ $col->{fields} }) {
|
||||
my $p = {};
|
||||
if ($f->{type_original} eq "Select" ||
|
||||
($f->{type_original} =~ m/\+NGCP::Panel::Field::.*Select$/ && $f->{enum})) {
|
||||
$p->{type} = "string";
|
||||
$p->{enum} = [ map {$_->{value}} @{ $f->{enum} // [] } ];
|
||||
} elsif ($f->{type_original} eq 'IntRange') {
|
||||
$p->{type} = "number";
|
||||
$p->{enum} = [ map {$_->{value}} @{ $f->{enum} // [] } ];
|
||||
} elsif ($f->{type_original} eq "Boolean") {
|
||||
$p->{type} = "boolean";
|
||||
} elsif (grep {m/^Number$/} @{$f->{types}}) {
|
||||
$p->{type} = "number";
|
||||
} elsif ($f->{type_original} eq '+NGCP::Panel::Field::EmailList' ||
|
||||
$f->{type_original} eq 'Email') {
|
||||
$p->{type} = "string";
|
||||
$p->{format} = "email"; # not the same as emaillist but that's nit-picky
|
||||
} elsif ($f->{type_original} eq '+NGCP::Panel::Field::DateTime') {
|
||||
$p->{type} = "string";
|
||||
$p->{format} = "date-time"; # actually a slightly different format
|
||||
} elsif ($f->{type_original} eq "Text" || grep {m/^String$/} @{$f->{types}}) {
|
||||
$p->{type} = "string";
|
||||
} elsif ($f->{type_original} eq "Repeatable" || grep {m/^Array$/} @{$f->{types}}) {
|
||||
$p->{type} = "array";
|
||||
$p->{items}{type} = "object"; # content of array basically unspecified
|
||||
} else {
|
||||
$p->{type} = "object"; # object or uncategorizable
|
||||
}
|
||||
|
||||
$p->{description} = $f->{description};
|
||||
if (grep {m/^null$/} @{ $f->{types} // [] }) {
|
||||
push @{ $e->{required} }, $f->{name};
|
||||
}
|
||||
|
||||
$e->{properties}{$f->{name}} = $p;
|
||||
}
|
||||
unless (@{ $e->{required} }) {
|
||||
delete $e->{required}; # empty required is not allowed
|
||||
}
|
||||
unless (keys %{ $e->{properties} }) {
|
||||
delete $e->{properties}; # try delete empty properties (then it's a valid Free Form Object)
|
||||
}
|
||||
|
||||
$schemas{$entity} = $e;
|
||||
}
|
||||
|
||||
my $role = "".$user_role;
|
||||
my $result = {
|
||||
"openapi" => "3.0.0",
|
||||
"info" => {
|
||||
"title" => "NGCP API",
|
||||
"description" => "Sipwise NGCP API (role $role)",
|
||||
"version" => "1.0.1",
|
||||
},
|
||||
"servers" => [ { "url" => "/api" } ],
|
||||
|
||||
"paths" => \%paths,
|
||||
"components" => {
|
||||
"schemas" => \%schemas,
|
||||
"responses" => \%responses,
|
||||
"parameters" => \%parameters,
|
||||
},
|
||||
};
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
NGCP::Panel::Utils::API
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
A helper to manipulate REST API related data
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Irina Peshinskaya
|
||||
|
||||
=head1 LICENSE
|
||||
|
||||
This library is free software. You can redistribute it and/or modify
|
||||
it under the same terms as Perl itself.
|
||||
|
||||
=cut
|
||||
# vim: set tabstop=4 expandtab:
|
||||
|
||||
@ -0,0 +1,76 @@
|
||||
use warnings;
|
||||
use strict;
|
||||
|
||||
use Test::More;
|
||||
|
||||
use DDP;
|
||||
|
||||
use_ok('NGCP::Panel::Utils::API');
|
||||
|
||||
my $empty_result = NGCP::Panel::Utils::API::generate_swagger_datastructure(
|
||||
{}, 'admin',
|
||||
);
|
||||
|
||||
basic_result_check($empty_result);
|
||||
|
||||
my $collections1 = {
|
||||
admins => {
|
||||
actions => [ "GET", "HEAD", "OPTIONS", "POST" ],
|
||||
config => {}, # unused currently
|
||||
description => "Defines admins to log into the system via panel or api.",
|
||||
entity_name => "Admin",
|
||||
fields => [
|
||||
{ description => "Billing data",
|
||||
name => "billing_data",
|
||||
readonly => undef,
|
||||
type_original => "Boolean",
|
||||
types => [ "null", "Boolean" ]
|
||||
},
|
||||
],
|
||||
item_actions => [ "DELETE", "GET", "HEAD", "OPTIONS" ],
|
||||
journal_resource_config => {}, # unused currently
|
||||
name => "Admins",
|
||||
properties => {},
|
||||
query_params => [
|
||||
{ description => "Filter for admins belonging to a specific reseller",
|
||||
param => "reseller_id",
|
||||
},
|
||||
],
|
||||
sorting_cols => [ "id", "reseller_id", ],
|
||||
uploads => [], # unused currently
|
||||
uri => "/api/admins/", # unused currently
|
||||
},
|
||||
};
|
||||
|
||||
my $result1 = NGCP::Panel::Utils::API::generate_swagger_datastructure(
|
||||
$collections1, 'admin',
|
||||
);
|
||||
|
||||
basic_result_check($result1);
|
||||
|
||||
ok(exists($result1->{paths}{'/admins/'}), "Collection Path for admins exists");
|
||||
ok(exists($result1->{paths}{'/admins/{id}'}), "Item Path for admins exists");
|
||||
|
||||
ok(exists($result1->{paths}{'/admins/'}{get}), "Collection Path for admins has get");
|
||||
ok(exists($result1->{paths}{'/admins/'}{post}), "Collection Path for admins has post");
|
||||
ok(exists($result1->{paths}{'/admins/{id}'}{get}), "Item Path for admins has get");
|
||||
|
||||
ok(exists($result1->{components}{schemas}{Admin}), "Schema for Admin exists");
|
||||
is($result1->{components}{schemas}{Admin}{type}, 'object', "Schema for Admin content check");
|
||||
is($result1->{components}{schemas}{Admin}{properties}{billing_data}{type}, 'boolean', "Schema for Admin content check");
|
||||
|
||||
done_testing;
|
||||
|
||||
sub basic_result_check {
|
||||
my ($res) = @_;
|
||||
|
||||
ok(exists($res->{info}), "Info Object exists");
|
||||
ok(exists($res->{openapi}), "OpenAPI Object exists");
|
||||
ok(exists($res->{paths}), "Paths Object exists");
|
||||
ok(exists($res->{components}), "Components Object exists");
|
||||
|
||||
is ($res->{openapi}, '3.0.0', 'Check OpenAPI version');
|
||||
is ($res->{info}{title}, 'NGCP API', 'Check NGCP info');
|
||||
return;
|
||||
}
|
||||
|
||||
Loading…
Reference in new issue