File Coverage

blib/lib/Mojolicious/Command/openapi.pm
Criterion Covered Total %
statement 65 74 87.8
branch 27 34 79.4
condition 17 31 54.8
subroutine 13 18 72.2
pod 1 1 100.0
total 123 158 77.8


line stmt bran cond sub pod time code
1             package Mojolicious::Command::openapi;
2 3     3   695931 use Mojo::Base 'Mojolicious::Command';
  3         39  
  3         22  
3              
4 3     3   64368 use OpenAPI::Client;
  3         10  
  3         47  
5 3     3   145 use Mojo::JSON qw(encode_json decode_json j);
  3         8  
  3         196  
6 3     3   17 use Mojo::Util qw(encode getopt);
  3         6  
  3         190  
7              
8 3     3   20 use constant YAML => eval 'require YAML::XS;1';
  3         6  
  3         264  
9 3   50 3   17 use constant REPLACE => $ENV{JSON_VALIDATOR_REPLACE} // 1;
  3         7  
  3         3749  
10              
11 0   0 0   0 sub _say { length && say encode('UTF-8', $_) for @_ }
12 0     0   0 sub _warn { warn @_ }
13              
14             has description => 'Perform Open API requests';
15             has usage => sub { shift->extract_usage . "\n" };
16              
17             has _client => undef;
18              
19             sub run {
20 10     10 1 368004 my ($self, @args) = @_;
21 10         27 my %ua;
22              
23             getopt \@args,
24 0     0   0 'i|inactivity-timeout=i' => sub { $ua{inactivity_timeout} = $_[1] },
25             'I|information' => \my $info,
26 0     0   0 'o|connect-timeout=i' => sub { $ua{connect_timeout} = $_[1] },
27             'p|parameter=s' => \my %parameters,
28             'c|content=s' => \my $content,
29 0     0   0 'S|response-size=i' => sub { $ua{max_response_size} = $_[1] },
30 10         142 'v|verbose' => \my $verbose;
31              
32             # Read body from STDIN
33 10         8223 vec(my $r, fileno(STDIN), 1) = 1;
34 10 50 33     484 $content //= !-t STDIN && select($r, undef, undef, 0) ? join '', : undef;
      66        
35              
36 10         42 my @client_args = (shift @args);
37 10         26 my $op = shift @args;
38 10   100     57 my $selector = shift @args // '';
39              
40 10 100       60 die $self->usage unless $client_args[0];
41              
42 9 100 66     256 if ($client_args[0] =~ m!^/! and !-e $client_args[0]) {
43 3         41 $client_args[0] = Mojo::URL->new($client_args[0]);
44 3         277 push @client_args, app => $self->app;
45             }
46              
47 9         155 $self->_client(OpenAPI::Client->new(@client_args));
48 9 100       1422 return $self->_info($op) if $info;
49 7 100       35 return $self->_list unless $op;
50 5 50       19 die qq(Unknown operationId "$op".\n) unless $self->_client->can($op);
51              
52 5 50       84 $self->_client->ua->proxy->detect unless $ENV{OPENAPI_NO_PROXY};
53 5         296 $self->_client->ua->$_($ua{$_}) for keys %ua;
54             $self->_client->ua->on(
55             start => sub {
56 2     2   6864 my ($ua, $tx) = @_;
57 2         22 weaken $tx;
58 2 50       8 $tx->res->content->on(body => sub { _warn _header($tx->req), _header($tx->res) }) if $verbose;
  0         0  
59             }
60 5         18 );
61              
62 5 100       120 my $tx = $self->_client->call($op => \%parameters, $content ? (json => decode_json $content) : ());
63 5 100 66     22963 if ($tx->error and $tx->error->{message} eq 'Invalid input') {
64 3 100       88 _warn _header($tx->req), _header($tx->res) if $verbose;
65             }
66              
67 5 50 66     188 return _json($tx->res->json, $selector) if !length $selector || $selector =~ m!^/!;
68 0         0 return _say $tx->res->dom->find($selector)->each;
69             }
70              
71 2     2   416 sub _header { $_[0]->build_start_line, $_[0]->headers->to_string, "\n\n" }
72              
73             sub _info {
74 2     2   8 my ($self, $op) = @_;
75 2         7 local $YAML::XS::Boolean = 'JSON::PP';
76              
77 2 50       8 unless ($op) {
78 0         0 my $op_spec = $self->_client->validator->bundle->data;
79 0         0 return _say YAML ? YAML::XS::Dump($op_spec) : Mojo::Util::dumper($op_spec);
80             }
81              
82 2         7 my ($schema, $op_spec) = ($self->_client->validator);
83 2         12 for my $route ($schema->routes->each) {
84 2 100 66     587 next if !$route->{operation_id} or $route->{operation_id} ne $op;
85 1         7 $op_spec = $schema->get(['paths', @$route{qw(path method)}]);
86             }
87              
88 2 100       115 return _warn qq(Could not find the given operationId "$op".\n) unless $op_spec;
89 1         3 local $YAML::XS::Boolean = 'JSON::PP';
90 1     1   81 return _say YAML ? YAML::XS::Dump($op_spec) : Mojo::Util::dumper($op_spec);
  1         12  
  1         2  
  1         102  
91             }
92              
93             sub _json {
94 5 50   5   1257 return unless defined(my $data = Mojo::JSON::Pointer->new(shift)->get(shift));
95 5 100 66     222 return _say $data unless ref $data eq 'HASH' || ref $data eq 'ARRAY';
96 4         19 _say Mojo::Util::decode('UTF-8', encode_json $data);
97             }
98              
99             sub _list {
100 2     2   4 my $self = shift;
101 2         6 _warn "--- Operations for @{[$self->_client->base_url]}\n";
  2         6  
102 2   33     479 $_->{operation_id} && _say $_->{operation_id} for $self->_client->validator->routes->each;
103             }
104              
105             1;
106              
107             =encoding utf8
108              
109             =head1 NAME
110              
111             Mojolicious::Command::openapi - Perform Open API requests
112              
113             =head1 SYNOPSIS
114              
115             Usage: APPLICATION openapi SPECIFICATION OPERATION "{ARGUMENTS}" [SELECTOR|JSON-POINTER]
116              
117             # Fetch /api from myapp.pl and list available operationId
118             ./myapp.pl openapi /api
119              
120             # Dump the whole specification or for an operationId
121             ./myapp.pl openapi /api -I
122             ./myapp.pl openapi /api -I addPet
123              
124             # Run an operation against a local application
125             ./myapp.pl openapi /api listPets /pets/0
126              
127             # Run an operation against a local application, with body parameter
128             ./myapp.pl openapi /api addPet -c '{"name":"pluto"}'
129             echo '{"name":"pluto"} | ./myapp.pl openapi /api addPet
130              
131             # Run an operation with parameters
132             mojo openapi spec.json listPets -p limit=10 -p type=dog
133              
134             # Run against local or online specifications
135             mojo openapi /path/to/spec.json listPets
136             mojo openapi http://service.example.com/api.json listPets
137              
138             Options:
139             -h, --help Show this summary of available options
140             -c, --content JSON content, with body parameter data
141             -i, --inactivity-timeout Inactivity timeout, defaults to the
142             value of MOJO_INACTIVITY_TIMEOUT or 20
143             -I, --information [operationId] Dump the specification about a given
144             operationId or the whole spec.
145             YAML::XS is preferred if available.
146             -o, --connect-timeout Connect timeout, defaults to the value
147             of MOJO_CONNECT_TIMEOUT or 10
148             -p, --parameter Specify multiple header, path, or
149             query parameter
150             -S, --response-size Maximum response size in bytes,
151             defaults to 2147483648 (2GB)
152             -v, --verbose Print request and response headers to
153             STDERR
154              
155             =head1 DESCRIPTION
156              
157             L is a command line interface for
158             L.
159              
160             Not that this implementation is currently EXPERIMENTAL! Feedback is
161             appreciated.
162              
163             =head1 ATTRIBUTES
164              
165             =head2 description
166              
167             $str = $command->description;
168              
169             =head2 usage
170              
171             $str = $command->usage;
172              
173             =head1 METHODS
174              
175             =head2 run
176              
177             $command->run(@ARGV);
178              
179             Run this command.
180              
181             =head1 SEE ALSO
182              
183             L.
184              
185             =cut