OpenAPI-Client-1.07/000755 000765 000024 00000000000 14326233320 015255 5ustar00jhthorsenstaff000000 000000 OpenAPI-Client-1.07/Changes000644 000765 000024 00000007046 14326233317 016565 0ustar00jhthorsenstaff000000 000000 Revision history for perl distribution OpenAPI-Client 1.07 2022-10-26T22:31:59 - No need to run t/00-project.t on install #40 1.06 2022-09-11T09:05:17 - Fix "Use of uninitialized value" warning - Specified Perl version - Updated basic repository files - Updated contributors list 1.05 2022-08-17T09:19:00+0900 - Add support for "collectionFormat" for query params #38 1.04 2022-06-03T09:56:54+0900 - Allow inheritance and roles to be applied before new() #35 #37 Contributor: Veesh Goldman 1.03 2021-10-02T09:27:11+0900 - Fix documentation issues regarding "base_url" #33 - Fix "mojo openapi ..." with 3.0.x spec - Changed "mojo openapi -I" to also be able to dump the whole spec (bundled) 1.02 2021-08-28T16:49:50+0200 - Add support for kebab case in path templating #32 Contributor: Roy Storey 1.01 2021-06-17T12:45:28+0900 - Fix generating correct base URL for OpenAPIv3 schemas 1.00 2021-02-23T15:25:00+0900 - Updated to use new JSON::Validator schema API - Add support for JSON::Validator::Schema::OpenAPIv3 - Removed pre_processor() 0.25 2020-06-30T18:45:37+0900 - Fix setting correct request method on client errors 0.24 2019-05-05T14:36:11+0700 - Fix t/command-local-with-ref.t 0.23 2019-05-05T14:13:14+0700 - Add documentation for option --information|-I - Fix deprecation warning for JSON::Validator->coerce(1) #17 - Fix printing YAML::XS::Boolean as "false" and "true" in dumped documentation - Printed documentation has $ref resolved Can be turned off with JSON_VALIDATOR_REPLACE=0 0.22 2019-01-25T14:45:56+0900 - Updated documentation to use $client instead of $self 0.21 2018-10-03T16:19:00+0900 - Fix failing tests after Mojolicious::Plugin::OpenAPI 2.00 #15 0.20 2018-09-30T22:04:20+0900 - Fix skip tests when getting "Service Unavailable" 0.19 2018-09-26T13:56:14+0900 - Skip tests when getting "Service Unavailable" 0.18 2018-09-02T14:13:51+0200 - Add "after_build_tx" event - Add "opeartionId_p" methods for promise based API 0.17 2018-08-02T16:00:20+0800 - Fix duplicate keys in test #13 0.16 2018-07-06T16:08:35+0800 - Fix "host" can contain port #12 0.15 2018-02-15T22:35:36+0100 - Require JSON::Validator 2.03 0.14 2017-12-23T17:10:22+0100 - Add call_p() that returns a Mojo::Promise 0.13 2017-12-11T19:04:35+0100 - Add default coercion to input parameters - Add call() for calling "invalid" operationIds names - Fix "openapi" client when printing data structures #8 0.12 2017-12-09T11:15:28+0100 - Bumping dependencies 0.11 2017-12-08T14:57:01+0100 - Fix openapi command will not try to decode empty strings #5 Contributor: Ed J 0.10 2017-11-27T18:01:50+0100 - Can resolve $ref in spec - Add support for inheriting "parameters" 0.09 2017-10-04T18:44:23+0200 - Add pre_processor() which can be used to manipulate the request #2 0.08 2017-10-03T11:02:55+0200 - Improved building default base_url() #1 0.07 2017-08-22T21:05:46+0200 - Try to make reading from STDIN a bit more robust 0.06 2017-08-21T09:14:23+0200 - Add validator() method - Add "mojo openapi spec.json -I operationId" - Will list available operationIds when no operationId is specified 0.05 2017-08-20T11:18:54+0200 - Forgot to bump JSON::Validator to 1.01 0.04 2017-08-19T23:05:26+0200 - Fix "mojo openapi" against online resources 0.03 2017-08-19T22:39:33+0200 - Add "mojo openapi" command - Add support for running against local app - Removed local_app() method 0.02 2017-08-19T10:59:07+0200 - Fix validation of body parameters 0.01 2017-08-18T19:30:32+0200 - Converted Swagger2::Client to OpenAPI::Client - Marked as EXPERIMENTAL OpenAPI-Client-1.07/MANIFEST000644 000765 000024 00000001020 14326233320 016377 0ustar00jhthorsenstaff000000 000000 Changes lib/Mojolicious/Command/openapi.pm lib/OpenAPI/Client.pm Makefile.PL MANIFEST This list of files t/00-project.t t/base-url.t t/body-validation.t t/client.t t/command-local-with-ref.t t/command-local.t t/command-remote.t t/command.t t/json-request.t t/path-templating.t t/spec.json t/spec/dummy.json t/spec/with-external-ref.json t/spec/with-ref.json META.yml Module YAML meta-data (added by MakeMaker) META.json Module JSON meta-data (added by MakeMaker) OpenAPI-Client-1.07/t/000755 000765 000024 00000000000 14326233320 015520 5ustar00jhthorsenstaff000000 000000 OpenAPI-Client-1.07/META.yml000664 000765 000024 00000002552 14326233320 016534 0ustar00jhthorsenstaff000000 000000 --- abstract: 'A client for talking to an Open API powered server' author: - 'Jan Henning Thorsen ' build_requires: ExtUtils::MakeMaker: '0' Test::More: '0.88' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 0 generated_by: 'ExtUtils::MakeMaker version 7.62, CPAN::Meta::Converter version 2.150010' license: artistic_2 meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: OpenAPI-Client no_index: directory: - t - inc - examples - t requires: JSON::Validator: '5.09' Mojolicious::Plugin::OpenAPI: '5.05' perl: '5.016' resources: IRC: url: irc://irc.libera.chat/#convos web: https://web.libera.chat/#convos bugtracker: https://github.com/jhthorsen/openapi-client/issues homepage: https://github.com/jhthorsen/openapi-client license: http://www.opensource.org/licenses/artistic-license-2.0 repository: https://github.com/jhthorsen/openapi-client.git version: '1.07' x_contributors: - 'Clive Holloway ' - 'Ed J ' - 'Jan Henning Thorsen ' - 'Mohammad S Anwar ' - 'Reneeb ' - 'Roy Storey ' - 'Veesh Goldman ' x_serialization_backend: 'CPAN::Meta::YAML version 0.018' OpenAPI-Client-1.07/lib/000755 000765 000024 00000000000 14326233320 016023 5ustar00jhthorsenstaff000000 000000 OpenAPI-Client-1.07/Makefile.PL000644 000765 000024 00000003431 14307220306 017227 0ustar00jhthorsenstaff000000 000000 use 5.016; use strict; use warnings; use ExtUtils::MakeMaker; my $GITHUB_URL = 'https://github.com/jhthorsen/openapi-client'; my %WriteMakefileArgs = ( NAME => 'OpenAPI::Client', AUTHOR => 'Jan Henning Thorsen ', LICENSE => 'artistic_2', ABSTRACT_FROM => 'lib/OpenAPI/Client.pm', VERSION_FROM => 'lib/OpenAPI/Client.pm', TEST_REQUIRES => {'Test::More' => '0.88'}, PREREQ_PM => {'JSON::Validator' => '5.09', 'Mojolicious::Plugin::OpenAPI' => '5.05'}, META_MERGE => { 'dynamic_config' => 0, 'meta-spec' => {version => 2}, 'no_index' => {directory => [qw(examples t)]}, 'prereqs' => {runtime => {requires => {perl => '5.016'}}}, 'resources' => { bugtracker => {web => "$GITHUB_URL/issues"}, homepage => $GITHUB_URL, license => ['http://www.opensource.org/licenses/artistic-license-2.0'], repository => {type => 'git', url => "$GITHUB_URL.git", web => $GITHUB_URL}, x_IRC => {url => 'irc://irc.libera.chat/#convos', web => 'https://web.libera.chat/#convos'}, }, 'x_contributors' => [ 'Clive Holloway ', 'Ed J ', 'Jan Henning Thorsen ', 'Mohammad S Anwar ', 'Reneeb ', 'Roy Storey ', 'Veesh Goldman ', ], }, test => {TESTS => (-e 'META.yml' ? 't/*.t' : 't/*.t xt/*.t')}, ); unless (eval { ExtUtils::MakeMaker->VERSION('6.63_03') }) { my $test_requires = delete $WriteMakefileArgs{TEST_REQUIRES}; @{$WriteMakefileArgs{PREREQ_PM}}{keys %$test_requires} = values %$test_requires; } WriteMakefile(%WriteMakefileArgs); OpenAPI-Client-1.07/META.json000664 000765 000024 00000004162 14326233320 016703 0ustar00jhthorsenstaff000000 000000 { "abstract" : "A client for talking to an Open API powered server", "author" : [ "Jan Henning Thorsen " ], "dynamic_config" : 0, "generated_by" : "ExtUtils::MakeMaker version 7.62, CPAN::Meta::Converter version 2.150010", "license" : [ "artistic_2" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "OpenAPI-Client", "no_index" : { "directory" : [ "t", "inc", "examples", "t" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "JSON::Validator" : "5.09", "Mojolicious::Plugin::OpenAPI" : "5.05", "perl" : "5.016" } }, "test" : { "requires" : { "Test::More" : "0.88" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/jhthorsen/openapi-client/issues" }, "homepage" : "https://github.com/jhthorsen/openapi-client", "license" : [ "http://www.opensource.org/licenses/artistic-license-2.0" ], "repository" : { "type" : "git", "url" : "https://github.com/jhthorsen/openapi-client.git", "web" : "https://github.com/jhthorsen/openapi-client" }, "x_IRC" : { "url" : "irc://irc.libera.chat/#convos", "web" : "https://web.libera.chat/#convos" } }, "version" : "1.07", "x_contributors" : [ "Clive Holloway ", "Ed J ", "Jan Henning Thorsen ", "Mohammad S Anwar ", "Reneeb ", "Roy Storey ", "Veesh Goldman " ], "x_serialization_backend" : "JSON::PP version 4.06" } OpenAPI-Client-1.07/lib/OpenAPI/000755 000765 000024 00000000000 14326233320 017256 5ustar00jhthorsenstaff000000 000000 OpenAPI-Client-1.07/lib/Mojolicious/000755 000765 000024 00000000000 14326233320 020317 5ustar00jhthorsenstaff000000 000000 OpenAPI-Client-1.07/lib/Mojolicious/Command/000755 000765 000024 00000000000 14326233320 021675 5ustar00jhthorsenstaff000000 000000 OpenAPI-Client-1.07/lib/Mojolicious/Command/openapi.pm000644 000765 000024 00000013553 14326233317 023703 0ustar00jhthorsenstaff000000 000000 package Mojolicious::Command::openapi; use Mojo::Base 'Mojolicious::Command'; use OpenAPI::Client; use Mojo::JSON qw(encode_json decode_json j); use Mojo::Util qw(encode getopt); use constant YAML => eval 'require YAML::XS;1'; use constant REPLACE => $ENV{JSON_VALIDATOR_REPLACE} // 1; sub _say { length && say encode('UTF-8', $_) for @_ } sub _warn { warn @_ } has description => 'Perform Open API requests'; has usage => sub { shift->extract_usage . "\n" }; has _client => undef; sub run { my ($self, @args) = @_; my %ua; getopt \@args, 'i|inactivity-timeout=i' => sub { $ua{inactivity_timeout} = $_[1] }, 'I|information' => \my $info, 'o|connect-timeout=i' => sub { $ua{connect_timeout} = $_[1] }, 'p|parameter=s' => \my %parameters, 'c|content=s' => \my $content, 'S|response-size=i' => sub { $ua{max_response_size} = $_[1] }, 'v|verbose' => \my $verbose; # Read body from STDIN vec(my $r, fileno(STDIN), 1) = 1; $content //= !-t STDIN && select($r, undef, undef, 0) ? join '', : undef; my @client_args = (shift @args); my $op = shift @args; my $selector = shift @args // ''; die $self->usage unless $client_args[0]; if ($client_args[0] =~ m!^/! and !-e $client_args[0]) { $client_args[0] = Mojo::URL->new($client_args[0]); push @client_args, app => $self->app; } $self->_client(OpenAPI::Client->new(@client_args)); return $self->_info($op) if $info; return $self->_list unless $op; die qq(Unknown operationId "$op".\n) unless $self->_client->can($op); $self->_client->ua->proxy->detect unless $ENV{OPENAPI_NO_PROXY}; $self->_client->ua->$_($ua{$_}) for keys %ua; $self->_client->ua->on( start => sub { my ($ua, $tx) = @_; weaken $tx; $tx->res->content->on(body => sub { _warn _header($tx->req), _header($tx->res) }) if $verbose; } ); my $tx = $self->_client->call($op => \%parameters, $content ? (json => decode_json $content) : ()); if ($tx->error and $tx->error->{message} eq 'Invalid input') { _warn _header($tx->req), _header($tx->res) if $verbose; } return _json($tx->res->json, $selector) if !length $selector || $selector =~ m!^/!; return _say $tx->res->dom->find($selector)->each; } sub _header { $_[0]->build_start_line, $_[0]->headers->to_string, "\n\n" } sub _info { my ($self, $op) = @_; local $YAML::XS::Boolean = 'JSON::PP'; unless ($op) { my $op_spec = $self->_client->validator->bundle->data; return _say YAML ? YAML::XS::Dump($op_spec) : Mojo::Util::dumper($op_spec); } my ($schema, $op_spec) = ($self->_client->validator); for my $route ($schema->routes->each) { next if !$route->{operation_id} or $route->{operation_id} ne $op; $op_spec = $schema->get(['paths', @$route{qw(path method)}]); } return _warn qq(Could not find the given operationId "$op".\n) unless $op_spec; local $YAML::XS::Boolean = 'JSON::PP'; return _say YAML ? YAML::XS::Dump($op_spec) : Mojo::Util::dumper($op_spec); } sub _json { return unless defined(my $data = Mojo::JSON::Pointer->new(shift)->get(shift)); return _say $data unless ref $data eq 'HASH' || ref $data eq 'ARRAY'; _say Mojo::Util::decode('UTF-8', encode_json $data); } sub _list { my $self = shift; _warn "--- Operations for @{[$self->_client->base_url]}\n"; $_->{operation_id} && _say $_->{operation_id} for $self->_client->validator->routes->each; } 1; =encoding utf8 =head1 NAME Mojolicious::Command::openapi - Perform Open API requests =head1 SYNOPSIS Usage: APPLICATION openapi SPECIFICATION OPERATION "{ARGUMENTS}" [SELECTOR|JSON-POINTER] # Fetch /api from myapp.pl and list available operationId ./myapp.pl openapi /api # Dump the whole specification or for an operationId ./myapp.pl openapi /api -I ./myapp.pl openapi /api -I addPet # Run an operation against a local application ./myapp.pl openapi /api listPets /pets/0 # Run an operation against a local application, with body parameter ./myapp.pl openapi /api addPet -c '{"name":"pluto"}' echo '{"name":"pluto"} | ./myapp.pl openapi /api addPet # Run an operation with parameters mojo openapi spec.json listPets -p limit=10 -p type=dog # Run against local or online specifications mojo openapi /path/to/spec.json listPets mojo openapi http://service.example.com/api.json listPets Options: -h, --help Show this summary of available options -c, --content JSON content, with body parameter data -i, --inactivity-timeout Inactivity timeout, defaults to the value of MOJO_INACTIVITY_TIMEOUT or 20 -I, --information [operationId] Dump the specification about a given operationId or the whole spec. YAML::XS is preferred if available. -o, --connect-timeout Connect timeout, defaults to the value of MOJO_CONNECT_TIMEOUT or 10 -p, --parameter Specify multiple header, path, or query parameter -S, --response-size Maximum response size in bytes, defaults to 2147483648 (2GB) -v, --verbose Print request and response headers to STDERR =head1 DESCRIPTION L is a command line interface for L. Not that this implementation is currently EXPERIMENTAL! Feedback is appreciated. =head1 ATTRIBUTES =head2 description $str = $command->description; =head2 usage $str = $command->usage; =head1 METHODS =head2 run $command->run(@ARGV); Run this command. =head1 SEE ALSO L. =cut OpenAPI-Client-1.07/lib/OpenAPI/Client.pm000644 000765 000024 00000033313 14326233317 021043 0ustar00jhthorsenstaff000000 000000 package OpenAPI::Client; use Mojo::EventEmitter -base; use Carp (); use JSON::Validator; use Mojo::UserAgent; use Mojo::Promise; use Scalar::Util qw(blessed); use constant DEBUG => $ENV{OPENAPI_CLIENT_DEBUG} || 0; our $VERSION = '1.07'; has base_url => sub { my $self = shift; my $validator = $self->validator; my $url = $validator->can('base_url') ? $validator->base_url->clone : Mojo::URL->new; $url->scheme('http') unless $url->scheme; $url->host('localhost') unless $url->host; return $url; }; has ua => sub { Mojo::UserAgent->new }; sub call { my ($self, $op) = (shift, shift); my $code = $self->can($op) or Carp::croak('[OpenAPI::Client] No such operationId'); return $self->$code(@_); } sub call_p { my ($self, $op) = (shift, shift); my $code = $self->can("${op}_p") or return Mojo::Promise->reject('[OpenAPI::Client] No such operationId'); return $self->$code(@_); } sub new { my ($parent, $specification) = (shift, shift); my $attrs = @_ == 1 ? shift : {@_}; my $class = $parent->_url_to_class($specification); $parent->_generate_class($class, $specification, $attrs) unless $class->isa($parent); my $self = $class->SUPER::new($attrs); $self->base_url(Mojo::URL->new($self->{base_url})) if $self->{base_url} and !blessed $self->{base_url}; $self->ua->transactor->name('Mojo-OpenAPI (Perl)') unless $self->{ua}; if (my $app = delete $self->{app}) { $self->base_url->host(undef)->scheme(undef)->port(undef); $self->ua->server->app($app); } return $self; } sub validator { Carp::confess("validator() is not defined for $_[0]") } sub _generate_class { my ($parent, $class, $specification, $attrs) = @_; my $jv = JSON::Validator->new; $jv->coerce($attrs->{coerce} // 'booleans,numbers,strings'); $jv->store->ua->server->app($attrs->{app}) if $attrs->{app}; my $schema = $jv->schema($specification)->schema; die "Invalid schema: $specification has the following errors:\n", join "\n", @{$schema->errors} if @{$schema->errors}; eval <<"HERE" or Carp::confess("package $class: $@"); package $class; use Mojo::Base '$parent'; 1; HERE Mojo::Util::monkey_patch($class => validator => sub {$schema}); return unless $schema->can('routes'); # In case it is not an OpenAPI spec for my $route ($schema->routes->each) { next unless $route->{operation_id}; warn "[$class] Add method $route->{operation_id}() for $route->{method} $route->{path}\n" if DEBUG; $class->_generate_method_bnb($route->{operation_id} => $route); $class->_generate_method_p("$route->{operation_id}_p" => $route); } } sub _generate_method_bnb { my ($class, $method_name, $route) = @_; Mojo::Util::monkey_patch $class => $method_name => sub { my $cb = ref $_[-1] eq 'CODE' ? pop : undef; my $self = shift; my $tx = $self->_build_tx($route, @_); if ($tx->error) { return $tx unless $cb; Mojo::IOLoop->next_tick(sub { $self->$cb($tx) }); return $self; } return $self->ua->start($tx) unless $cb; $self->ua->start($tx, sub { $self->$cb($_[1]) }); return $self; }; } sub _generate_method_p { my ($class, $method_name, $route) = @_; Mojo::Util::monkey_patch $class => $method_name => sub { my $self = shift; my $tx = $self->_build_tx($route, @_); return $self->ua->start_p($tx) unless my $err = $tx->error; return Mojo::Promise->new->reject($err->{message}) unless $err->{code}; return Mojo::Promise->new->reject('WebSocket handshake failed') if $tx->req->is_handshake && !$tx->is_websocket; return Mojo::Promise->new->resolve($tx); }; } sub _build_tx { my ($self, $route, $params, %content) = @_; my $v = $self->validator; my $url = $self->base_url->clone; my ($tx, %headers); push @{$url->path}, map { local $_ = $_; s,\{([-\w]+)\},{$params->{$1}//''},ge; $_ } grep {length} split '/', $route->{path}; my @errors = $self->validator->validate_request( [@$route{qw(method path)}], { body => sub { my ($name, $param) = @_; if (exists $params->{$name}) { $content{json} = $params->{$name}; } else { for ('body', sort keys %{$self->ua->transactor->generators}) { next unless exists $content{$_}; $params->{$name} = $content{$_}; last; } } return {exists => $params->{$name}, value => $params->{$name}}; }, formData => sub { my ($name, $param) = @_; my $value = _param_as_array($name => $params); $content{form}{$name} = $params->{$name}; return {exists => !!@$value, value => $value}; }, header => sub { my ($name, $param) = @_; my $value = _param_as_array($name => $params); $headers{$name} = $value; return {exists => !!@$value, value => $value}; }, path => sub { my ($name, $param) = @_; return {exists => exists $params->{$name}, value => $params->{$name}}; }, query => sub { my ($name, $param) = @_; my $value = _param_as_array($name => $params); $url->query->param($name => _coerce_collection_format($value, $param)); return {exists => !!@$value, value => $value}; }, } ); if (@errors) { warn "[@{[ref $self]}] Validation for $route->{method} $url failed: @errors\n" if DEBUG; $tx = Mojo::Transaction::HTTP->new; $tx->req->method(uc $route->{method}); $tx->req->url($url); $tx->res->headers->content_type('application/json'); $tx->res->body(Mojo::JSON::encode_json({errors => \@errors})); $tx->res->code(400)->message($tx->res->default_message); $tx->res->error({message => 'Invalid input', code => 400}); } else { warn "[@{[ref $self]}] Validation for $route->{method} $url was successful\n" if DEBUG; $tx = $self->ua->build_tx($route->{method}, $url, \%headers, defined $content{body} ? $content{body} : %content); } $tx->req->env->{operationId} = $route->{operation_id}; $self->emit(after_build_tx => $tx); return $tx; } sub _coerce_collection_format { my ($value, $param) = @_; my $format = $param->{collectionFormat} || (+($param->{type} // '') eq 'array' ? 'csv' : ''); return $value if !$format or $format eq 'multi'; return join "|", @$value if $format eq 'pipes'; return join " ", @$value if $format eq 'ssv'; return join "\t", @$value if $format eq 'tsv'; return join ",", @$value; } sub _param_as_array { my ($name, $params) = @_; return !exists $params->{$name} ? [] : ref $params->{$name} eq 'ARRAY' ? $params->{$name} : [$params->{$name}]; } sub _url_to_class { my ($self, $package) = @_; $package =~ s!^\w+?://!!; $package =~ s!\W!_!g; $package = Mojo::Util::md5_sum($package) if length $package > 110; # 110 is a bit random, but it cannot be too long return "$self\::$package"; } 1; =encoding utf8 =head1 NAME OpenAPI::Client - A client for talking to an Open API powered server =head1 DESCRIPTION L can generating classes that can talk to an Open API server. This is done by generating a custom class, based on a Open API specification, with methods that transform parameters into a HTTP request. The generated class will perform input validation, so invalid data won't be sent to the server. Note that this implementation is currently EXPERIMENTAL, but unlikely to change! Feedback is appreciated. =head1 SYNOPSIS =head2 Open API specification The specification given to L need to point to a valid OpenAPI document. This document can be OpenAPI v2.x or v3.x, and it can be in either JSON or YAML format. Example: openapi: 3.0.1 info: title: Swagger Petstore version: 1.0.0 servers: - url: http://petstore.swagger.io/v1 paths: /pets: get: operationId: listPets ... C, C and the first item in C will be used to construct L. This can be altered at any time, if you need to send data to a custom endpoint. =head2 Client The OpenAPI API specification will be used to generate a sub-class of L where the "operationId", inside of each path definition, is used to generate methods: use OpenAPI::Client; $client = OpenAPI::Client->new("file:///path/to/api.json"); # Blocking $tx = $client->listPets; # Non-blocking $client = $client->listPets(sub { my ($client, $tx) = @_; }); # Promises $promise = $client->listPets_p->then(sub { my $tx = shift }); # With parameters $tx = $client->listPets({limit => 10}); See L for more information about what you can do with the C<$tx> object, but you often just want something like this: # Check for errors die $tx->error->{message} if $tx->error; # Extract data from the JSON responses say $tx->res->json->{pets}[0]{name}; Check out L, L and L for some of the most used methods in that class. =head1 CUSTOMIZATION =head2 Custom server URL If you want to request a different server than what is specified in the Open API document, you can change the L: # Pass on a Mojo::URL object to the constructor $base_url = Mojo::URL->new("http://example.com"); $client1 = OpenAPI::Client->new("file:///path/to/api.json", base_url => $base_url); # A plain string will be converted to a Mojo::URL object $client2 = OpenAPI::Client->new("file:///path/to/api.json", base_url => "http://example.com"); # Change the base_url after the client has been created $client3 = OpenAPI::Client->new("file:///path/to/api.json"); $client3->base_url->host("other.example.com"); =head2 Custom content You can send XML or any format you like, but this require you to add a new "generator": use Your::XML::Library "to_xml"; $client->ua->transactor->add_generator(xml => sub { my ($t, $tx, $data) = @_; $tx->req->body(to_xml $data); return $tx; }); $client->addHero({}, xml => {name => "Supergirl"}); See L for more details. =head1 EVENTS =head2 after_build_tx $client->on(after_build_tx => sub { my ($client, $tx) = @_ }) This event is emitted after a L object has been built, just before it is passed on to the L. Note that all validation has already been run, so alternating the C<$tx> too much, might cause an invalid request on the server side. A special L variable will be set, to reference the operationId: $tx->req->env->{operationId}; Note that this usage of C is currently EXPERIMENTAL: =head1 ATTRIBUTES =head2 base_url $base_url = $client->base_url; Returns a L object with the base URL to the API. The default value comes from C, C and C in the OpenAPI v2 specification or from C in the OpenAPI v3 specification. =head2 ua $ua = $client->ua; Returns a L object which is used to execute requests. =head1 METHODS =head2 call $tx = $client->call($operationId => \%params, %content); $client = $client->call($operationId => \%params, %content, sub { my ($client, $tx) = @_; }); Used to either call an C<$operationId> that has an "invalid name", such as "list pets" instead of "listPets" or to call an C<$operationId> that you are unsure is supported yet. If it is not, an exception will be thrown, matching text "No such operationId". C<$operationId> is the name of the resource defined in the L. C<$params> is optional, but must be a hash ref, where the keys should match a named parameter in the L. C<%content> is used for the body of the request, where the key need to be either "body" or a matching L. Example: $client->addHero({}, body => "Some data"); $client->addHero({}, json => {name => "Supergirl"}); C<$tx> is a L object. =head2 call_p $promise = $client->call_p($operationId => $params, %content); $promise->then(sub { my $tx = shift }); As L above, but returns a L object. =head2 new $client = OpenAPI::Client->new($specification, \%attributes); $client = OpenAPI::Client->new($specification, %attributes); Returns an object of a generated class, with methods generated from the Open API specification located at C<$specification>. See L for valid versions of C<$specification>. Note that the class is cached by perl, so loading a new specification from the same URL will not generate a new class. Extra C<%attributes>: =over 2 =item * app Specifying an C is useful when running against a local L instance. =item * coerce See L. Default to "booleans,numbers,strings". =back =head2 validator $validator = $client->validator; $validator = $class->validator; Returns a L object for a generated class. Note that this is a global variable, so changing the object will affect all instances returned by L. =head1 COPYRIGHT AND LICENSE Copyright (C) 2017-2021, Jan Henning Thorsen This program is free software, you can redistribute it and/or modify it under the terms of the Artistic License version 2.0. =head1 AUTHORS =head2 Project Founder Jan Henning Thorsen - C =head2 Contributors =over 2 =item * Clive Holloway =item * Ed J =item * Jan Henning Thorsen =item * Jan Henning Thorsen =item * Mohammad S Anwar =item * Reneeb =item * Roy Storey =item * Veesh Goldman =back =cut OpenAPI-Client-1.07/t/spec.json000644 000765 000024 00000001343 14245537462 017364 0ustar00jhthorsenstaff000000 000000 { "swagger": "2.0", "info": { "version": "0.8", "title": "Test client spec" }, "schemes": [ "http" ], "host": "localhost", "basePath": "/v1", "paths": { "/pets/{type}": { "get": { "operationId": "listPets", "x-mojo-to": "listPets", "parameters": [ { "in": "path", "name": "type", "type": "string", "required": true }, { "$ref": "#/parameters/p" } ], "responses": { "200": { "description": "pet response", "schema": { "$ref": "#/definitions/r" } } } } } }, "definitions": { "r": { "type": "array" } }, "parameters": { "p": { "in": "query", "name": "p", "type": "integer" } } } OpenAPI-Client-1.07/t/client.t000644 000765 000024 00000016215 14277027607 017206 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Mojo::JSON 'true'; use OpenAPI::Client; use Test::More; use Mojolicious::Lite; app->log->level('error') unless $ENV{HARNESS_IS_VERBOSE}; my $i = 0; get '/pets/:type' => sub { $i++; my $c = shift->openapi->valid_input or return; $c->render(openapi => [{type => $c->param('type')}]); }, 'listPetsByType'; get '/pets' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => [$c->req->params->to_hash]); }, 'list pets'; get '/collection-format' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => [$c->req->params->to_hash]); }, 'collectionFormat'; post '/pets' => sub { my $c = shift->openapi->valid_input or return; my $res = $c->req->body_params->to_hash; $res->{dummy} = true if $c->req->headers->header('x-dummy'); $c->render(openapi => $res); }, 'addPet'; plugin OpenAPI => {url => 'data://main/test.json'}; is(OpenAPI::Client->new('data://main/test.json')->base_url, 'http://api.example.com:3000/v1', 'base_url'); is(OpenAPI::Client->new('data://main/test.json')->base_url->host, 'api.example.com', 'base_url host'); is(OpenAPI::Client->new('data://main/test.json')->base_url->port, '3000', 'base_url port'); my $client = OpenAPI::Client->new('data://main/test.json', app => app); my ($obj, $tx); is +ref($client), 'OpenAPI::Client::main_test_json', 'generated class'; isa_ok($client, 'OpenAPI::Client'); can_ok($client, 'addPet'); subtest 'subclassing' => sub { package OpenAPI::Child { use Mojo::Base 'OpenAPI::Client'; sub frobnicate { } } my $old_client = OpenAPI::Client->new('data://main/test.json'); my $new_client = OpenAPI::Child->new('data://main/test.json'); can_ok($new_client, 'frobnicate'); ok(!$old_client->can('frobnicate'), 'does not bleed over'); }; note 'Sync testing'; $tx = $client->listPetsByType; is $tx->res->code, 400, 'sync invalid listPetsByType'; is $tx->error->{message}, 'Invalid input', 'sync invalid message'; is $i, 0, 'invalid on client side'; $tx = $client->listPetsByType({type => 'dog', p => 12}); is $tx->res->code, 200, 'sync listPetsByType'; is $tx->req->url->query->param('p'), 12, 'sync listPetsByType p=12'; is $i, 1, 'one request'; $tx = $client->addPet({age => '5', type => 'dog', name => 'Pluto', 'x-dummy' => true}); is $tx->res->code, 200, 'coercion for "age":"5" works'; ok $tx->remote_address, 'server side response'; $tx = $client->addPet({}); is $tx->req->method, 'POST', 'correct method on invalid input'; ok !$tx->remote_address, 'client side error'; note 'Async testing'; $i = 0; is $client->listPetsByType(sub { ($obj, $tx) = @_; Mojo::IOLoop->stop }), $client, 'async request'; Mojo::IOLoop->start; is $obj, $client, 'got client in callback'; is $tx->res->code, 400, 'invalid listPetsByType'; is $tx->error->{message}, 'Invalid input', 'sync invalid message'; is $i, 0, 'invalid on client side'; note 'Promise testing'; my $p = $client->listPetsByType_p->then(sub { $tx = shift }); $tx = undef; $p->wait; is $tx->res->code, 400, 'invalid listPetsByType'; is $tx->error->{message}, 'Invalid input', 'sync invalid message'; is $i, 0, 'invalid on client side'; note 'call()'; $tx = $client->call('list pets', {page => 2}); is_deeply $tx->res->json, [{page => 2}], 'call(list pets)'; eval { $tx = $client->call('nope') }; like $@, qr{No such operationId.*client\.t}, 'call(nope)'; # this approach from https://metacpan.org/source/SRI/Mojolicious-7.59/t/mojo/promise.t and user_agent.t note 'call_p()'; my $promise = $client->call_p('list pets', {page => 2}); my (@results, @errors); $promise->then(sub { @results = @_ }, sub { @errors = @_ }); $promise->wait; is_deeply $results[0]->res->json, [{page => 2}], 'call_p(list pets)'; is_deeply \@errors, [], 'promise not rejected'; note 'call_p() rejecting'; $promise = $client->call_p('list all pets', {page => 2}); (@results, @errors) = (); $promise->then(sub { @results = @_ }, sub { @errors = @_ }); $promise->wait; is_deeply \@results, [], 'call_p(list all pets) does not exist'; is_deeply \@errors, ['[OpenAPI::Client] No such operationId'], 'promise got rejected'; note 'boolean'; my $err; $client->listPetsByType_p({type => 'cat', is_cool => true})->then(sub { $tx = shift }, sub { $err = shift })->wait; is $tx->res->code, 200, 'accepted is_cool=true'; is $tx->req->url->query->to_string, 'is_cool=1', 'is_cool in query parameter'; subtest 'collectionFormat' => sub { $client->collectionFormat_p({csv => [42, 43, 44]})->then(sub { $tx = shift }, sub { $err = shift })->wait; is $tx->res->code, 200, 'accepted' or diag $tx->res->text; like $tx->req->url->query->to_string, qr{^csv=42[^4]+43[^4]+44$}, 'csv in query parameter'; }; done_testing; __DATA__ @@ test.json { "swagger": "2.0", "info": { "version": "0.8", "title": "Test client spec" }, "schemes": [ "http" ], "host": "api.example.com:3000", "basePath": "/v1", "paths": { "x-whatever": [], "/pets": { "x-whatever": [], "parameters": [ { "$ref": "#/parameters/name" } ], "get": { "operationId": "list pets", "parameters": [ { "in": "query", "name": "page", "type": "integer" } ], "responses": { "200": { "description": "pets", "schema": { "type": "array" } } } }, "post": { "x-whatever": [], "operationId": "addPet", "parameters": [ { "in": "header", "name": "x-dummy", "type": "boolean" }, { "in": "formData", "name": "age", "type": "integer" }, { "in": "formData", "name": "type", "type": "string", "required": true } ], "responses": { "200": { "description": "pet response", "schema": { "type": "object" } } } } }, "/pets/{type}": { "get": { "operationId": "listPetsByType", "parameters": [ { "in": "query", "name": "is_cool", "type": "boolean" }, { "in": "path", "name": "type", "type": "string", "required": true }, { "$ref": "#/parameters/p" } ], "responses": { "200": { "description": "pet response", "schema": { "$ref": "#/definitions/ok" } } } } }, "/collection-format": { "get": { "operationId": "collectionFormat", "parameters": [ {"in": "query", "name": "csv", "type": "array", "items": {"type": "number"}} ], "responses": { "200": { "description": "default collectionFormat when type is array", "schema": { "$ref": "#/definitions/ok" } } } } } }, "parameters": { "name": { "in": "formData", "name": "name", "type": "string" }, "p": { "in": "query", "name": "p", "type": "integer" } }, "definitions": { "ok": { "type": "array" } } } OpenAPI-Client-1.07/t/spec/000755 000765 000024 00000000000 14326233320 016452 5ustar00jhthorsenstaff000000 000000 OpenAPI-Client-1.07/t/command-local-with-ref.t000644 000765 000024 00000001653 14245537462 022161 0ustar00jhthorsenstaff000000 000000 use lib '.'; use Mojo::File 'path'; use Mojolicious; use OpenAPI::Client; use Test::More; my $spec = path(qw(t spec with-ref.json))->to_abs; plan skip_all => 'Cannot read spec' unless -r $spec; $ENV{MOJO_LOG_LEVEL} //= 'warn'; eval { my $app = Mojolicious->new; my $oc; $app->routes->get(sub { my $c = shift }, 'dummy'); $app->plugin(OpenAPI => {default_response_codes => [], spec => $spec}); $app->plugin(OpenAPI => {default_response_codes => [], spec => path(qw(t spec with-external-ref.json))->to_abs}); $oc = OpenAPI::Client->new('/api', app => $app); ok $oc, 'OpenAPI::Client loaded bundled spec' or diag $@; ok $oc->validator->get('/responses/error'), 'responses/error is still there'; $oc = OpenAPI::Client->new('/ext', app => $app); ok $oc, 'OpenAPI::Client loaded bundled spec' or diag $@; } or do { # Getting "Service Unavailable" from some of the cpantesters plan skip_all => $@; }; done_testing; OpenAPI-Client-1.07/t/base-url.t000644 000765 000024 00000002306 14245537462 017436 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use OpenAPI::Client; use Test::More; subtest default => sub { my $client = OpenAPI::Client->new('data://main/test.json'); isa_ok($client->base_url, 'Mojo::URL'); is($client->base_url, 'http://localhost/', 'base_url'); }; subtest constructor => sub { my $client = OpenAPI::Client->new('data://main/test.json', base_url => 'https://example.com/v1/'); isa_ok($client->base_url, 'Mojo::URL'); is($client->base_url, 'https://example.com/v1/', 'base_url'); $client = OpenAPI::Client->new('data://main/test.json', base_url => Mojo::URL->new('https://example.com/v1/')); isa_ok($client->base_url, 'Mojo::URL'); is($client->base_url, 'https://example.com/v1/', 'base_url'); }; subtest attribute => sub { my $client = OpenAPI::Client->new('data://main/test.json'); isa_ok($client->base_url, 'Mojo::URL'); is($client->base_url, 'http://localhost/', 'base_url'); $client->base_url->host('other.example.com')->path('/test'); isa_ok($client->base_url, 'Mojo::URL'); is($client->base_url, 'http://other.example.com/test', 'base_url'); }; done_testing; __DATA__ @@ test.json { "swagger": "2.0", "info": {"version": "0.8", "title": "Test default base_url"}, "paths": {} } OpenAPI-Client-1.07/t/body-validation.t000644 000765 000024 00000004735 14245537462 021021 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use OpenAPI::Client; use Test::More; use Mojo::JSON 'true'; use Mojolicious::Lite; app->log->level('error') unless $ENV{HARNESS_IS_VERBOSE}; my $i = 0; post '/user/login' => sub { $i++; my $c = shift->openapi->valid_input or return; return $c->render(openapi => $c->req->json); }, 'loginUser'; plugin OpenAPI => {url => 'data://main/test.json'}; my $client = OpenAPI::Client->new('data://main/test.json', app => app); my ($tx, $req); $client->once(after_build_tx => sub { $req = pop->req }); $tx = $client->loginUser; is $tx->req, $req, 'after_build_tx emitted tx'; is $tx->res->code, 400, 'invalid loginUser' or diag explain($tx->res->json); is $tx->error->{message}, 'Invalid input', 'invalid message'; $tx = $client->loginUser({body => {email => 'superman@example.com', password => 's3cret'}}); is $tx->res->code, 200, 'valid loginUser' or diag explain($tx->res->json); is $tx->res->json->{email}, 'superman@example.com', 'valid return'; $tx = $client->loginUser({body => {password => 's3cret'}}); is $tx->res->code, 400, 'missing email' or diag explain($tx->res->json); is $tx->error->{message}, 'Invalid input', 'invalid message'; $client->once(after_build_tx => sub { pop->req->headers->header('X-Secret' => 'supersecret') }); $tx = $client->loginUser; is $tx->req->env->{operationId}, 'loginUser', 'got operationId in env'; is $tx->req->headers->header('X-Secret'), 'supersecret', 'modified request'; is $i, 1, 'only sent data to server once'; done_testing; __DATA__ @@ test.json { "swagger": "2.0", "info": { "version": "0.8", "title": "Test client spec" }, "schemes": [ "http" ], "host": "api.example.com", "basePath": "/v1", "paths": { "/user/login": { "post": { "tags": [ "user" ], "summary": "Log in a user based on email and password.", "operationId": "loginUser", "parameters": [ { "name": "body", "in": "body", "required": true, "schema": { "type": "object", "required": ["email", "password"], "properties": { "email": { "type": "string", "format": "email", "description": "User email" }, "password": { "type": "string", "description": "User password" } } } } ], "responses": { "200": { "description": "User profile.", "schema": { "type": "object" } } } } } } } OpenAPI-Client-1.07/t/command-local.t000644 000765 000024 00000004275 14245537462 020441 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Mojo::File 'path'; use Mojolicious::Command::openapi; use Mojolicious; use Test::More; $ENV{MOJO_LOG_LEVEL} //= 'warn'; my @said; Mojo::Util::monkey_patch('Mojolicious::Command::openapi', _say => sub { push @said, @_ }); Mojo::Util::monkey_patch('Mojolicious::Command::openapi', _warn => sub { push @said, @_ }); eval { my $app = Mojolicious->new; $app->routes->post( '/pets' => sub { my $c = shift; my $res = $c->req->json; $res->{key} = $c->param('key'); $c->render(openapi => $res); } )->name('addPet'); $app->plugin('OpenAPI', {url => 'data://main/test.json'}); my $cmd = Mojolicious::Command::openapi->new(app => $app); $cmd->run('/v1'); like "@said", qr{addPet}, 'validated spec from local app'; @said = (); $cmd->run('/v1', 'addPet', -p => "key=abc", -c => '{"type":"dog"}'); like "@said", qr{"key":"abc"}, 'addPet with key'; like "@said", qr{"type":"dog"}, 'addPet with type'; @said = (); my $characters = qq(\x{88c5}\x{903c}\x{4e2d}); my $encoded = Mojo::Util::encode("UTF-8", $characters); $cmd->run('/v1', 'addPet', -p => "key=abc", -c => qq[{"type":"$encoded"}]); like "@said", qr{"key":"abc"}, 'addPet with key'; like "@said", qr{"type":"$characters"}, 'addPet with unicode'; } or do { # Getting "Service Unavailable" from some of the cpantesters plan skip_all => $@; }; done_testing; __DATA__ @@ test.json { "swagger": "2.0", "info": { "version": "0.8", "title": "Test client spec" }, "schemes": [ "http" ], "host": "api.example.com", "basePath": "/v1", "paths": { "/pets": { "post": { "operationId": "addPet", "parameters": [ { "in": "query", "name": "key", "type": "string" }, { "in": "body", "name": "body", "required": true, "schema": { "type": "object", "properties": { "type": { "type": "string", "description": "Type" } } } } ], "responses": { "200": { "description": "pet response", "schema": { "type": "object" } } } } } } } OpenAPI-Client-1.07/t/command.t000644 000765 000024 00000003141 14245537462 017340 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Mojo::File 'path'; use Test::More; use File::Temp qw(tempfile); use Mojolicious::Command::openapi; my $cmd = Mojolicious::Command::openapi->new; my @said; Mojo::Util::monkey_patch('Mojolicious::Command::openapi', _say => sub { push @said, @_ }); Mojo::Util::monkey_patch('Mojolicious::Command::openapi', _warn => sub { push @said, @_ }); like $cmd->description, qr{Perform Open API requests}, 'description'; like $cmd->usage, qr{APPLICATION openapi SPECIFICATION OPERATION}, 'usage'; eval { $cmd->run }; like $@, qr{APPLICATION openapi SPECIFICATION OPERATION}, 'no arguments'; @said = (); $cmd->run(path('t', 'spec.json')); like $said[0], qr{Operations for http://localhost/v1}, 'validated spec from command line'; like $said[1], qr{^listPets$}m, 'validated spec from command line'; @said = (); $cmd->run(path('t', 'spec.json'), -I => 'listPets'); like "@said", qr{pet response}, 'information about operation'; @said = (); $cmd->run(path('t', 'spec.json'), -I => 'unknown'); like "@said", qr{Could not find}, 'no information about operation'; @said = (); my ($fh, $filename) = tempfile; close $fh; # This is because under Docker, STDIN is !-t, readable immediately, # gives EOF. This simulates that open STDIN, '<', $filename; eval { $cmd->run(path('t', 'spec.json'), 'listPets') }; is $@, ''; like "@said", qr{Missing property}, 'missing property'; @said = (); $cmd->run(path('t', 'spec.json'), 'listPets', '-v'); like "@said", qr{400 Bad Request}, 'verbose'; @said = (); $cmd->run(path('t', 'spec.json'), 'listPets', '/errors/0/path'); like "@said", qr{^/type}, 'json path'; done_testing; OpenAPI-Client-1.07/t/00-project.t000644 000765 000024 00000002663 14326232256 017606 0ustar00jhthorsenstaff000000 000000 use strict; use warnings; use Test::More; use File::Find; plan skip_all => 'No such directory: .git' unless $ENV{TEST_ALL} or -d '.git'; plan skip_all => 'HARNESS_PERL_SWITCHES =~ /Devel::Cover/' if +($ENV{HARNESS_PERL_SWITCHES} || '') =~ /Devel::Cover/; for (qw( Test::CPAN::Changes::changes_file_ok+VERSION!4 Test::Pod::Coverage::pod_coverage_ok+VERSION!1 Test::Pod::pod_file_ok+VERSION!1 Test::Spelling::pod_file_spelling_ok+has_working_spellchecker!1 )) { my ($fqn, $module, $sub, $check, $skip_n) = /^((.*)::(\w+))\+(\w+)!(\d+)$/; next if eval "use $module;$module->$check"; no strict qw(refs); *$fqn = sub { SKIP: { skip "$sub(@_) ($module is required)", $skip_n } }; } my @files; find({wanted => sub { /\.pm$/ and push @files, $File::Find::name }, no_chdir => 1}, -e 'blib' ? 'blib' : 'lib'); plan tests => @files * 4 + 4; Test::Spelling::add_stopwords() if Test::Spelling->can('has_working_spellchecker') && Test::Spelling->has_working_spellchecker; for my $file (@files) { my $module = $file; $module =~ s,\.pm$,,; $module =~ s,.*/?lib/,,; $module =~ s,/,::,g; ok eval "use $module; 1", "use $module" or diag $@; Test::Pod::pod_file_ok($file); Test::Pod::Coverage::pod_coverage_ok($module, {also_private => [qr/^[A-Z_]+$/]}); Test::Spelling::pod_file_spelling_ok($file); } Test::CPAN::Changes::changes_file_ok(); __DATA__ Anwar Henning OpenAPI Reneeb Thorsen Veesh listPets operationId ua validator OpenAPI-Client-1.07/t/json-request.t000644 000765 000024 00000002711 14326233125 020350 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use OpenAPI::Client; use Test::More; use Mojolicious::Lite; app->log->level('error') unless $ENV{HARNESS_IS_VERBOSE}; post '/user' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => $c->validation->output); }, 'addUser'; plugin OpenAPI => {url => 'data://main/test.json'}; my $client = OpenAPI::Client->new('data://main/test.json', ua => app->ua); $client->base_url->host(undef)->scheme(undef)->port(undef); is $client->ua, app->ua, 'passed ua as argument, instead of app'; my $tx = $client->addUser({user => {username => 'superwoman'}}); is $tx->res->json->{user}{username}, 'superwoman', 'echo back username (b)'; like $tx->req->headers->header('Content-Type'), qr{application/json}, 'application/json'; undef $tx; my $p = $client->addUser_p({user => {username => 'supergirl'}})->then(sub { $tx = shift }); $p->wait; is $tx->res->json->{user}{username}, 'supergirl', 'echo back username (p)'; done_testing; __DATA__ @@ test.json { "swagger": "2.0", "info": { "version": "0.8", "title": "Test client spec" }, "schemes": [ "http" ], "host": "api.example.com", "basePath": "/v1", "paths": { "/user": { "post": { "operationId": "addUser", "parameters": [ {"in":"body","name":"user","schema":{}} ], "responses": { "200": { "description": "user", "schema": { "type": "object" } } } } } } } OpenAPI-Client-1.07/t/command-remote.t000644 000765 000024 00000000702 14245537462 020631 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use Mojo::File 'path'; use Mojolicious::Command::openapi; use Test::More; plan skip_all => 'TEST_ONLINE=1' unless $ENV{TEST_ONLINE}; my @said; Mojo::Util::monkey_patch('Mojolicious::Command::openapi', _say => sub { push @said, @_ }); my $cmd = Mojolicious::Command::openapi->new; $cmd->run('http://petstore.swagger.io/v2/swagger.json', 'getPetById', -p => 'petId=1'); like "@said", qr{"id":1}, 'getPetById'; done_testing; OpenAPI-Client-1.07/t/path-templating.t000644 000765 000024 00000002422 14245537462 021021 0ustar00jhthorsenstaff000000 000000 use Mojo::Base -strict; use OpenAPI::Client; use Test::More; use Mojolicious::Lite; app->log->level('error') unless $ENV{HARNESS_IS_VERBOSE}; post '/user/:user-name' => sub { my $c = shift->openapi->valid_input or return; $c->render(openapi => $c->validation->output); }, 'addUser'; plugin OpenAPI => {url => 'data://main/test.json'}; my $client = OpenAPI::Client->new('data://main/test.json', app => app); my $tx = $client->addUser({'user-name' => 'superwoman'}); is $tx->res->json->{'user-name'}, 'superwoman', 'echo back username (b)'; undef $tx; my $p = $client->addUser_p({'user-name' => 'supergirl'})->then(sub { $tx = shift }); $p->wait; is $tx->res->json->{'user-name'}, 'supergirl', 'echo back username (p)'; done_testing; __DATA__ @@ test.json { "swagger": "2.0", "info": { "version": "0.8", "title": "Test client spec" }, "schemes": [ "http" ], "host": "api.example.com", "basePath": "/v1", "paths": { "/user/{user-name}": { "post": { "operationId": "addUser", "parameters": [ { "in": "path", "name": "user-name", "required": true, "type": "string" } ], "responses": { "200": { "description": "user", "schema": { "type": "object" } } } } } } } OpenAPI-Client-1.07/t/spec/with-external-ref.json000644 000765 000024 00000001117 14245537462 022730 0ustar00jhthorsenstaff000000 000000 { "swagger": "2.0", "info": {"title": "t-app", "version": "0.1.0"}, "basePath": "/ext", "definitions": { "error": {"type": "object"} }, "paths": { "/dummy": { "get": { "operationId": "dummy", "responses": { "200": {"description": "", "schema": {"$ref": "../spec/dummy.json"}}, "201": {"description": "", "schema": {"$ref": "./dummy.json"}}, "202": {"description": "", "schema": {"$ref": "dummy.json"}}, "default": {"description": "Err", "schema":{"$ref": "#/definitions/error"}} } } } } } OpenAPI-Client-1.07/t/spec/dummy.json000644 000765 000024 00000000027 14245537462 020515 0ustar00jhthorsenstaff000000 000000 { "type": "object" } OpenAPI-Client-1.07/t/spec/with-ref.json000644 000765 000024 00000002057 14245537462 021114 0ustar00jhthorsenstaff000000 000000 { "swagger": "2.0", "info": { "license": { "name": "Apache License, Version 2.0", "url": "http://www.apache.org/licenses/LICENSE-2.0.html" }, "title": "t-app", "version": "0.1.0" }, "basePath": "/api", "produces": [ "application/json" ], "consumes": [ "application/json" ], "responses": { "error": { "description": "Self sufficient", "schema": { "type": "object", "additionalProperties": false, "required": [ "error" ], "properties": { "error": { "type": "string" } } } } }, "paths": { "/t": { "get": { "operationId": "listT", "x-mojo-to": "Controller::OpenAPI::T#list", "tags": [ "t" ], "responses": { "200": { "description": "Self sufficient", "schema": { "type": "array", "items": { "type": "string" } } }, "default": { "$ref": "#/responses/error" } } } } } }