line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package OpenAPI::Client; |
2
|
11
|
|
|
11
|
|
1432922
|
use Mojo::EventEmitter -base; |
|
11
|
|
|
|
|
4647
|
|
|
11
|
|
|
|
|
106
|
|
3
|
|
|
|
|
|
|
|
4
|
9
|
|
|
9
|
|
1571
|
use Carp (); |
|
9
|
|
|
|
|
17
|
|
|
9
|
|
|
|
|
159
|
|
5
|
9
|
|
|
9
|
|
3986
|
use JSON::Validator; |
|
9
|
|
|
|
|
2061133
|
|
|
9
|
|
|
|
|
80
|
|
6
|
9
|
|
|
9
|
|
394
|
use Mojo::UserAgent; |
|
9
|
|
|
|
|
23
|
|
|
9
|
|
|
|
|
115
|
|
7
|
9
|
|
|
9
|
|
227
|
use Mojo::Promise; |
|
9
|
|
|
|
|
18
|
|
|
9
|
|
|
|
|
108
|
|
8
|
9
|
|
|
9
|
|
223
|
use Scalar::Util qw(blessed); |
|
9
|
|
|
|
|
14
|
|
|
9
|
|
|
|
|
580
|
|
9
|
|
|
|
|
|
|
|
10
|
9
|
|
50
|
9
|
|
52
|
use constant DEBUG => $ENV{OPENAPI_CLIENT_DEBUG} || 0; |
|
9
|
|
|
|
|
19
|
|
|
9
|
|
|
|
|
22100
|
|
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
our $VERSION = '1.06'; |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
has base_url => sub { |
15
|
|
|
|
|
|
|
my $self = shift; |
16
|
|
|
|
|
|
|
my $validator = $self->validator; |
17
|
|
|
|
|
|
|
my $url = $validator->can('base_url') ? $validator->base_url->clone : Mojo::URL->new; |
18
|
|
|
|
|
|
|
$url->scheme('http') unless $url->scheme; |
19
|
|
|
|
|
|
|
$url->host('localhost') unless $url->host; |
20
|
|
|
|
|
|
|
return $url; |
21
|
|
|
|
|
|
|
}; |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
has ua => sub { Mojo::UserAgent->new }; |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
sub call { |
26
|
7
|
|
|
7
|
1
|
17780
|
my ($self, $op) = (shift, shift); |
27
|
7
|
100
|
|
|
|
330
|
my $code = $self->can($op) or Carp::croak('[OpenAPI::Client] No such operationId'); |
28
|
6
|
|
|
|
|
30
|
return $self->$code(@_); |
29
|
|
|
|
|
|
|
} |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
sub call_p { |
32
|
2
|
|
|
2
|
1
|
17825
|
my ($self, $op) = (shift, shift); |
33
|
2
|
100
|
|
|
|
56
|
my $code = $self->can("${op}_p") or return Mojo::Promise->reject('[OpenAPI::Client] No such operationId'); |
34
|
1
|
|
|
|
|
6
|
return $self->$code(@_); |
35
|
|
|
|
|
|
|
} |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
sub new { |
38
|
24
|
|
|
24
|
1
|
1833616
|
my ($parent, $specification) = (shift, shift); |
39
|
24
|
50
|
|
|
|
117
|
my $attrs = @_ == 1 ? shift : {@_}; |
40
|
|
|
|
|
|
|
|
41
|
24
|
|
|
|
|
108
|
my $class = $parent->_url_to_class($specification); |
42
|
24
|
100
|
|
|
|
247
|
$parent->_generate_class($class, $specification, $attrs) unless $class->isa($parent); |
43
|
|
|
|
|
|
|
|
44
|
24
|
|
|
|
|
447
|
my $self = $class->SUPER::new($attrs); |
45
|
24
|
100
|
100
|
|
|
352
|
$self->base_url(Mojo::URL->new($self->{base_url})) if $self->{base_url} and !blessed $self->{base_url}; |
46
|
24
|
50
|
|
|
|
247
|
$self->ua->transactor->name('Mojo-OpenAPI (Perl)') unless $self->{ua}; |
47
|
|
|
|
|
|
|
|
48
|
24
|
100
|
|
|
|
793
|
if (my $app = delete $self->{app}) { |
49
|
9
|
|
|
|
|
52
|
$self->base_url->host(undef)->scheme(undef)->port(undef); |
50
|
9
|
|
|
|
|
125
|
$self->ua->server->app($app); |
51
|
|
|
|
|
|
|
} |
52
|
|
|
|
|
|
|
|
53
|
24
|
|
|
|
|
587
|
return $self; |
54
|
|
|
|
|
|
|
} |
55
|
|
|
|
|
|
|
|
56
|
0
|
|
|
0
|
1
|
0
|
sub validator { Carp::confess("validator() is not defined for $_[0]") } |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
sub _generate_class { |
59
|
10
|
|
|
10
|
|
36
|
my ($parent, $class, $specification, $attrs) = @_; |
60
|
|
|
|
|
|
|
|
61
|
10
|
|
|
|
|
104
|
my $jv = JSON::Validator->new; |
62
|
10
|
|
50
|
|
|
257
|
$jv->coerce($attrs->{coerce} // 'booleans,numbers,strings'); |
63
|
10
|
100
|
|
|
|
432
|
$jv->store->ua->server->app($attrs->{app}) if $attrs->{app}; |
64
|
|
|
|
|
|
|
|
65
|
10
|
|
|
|
|
1002
|
my $schema = $jv->schema($specification)->schema; |
66
|
10
|
50
|
|
|
|
157972
|
die "Invalid schema: $specification has the following errors:\n", join "\n", @{$schema->errors} if @{$schema->errors}; |
|
0
|
|
|
|
|
0
|
|
|
10
|
|
|
|
|
47
|
|
67
|
|
|
|
|
|
|
|
68
|
8
|
50
|
|
8
|
|
97
|
eval <<"HERE" or Carp::confess("package $class: $@"); |
|
8
|
|
|
|
|
16
|
|
|
8
|
|
|
|
|
72
|
|
|
10
|
|
|
|
|
2153565
|
|
69
|
|
|
|
|
|
|
package $class; |
70
|
|
|
|
|
|
|
use Mojo::Base '$parent'; |
71
|
|
|
|
|
|
|
1; |
72
|
|
|
|
|
|
|
HERE |
73
|
|
|
|
|
|
|
|
74
|
10
|
|
|
69
|
|
109
|
Mojo::Util::monkey_patch($class => validator => sub {$schema}); |
|
69
|
|
|
69
|
|
1205
|
|
75
|
10
|
100
|
|
|
|
242
|
return unless $schema->can('routes'); # In case it is not an OpenAPI spec |
76
|
|
|
|
|
|
|
|
77
|
8
|
|
|
|
|
46
|
for my $route ($schema->routes->each) { |
78
|
13
|
50
|
|
|
|
2755
|
next unless $route->{operation_id}; |
79
|
13
|
|
|
|
|
23
|
warn "[$class] Add method $route->{operation_id}() for $route->{method} $route->{path}\n" if DEBUG; |
80
|
13
|
|
|
|
|
75
|
$class->_generate_method_bnb($route->{operation_id} => $route); |
81
|
13
|
|
|
|
|
263
|
$class->_generate_method_p("$route->{operation_id}_p" => $route); |
82
|
|
|
|
|
|
|
} |
83
|
|
|
|
|
|
|
} |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
sub _generate_method_bnb { |
86
|
13
|
|
|
40
|
|
33
|
my ($class, $method_name, $route) = @_; |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
Mojo::Util::monkey_patch $class => $method_name => sub { |
89
|
17
|
100
|
|
17
|
|
72199
|
my $cb = ref $_[-1] eq 'CODE' ? pop : undef; |
|
|
|
|
9
|
|
|
|
90
|
17
|
|
|
|
|
40
|
my $self = shift; |
91
|
17
|
|
|
|
|
99
|
my $tx = $self->_build_tx($route, @_); |
92
|
|
|
|
|
|
|
|
93
|
17
|
100
|
|
|
|
90
|
if ($tx->error) { |
94
|
9
|
100
|
|
|
|
187
|
return $tx unless $cb; |
95
|
1
|
|
|
1
|
|
19
|
Mojo::IOLoop->next_tick(sub { $self->$cb($tx) }); |
|
1
|
|
|
|
|
146
|
|
96
|
1
|
|
|
|
|
118
|
return $self; |
97
|
|
|
|
|
|
|
} |
98
|
|
|
|
|
|
|
|
99
|
8
|
50
|
|
|
|
289
|
return $self->ua->start($tx) unless $cb; |
100
|
0
|
|
|
0
|
|
0
|
$self->ua->start($tx, sub { $self->$cb($_[1]) }); |
|
0
|
|
|
|
|
0
|
|
101
|
0
|
|
|
|
|
0
|
return $self; |
102
|
13
|
|
|
|
|
79
|
}; |
103
|
|
|
|
|
|
|
} |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
sub _generate_method_p { |
106
|
13
|
|
|
21
|
|
34
|
my ($class, $method_name, $route) = @_; |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
Mojo::Util::monkey_patch $class => $method_name => sub { |
109
|
6
|
|
|
12
|
|
65613
|
my $self = shift; |
110
|
6
|
|
|
|
|
41
|
my $tx = $self->_build_tx($route, @_); |
111
|
|
|
|
|
|
|
|
112
|
6
|
100
|
|
|
|
31
|
return $self->ua->start_p($tx) unless my $err = $tx->error; |
113
|
1
|
50
|
|
|
|
22
|
return Mojo::Promise->new->reject($err->{message}) unless $err->{code}; |
114
|
1
|
50
|
33
|
|
|
3
|
return Mojo::Promise->new->reject('WebSocket handshake failed') if $tx->req->is_handshake && !$tx->is_websocket; |
115
|
1
|
|
|
|
|
63
|
return Mojo::Promise->new->resolve($tx); |
116
|
13
|
|
|
|
|
63
|
}; |
117
|
|
|
|
|
|
|
} |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
sub _build_tx { |
120
|
23
|
|
|
35
|
|
82
|
my ($self, $route, $params, %content) = @_; |
121
|
23
|
|
|
|
|
89
|
my $v = $self->validator; |
122
|
23
|
|
|
|
|
119
|
my $url = $self->base_url->clone; |
123
|
23
|
|
|
|
|
1308
|
my ($tx, %headers); |
124
|
|
|
|
|
|
|
|
125
|
23
|
|
100
|
|
|
90
|
push @{$url->path}, map { local $_ = $_; s,\{([-\w]+)\},{$params->{$1}//''},ge; $_ } grep {length} split '/', |
|
37
|
|
|
|
|
79
|
|
|
37
|
|
|
|
|
157
|
|
|
10
|
|
|
|
|
26
|
|
|
10
|
|
|
|
|
81
|
|
|
37
|
|
|
|
|
132
|
|
|
60
|
|
|
|
|
2191
|
|
126
|
23
|
|
|
|
|
53
|
$route->{path}; |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
my @errors = $self->validator->validate_request( |
129
|
|
|
|
|
|
|
[@$route{qw(method path)}], |
130
|
|
|
|
|
|
|
{ |
131
|
|
|
|
|
|
|
body => sub { |
132
|
8
|
|
|
18
|
|
2212
|
my ($name, $param) = @_; |
133
|
|
|
|
|
|
|
|
134
|
8
|
100
|
|
|
|
31
|
if (exists $params->{$name}) { |
135
|
4
|
|
|
|
|
12
|
$content{json} = $params->{$name}; |
136
|
|
|
|
|
|
|
} |
137
|
|
|
|
|
|
|
else { |
138
|
4
|
|
|
|
|
7
|
for ('body', sort keys %{$self->ua->transactor->generators}) { |
|
4
|
|
|
|
|
13
|
|
139
|
14
|
100
|
|
|
|
124
|
next unless exists $content{$_}; |
140
|
2
|
|
|
|
|
7
|
$params->{$name} = $content{$_}; |
141
|
2
|
|
|
|
|
5
|
last; |
142
|
|
|
|
|
|
|
} |
143
|
|
|
|
|
|
|
} |
144
|
|
|
|
|
|
|
|
145
|
8
|
|
|
|
|
36
|
return {exists => $params->{$name}, value => $params->{$name}}; |
146
|
|
|
|
|
|
|
}, |
147
|
|
|
|
|
|
|
formData => sub { |
148
|
8
|
|
|
18
|
|
2263
|
my ($name, $param) = @_; |
149
|
8
|
|
|
|
|
25
|
my $value = _param_as_array($name => $params); |
150
|
8
|
|
|
|
|
25
|
$content{form}{$name} = $params->{$name}; |
151
|
8
|
|
|
|
|
35
|
return {exists => !!@$value, value => $value}; |
152
|
|
|
|
|
|
|
}, |
153
|
|
|
|
|
|
|
header => sub { |
154
|
2
|
|
|
12
|
|
283
|
my ($name, $param) = @_; |
155
|
2
|
|
|
|
|
9
|
my $value = _param_as_array($name => $params); |
156
|
2
|
|
|
|
|
7
|
$headers{$name} = $value; |
157
|
2
|
|
|
|
|
10
|
return {exists => !!@$value, value => $value}; |
158
|
|
|
|
|
|
|
}, |
159
|
|
|
|
|
|
|
path => sub { |
160
|
10
|
|
|
20
|
|
2108
|
my ($name, $param) = @_; |
161
|
10
|
|
|
|
|
53
|
return {exists => exists $params->{$name}, value => $params->{$name}}; |
162
|
|
|
|
|
|
|
}, |
163
|
|
|
|
|
|
|
query => sub { |
164
|
18
|
|
|
28
|
|
3971
|
my ($name, $param) = @_; |
165
|
18
|
|
|
|
|
61
|
my $value = _param_as_array($name => $params); |
166
|
18
|
|
|
|
|
87
|
$url->query->param($name => _coerce_collection_format($value, $param)); |
167
|
18
|
|
|
|
|
917
|
return {exists => !!@$value, value => $value}; |
168
|
|
|
|
|
|
|
}, |
169
|
|
|
|
|
|
|
} |
170
|
23
|
|
|
|
|
110
|
); |
171
|
|
|
|
|
|
|
|
172
|
23
|
100
|
|
|
|
4957
|
if (@errors) { |
173
|
10
|
|
|
|
|
18
|
warn "[@{[ref $self]}] Validation for $route->{method} $url failed: @errors\n" if DEBUG; |
174
|
10
|
|
|
|
|
86
|
$tx = Mojo::Transaction::HTTP->new; |
175
|
10
|
|
|
|
|
90
|
$tx->req->method(uc $route->{method}); |
176
|
10
|
|
|
|
|
289
|
$tx->req->url($url); |
177
|
10
|
|
|
|
|
139
|
$tx->res->headers->content_type('application/json'); |
178
|
10
|
|
|
|
|
802
|
$tx->res->body(Mojo::JSON::encode_json({errors => \@errors})); |
179
|
10
|
|
|
|
|
2255
|
$tx->res->code(400)->message($tx->res->default_message); |
180
|
10
|
|
|
|
|
278
|
$tx->res->error({message => 'Invalid input', code => 400}); |
181
|
|
|
|
|
|
|
} |
182
|
|
|
|
|
|
|
else { |
183
|
13
|
|
|
|
|
29
|
warn "[@{[ref $self]}] Validation for $route->{method} $url was successful\n" if DEBUG; |
184
|
13
|
50
|
|
|
|
69
|
$tx = $self->ua->build_tx($route->{method}, $url, \%headers, defined $content{body} ? $content{body} : %content); |
185
|
|
|
|
|
|
|
} |
186
|
|
|
|
|
|
|
|
187
|
23
|
|
|
|
|
4489
|
$tx->req->env->{operationId} = $route->{operation_id}; |
188
|
23
|
|
|
|
|
383
|
$self->emit(after_build_tx => $tx); |
189
|
|
|
|
|
|
|
|
190
|
23
|
|
|
|
|
398
|
return $tx; |
191
|
|
|
|
|
|
|
} |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
sub _coerce_collection_format { |
194
|
18
|
|
|
22
|
|
369
|
my ($value, $param) = @_; |
195
|
18
|
|
66
|
|
|
168
|
my $format = $param->{collectionFormat} || (+($param->{type} // '') eq 'array' ? 'csv' : ''); |
196
|
18
|
100
|
66
|
|
|
133
|
return $value if !$format or $format eq 'multi'; |
197
|
1
|
50
|
|
|
|
5
|
return join "|", @$value if $format eq 'pipes'; |
198
|
1
|
50
|
|
|
|
4
|
return join " ", @$value if $format eq 'ssv'; |
199
|
1
|
50
|
|
|
|
5
|
return join "\t", @$value if $format eq 'tsv'; |
200
|
1
|
|
|
|
|
9
|
return join ",", @$value; |
201
|
|
|
|
|
|
|
} |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
sub _param_as_array { |
204
|
28
|
|
|
32
|
|
76
|
my ($name, $params) = @_; |
205
|
28
|
100
|
|
|
|
138
|
return !exists $params->{$name} ? [] : ref $params->{$name} eq 'ARRAY' ? $params->{$name} : [$params->{$name}]; |
|
|
100
|
|
|
|
|
|
206
|
|
|
|
|
|
|
} |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
sub _url_to_class { |
209
|
24
|
|
|
24
|
|
107
|
my ($self, $package) = @_; |
210
|
|
|
|
|
|
|
|
211
|
24
|
|
|
|
|
124
|
$package =~ s!^\w+?://!!; |
212
|
24
|
|
|
|
|
857
|
$package =~ s!\W!_!g; |
213
|
24
|
50
|
|
|
|
779
|
$package = Mojo::Util::md5_sum($package) if length $package > 110; # 110 is a bit random, but it cannot be too long |
214
|
|
|
|
|
|
|
|
215
|
24
|
|
|
|
|
93
|
return "$self\::$package"; |
216
|
|
|
|
|
|
|
} |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
1; |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
=encoding utf8 |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
=head1 NAME |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
OpenAPI::Client - A client for talking to an Open API powered server |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
=head1 DESCRIPTION |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
L can generating classes that can talk to an Open API server. |
229
|
|
|
|
|
|
|
This is done by generating a custom class, based on a Open API specification, |
230
|
|
|
|
|
|
|
with methods that transform parameters into a HTTP request. |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
The generated class will perform input validation, so invalid data won't be |
233
|
|
|
|
|
|
|
sent to the server. |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
Note that this implementation is currently EXPERIMENTAL, but unlikely to change! |
236
|
|
|
|
|
|
|
Feedback is appreciated. |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
=head1 SYNOPSIS |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
=head2 Open API specification |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
The specification given to L need to point to a valid OpenAPI document. |
243
|
|
|
|
|
|
|
This document can be OpenAPI v2.x or v3.x, and it can be in either JSON or YAML |
244
|
|
|
|
|
|
|
format. Example: |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
openapi: 3.0.1 |
247
|
|
|
|
|
|
|
info: |
248
|
|
|
|
|
|
|
title: Swagger Petstore |
249
|
|
|
|
|
|
|
version: 1.0.0 |
250
|
|
|
|
|
|
|
servers: |
251
|
|
|
|
|
|
|
- url: http://petstore.swagger.io/v1 |
252
|
|
|
|
|
|
|
paths: |
253
|
|
|
|
|
|
|
/pets: |
254
|
|
|
|
|
|
|
get: |
255
|
|
|
|
|
|
|
operationId: listPets |
256
|
|
|
|
|
|
|
... |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
C, C and the first item in C will be used to construct |
259
|
|
|
|
|
|
|
L. This can be altered at any time, if you need to send data to a |
260
|
|
|
|
|
|
|
custom endpoint. |
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
=head2 Client |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
The OpenAPI API specification will be used to generate a sub-class of |
265
|
|
|
|
|
|
|
L where the "operationId", inside of each path definition, is |
266
|
|
|
|
|
|
|
used to generate methods: |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
use OpenAPI::Client; |
269
|
|
|
|
|
|
|
$client = OpenAPI::Client->new("file:///path/to/api.json"); |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
# Blocking |
272
|
|
|
|
|
|
|
$tx = $client->listPets; |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
# Non-blocking |
275
|
|
|
|
|
|
|
$client = $client->listPets(sub { my ($client, $tx) = @_; }); |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
# Promises |
278
|
|
|
|
|
|
|
$promise = $client->listPets_p->then(sub { my $tx = shift }); |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
# With parameters |
281
|
|
|
|
|
|
|
$tx = $client->listPets({limit => 10}); |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
See L for more information about what you can do with the |
284
|
|
|
|
|
|
|
C<$tx> object, but you often just want something like this: |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
# Check for errors |
287
|
|
|
|
|
|
|
die $tx->error->{message} if $tx->error; |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
# Extract data from the JSON responses |
290
|
|
|
|
|
|
|
say $tx->res->json->{pets}[0]{name}; |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
Check out L, L and |
293
|
|
|
|
|
|
|
L for some of the most used methods in that class. |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
=head1 CUSTOMIZATION |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
=head2 Custom server URL |
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
If you want to request a different server than what is specified in the Open |
300
|
|
|
|
|
|
|
API document, you can change the L: |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
# Pass on a Mojo::URL object to the constructor |
303
|
|
|
|
|
|
|
$base_url = Mojo::URL->new("http://example.com"); |
304
|
|
|
|
|
|
|
$client1 = OpenAPI::Client->new("file:///path/to/api.json", base_url => $base_url); |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
# A plain string will be converted to a Mojo::URL object |
307
|
|
|
|
|
|
|
$client2 = OpenAPI::Client->new("file:///path/to/api.json", base_url => "http://example.com"); |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
# Change the base_url after the client has been created |
310
|
|
|
|
|
|
|
$client3 = OpenAPI::Client->new("file:///path/to/api.json"); |
311
|
|
|
|
|
|
|
$client3->base_url->host("other.example.com"); |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
=head2 Custom content |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
You can send XML or any format you like, but this require you to add a new |
316
|
|
|
|
|
|
|
"generator": |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
use Your::XML::Library "to_xml"; |
319
|
|
|
|
|
|
|
$client->ua->transactor->add_generator(xml => sub { |
320
|
|
|
|
|
|
|
my ($t, $tx, $data) = @_; |
321
|
|
|
|
|
|
|
$tx->req->body(to_xml $data); |
322
|
|
|
|
|
|
|
return $tx; |
323
|
|
|
|
|
|
|
}); |
324
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
$client->addHero({}, xml => {name => "Supergirl"}); |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
See L for more details. |
328
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
=head1 EVENTS |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
=head2 after_build_tx |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
$client->on(after_build_tx => sub { my ($client, $tx) = @_ }) |
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
This event is emitted after a L object has been |
336
|
|
|
|
|
|
|
built, just before it is passed on to the L. Note that all validation has |
337
|
|
|
|
|
|
|
already been run, so alternating the C<$tx> too much, might cause an invalid |
338
|
|
|
|
|
|
|
request on the server side. |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
A special L variable will be set, to reference the |
341
|
|
|
|
|
|
|
operationId: |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
$tx->req->env->{operationId}; |
344
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
Note that this usage of C is currently EXPERIMENTAL: |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
=head1 ATTRIBUTES |
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
=head2 base_url |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
$base_url = $client->base_url; |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
Returns a L object with the base URL to the API. The default value |
354
|
|
|
|
|
|
|
comes from C, C and C in the OpenAPI v2 specification |
355
|
|
|
|
|
|
|
or from C in the OpenAPI v3 specification. |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
=head2 ua |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
$ua = $client->ua; |
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
Returns a L object which is used to execute requests. |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
=head1 METHODS |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
=head2 call |
366
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
$tx = $client->call($operationId => \%params, %content); |
368
|
|
|
|
|
|
|
$client = $client->call($operationId => \%params, %content, sub { my ($client, $tx) = @_; }); |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
Used to either call an C<$operationId> that has an "invalid name", such as |
371
|
|
|
|
|
|
|
"list pets" instead of "listPets" or to call an C<$operationId> that you are |
372
|
|
|
|
|
|
|
unsure is supported yet. If it is not, an exception will be thrown, |
373
|
|
|
|
|
|
|
matching text "No such operationId". |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
C<$operationId> is the name of the resource defined in the |
376
|
|
|
|
|
|
|
L. |
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
C<$params> is optional, but must be a hash ref, where the keys should match a |
379
|
|
|
|
|
|
|
named parameter in the L. |
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
C<%content> is used for the body of the request, where the key need to be |
382
|
|
|
|
|
|
|
either "body" or a matching L. Example: |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
$client->addHero({}, body => "Some data"); |
385
|
|
|
|
|
|
|
$client->addHero({}, json => {name => "Supergirl"}); |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
C<$tx> is a L object. |
388
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
=head2 call_p |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
$promise = $client->call_p($operationId => $params, %content); |
392
|
|
|
|
|
|
|
$promise->then(sub { my $tx = shift }); |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
As L above, but returns a L object. |
395
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
=head2 new |
397
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
$client = OpenAPI::Client->new($specification, \%attributes); |
399
|
|
|
|
|
|
|
$client = OpenAPI::Client->new($specification, %attributes); |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
Returns an object of a generated class, with methods generated from the Open |
402
|
|
|
|
|
|
|
API specification located at C<$specification>. See L |
403
|
|
|
|
|
|
|
for valid versions of C<$specification>. |
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
Note that the class is cached by perl, so loading a new specification from the |
406
|
|
|
|
|
|
|
same URL will not generate a new class. |
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
Extra C<%attributes>: |
409
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
=over 2 |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
=item * app |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
Specifying an C is useful when running against a local L |
415
|
|
|
|
|
|
|
instance. |
416
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
=item * coerce |
418
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
See L. Default to "booleans,numbers,strings". |
420
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
=back |
422
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
=head2 validator |
424
|
|
|
|
|
|
|
|
425
|
|
|
|
|
|
|
$validator = $client->validator; |
426
|
|
|
|
|
|
|
$validator = $class->validator; |
427
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
Returns a L object for a generated class. |
429
|
|
|
|
|
|
|
Note that this is a global variable, so changing the object will affect all |
430
|
|
|
|
|
|
|
instances returned by L. |
431
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
Copyright (C) 2017-2021, Jan Henning Thorsen |
435
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
This program is free software, you can redistribute it and/or modify it under |
437
|
|
|
|
|
|
|
the terms of the Artistic License version 2.0. |
438
|
|
|
|
|
|
|
|
439
|
|
|
|
|
|
|
=head1 AUTHORS |
440
|
|
|
|
|
|
|
|
441
|
|
|
|
|
|
|
=head2 Project Founder |
442
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
Jan Henning Thorsen - C |
444
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
=head2 Contributors |
446
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
=over 2 |
448
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
=item * Clive Holloway |
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
=item * Ed J |
453
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
=item * Jan Henning Thorsen |
455
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
=item * Jan Henning Thorsen |
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
=item * Mohammad S Anwar |
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
=item * Reneeb |
461
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
=item * Roy Storey |
463
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
=item * Veesh Goldman |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
=back |
467
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
=cut |