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'; 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; has _ops => sub { my $client = shift->_client; my $paths = $client->validator->schema->get('/paths') || {}; my %ops; for my $path (keys %$paths) { for my $http_method (keys %{$paths->{$path}}) { my $op_spec = $paths->{$path}{$http_method}; $ops{$op_spec->{operationId}} = $op_spec if $op_spec->{operationId}; } } return \%ops; }; sub run { my ($self, @args) = @_; my ($info_about, %ua); getopt \@args, 'i|inactivity-timeout=i' => sub { $ua{inactivity_timeout} = $_[1] }, 'I|information=s' => \$info_about, '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 '', <STDIN> : undef; my @client_args = (shift @args); my $op = $info_about || shift @args; my $selector = shift @args // ''; die $self->usage unless $client_args[0]; push @client_args, app => $self->app if $client_args[0] =~ m!^/! and !-e $client_args[0]; $self->_client(OpenAPI::Client->new(@client_args)); return $self->_info($info_about) if $info_about; 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) = @_; my $op_spec = $self->_ops->{$op}; return _warn qq(Could not find the given operationId "$op".\n) unless $op_spec; 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"; _say $_ for sort keys %{$self->_ops}; } 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 validate the specification ./myapp.pl openapi /api # 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 <content> JSON content, with body parameter data -i, --inactivity-timeout <seconds> Inactivity timeout, defaults to the value of MOJO_INACTIVITY_TIMEOUT or 20 -o, --connect-timeout <seconds> Connect timeout, defaults to the value of MOJO_CONNECT_TIMEOUT or 10 -p, --parameter <name=value> Specify multiple header, path, or query parameter -S, --response-size <size> Maximum response size in bytes, defaults to 2147483648 (2GB) -v, --verbose Print request and response headers to STDERR =head1 DESCRIPTION L<Mojolicious::Command::openapi> is a command line interface for L<OpenAPI::Client>. Not that this implementation is currently EXPERIMENTAL! Feedback is appreciated. =head1 ATTRIBUTES =head2 description $str = $self->description; =head2 usage $str = $self->usage; =head1 METHODS =head2 run $get->run(@ARGV); Run this command. =head1 SEE ALSO L<OpenAPI::Client>. =cut