jhthorsen / openapi-client Goto Github PK
View Code? Open in Web Editor NEWA client for talking to an Open API powered server
A client for talking to an Open API powered server
The docs could really use an example of a header parameter when making an OpenAPI call.
I've been setting up API clients for amazon's advertising APIs, and in the course of doing so, it was most convenient to mix in certain shared behaviors with a role.
The issue that I'm having is that I can't mix in roles nicely to the generated classes. For example:
OpenAPI::Client->with_roles('My::Awesome::Role')->new('path/to/spec.yaml')
Will still return a subclass of OpenAPI::Client
, rather than a subclass of the package created by mixing in my role.
The alternative of
OpenAPI::Client->new('path/to/spec.yaml')->with_roles('My::Awesome::Role')
works, except that the behavior is surprising, b/c the role never gets instantiated, so the defaults for fields in the role are not set.
For reference, I use Moo::Role
to create attributes (access to redis for storing access tokens, for example).
Either the constructor for OpenAPI::Client should use a dynamic classname when generating the child class on lines 71-74, or there should be an extra argument for putting more stuff into the generated class.
Or perhaps give access to the generated class before instantiating it?
Thanks for this wonderful module!
t/00-project.t fails like this, only on RedHat-like systems (seen on fedora 36, centos 7 and rocky 8):
# Failed test 'POD spelling for blib/lib/OpenAPI/Client.pm'
# at t/00-project.t line 38.
# Errors:
# Storey
# v2
# v3
# Looks like you failed 1 test of 12.
t/00-project.t ..............
Dubious, test returned 1 (wstat 256, 0x100)
The test suite fails on some of my smokers:
Duplicate keys not allowed, at character offset 595 (before "required": ["email",...") at /home/cpansand/.cpan/build/2018070703/Mojolicious-7.87-ThKlUI/blib/lib/Mojo/JSON.pm line 39.
t/body-validation.t .........
Dubious, test returned 255 (wstat 65280, 0xff00)
No subtests run
The possibly problematic files in the test file:
"required": true,
"required": ["email", "password"],
Looks like OpenAPI::Client doesn't currently support OpenAPI 3. Would be cool if it did, because Mojolicious::Plugin::OpenAPI does, and it's a one-way conversion... which means my Grand Plan(tm) of autogenerating the client-side API for the OpenAPI thing won't work :-/
Would this be very complicated?
When constructing an OpenAPI::Client with a bare URL as the value of the base_url param, the host and scheme cannot be changed after construction.
However, if you instead construct a Mojo::URL object and pass that as a base_url, the methods can be accessed.
I'm including a test case that I wrote that demonstrates the issue below:
use Test::More;
use OpenAPI::Client;
my $client1;
my $client2;
my $base_url1 = Mojo::URL->new('http://example.com');
my $base_url2 = 'http://example.com';
my $otherhost = 'somewhereelse.com';
# Client 1: Use a Mojo::URL object for base_url
# Construct a client with a Mojo::URL object
ok($client1 = OpenAPI::Client->new("file://testswagger.yaml", base_url => $base_url1), "Constructed client1 - OpenAPI::Client with Mojo::URL object");
# Try to set client to use a different host
ok($client1->base_url->host($otherhost), "Set client1 to use host $host");
# Try to set client to use HTTPS
ok($client1->base_url->scheme('https'), "Set client1 to use https");
# Client 2: Use a bare URL for base_url
# Construct a client with a bare URL
ok($client2 = OpenAPI::Client->new("file://testswagger.yaml", base_url => $base_url2), "Constructed client2 - OpenAPI::Client with bare URL");
# Try to set client1 to use a different host
ok($client2->base_url->host($host), "Set client2 to use $host");
# Try to set client1 to use HTTPS
ok($client2->base_url->scheme('https'), "Set client2 to use https");
done_testing();
The test swagger spec:
swagger: '2.0'
info:
version: 1.0.0
title: Simple example API
description: An API to illustrate Swagger
paths:
/list:
get:
description: Returns a list of stuff
responses:
200:
description: Successful response
I'm trying to connect to a Kubernetes API server. This requires me to do two things:
This is even true when trying to access the OpenAPI definition (at $apiserver/openapi/v2).
In order to do that, I prepared a Mojo::UserAgent like so:
my $token;
{
local $/="";
open my $token_f, "</run/secrets/kubernetes.io/serviceaccount/token";
$token=<$token_f>
}
my $ua = Mojo::UserAgent->new;
$ua->on(prepare => sub($ua, $tx) {
$tx->req->headers->header("Authorization: Bearer $token");
});
$ua = $ua->ca("/run/secrets/kubernetes.io/serviceaccount/ca.crt");
my $client = OpenAPI::Client->new("https://" . $ENV{KUBERNETES_SERVICE_HOST} . ":" . $ENV{KUBERNETES_SERVICE_PORT} . "/openapi/v2", ua => $ua);
However, OpenAPI::Client does not seem to use the Mojo::UserAgent at this point:
root@perl:~# perl ./test
GET https://10.152.183.1:443/openapi/v2: SSL connect attempt failed error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed
One can bypass that by setting MOJO_CA_FILE, but that misses the header:
root@perl:~# MOJO_CA_FILE=/run/secrets/kubernetes.io/serviceaccount/ca.crt perl ./test
GET https://10.152.183.1:443/openapi/v2: Forbidden at /usr/local/lib/perl5/site_perl/5.36.0/JSON/Validator/Store.pm line 190.
HI,
Nowadays, there are many services that want the header encrypted with api key like https://metacpan.org/pod/WebService::Cryptopia
my $signature = $self->api_key . "POST" . lc( url_encode( $url ) ) . $nonce . $request_content_base64_string;
chomp( $signature );
$self->log->trace( "Signature: $signature" ) if $self->log->is_trace;
my $hmac_signature = encode_base64( hmac_sha256( $signature, decode_base64( $self->api_secret ) ) );
chomp( $hmac_signature );
$self->log->trace( "HMAC signature: $hmac_signature" ) if $self->log->is_trace;
my $header_value = "amx " . $self->api_key . ':' . $hmac_signature . ':' . $nonce;
$self->log->trace( "Authorization: $header_value" ) if $self->log->is_trace;
my $request = HTTP::Request->new( 'POST' => $url );
$request->header( 'Authorization', $header_value );
Making signature with hamc_sha256(key+method+url+nonce+content) and adding it to request headers.
But OpenApi::Client's pre_process function can't see method and url, so I can't build signature and add it to headers.
return $self->ua->build_tx($http_method, $url, $self->pre_processor->(\%headers, \%req));
How can I make this possible?
Hi there.
I was trying to use OpenAPI::Client in a project of mine, where I generate the swagger using a plugin for phoenix (the go-to elixir web framework if you are not familiar). It generates swagger like many other plugins i guess, but comes up with swagger thats not quite what OpenAPI::Client expects. More specifically "schemes" and "basePath" is missing from the specification.
When that happens the base_url attribute of OpenAPI::Client gets into trouble with references to undef'ed objects aso.
I found that this version of base_url handled better defaults, to make it survive.
has base_url => sub {
my $self = shift;
my $schema = $self->validator->schema;
my $scheme = $schema->get('/schemes') || ['http'];
return Mojo::URL->new->host($schema->get('/host'))->path($schema->get('/basePath') || '/')
->scheme($scheme->[0]);
};
I know that you can just brush it of and say: That looks like a generator problem, and you might be right. But no one ever died for having better defaults ;)
Hi,
Atempting to load schema results in error of:
Invalid JSON specification HASH(0x11c7348):
securityDefinitions in API schema looks like:
"securityDefinitions": {
"myIdentity": {
"flow": "implicit",
"authorizationUrl": "https://identity.server.local/connect/authorize",
"tokenUrl": "https://identity.server.local/connect/token",
"scopes": {
"myAPI": "The scope needed to access the API"
},
"type": "oauth2",
"description": "My Identity Implicit Grant"
}
}
This worked with the Swagger2::Client.
Thanks
Hello! There's something weird going on with ports. According to the Swagger2 spec, I should be able to include a port as part of my host as such:
---
swagger: 2.0
host: api.example.com:3300
basePath: /
paths:
/foo:
get:
operationId: listPets
responses:
200:
description: "TEST"
info:
title: "TEST"
version: "foo"
But when I try to hit that endpoint with the client, it's somehow adding ":80" to the host. IE:
➜ ~ cat bug.pl
#!/usr/bin/env perl
use strict;
use warnings;
use OpenAPI::Client;
my $client = OpenAPI::Client->new('min.swagger');
print $client->listPets({})->result->to_string() . "\n";
➜ ~ MOJO_CLIENT_DEBUG=1 perl bug.pl
-- Blocking request (http://api.example.com:3300/foo)
-- Connect 602f9810529364379e04496e1971cc08 (http://api.example.com:3300:80)
Can't resolve: Name or service not known at bug.pl line 8.
I tried digging into it, but I can't seem to figure out where :80 is being added. Do you have any idea what's going on?
We have an OpenAPI specification that has a response component object with status
attribute
status:
type: integer
format: int32
description: >
The HTTP status code generated by the origin server for this occurrence
of the problem.
minimum: 100
maximum: 600
exclusiveMaximum: true
example: 503
We have used the exclusiveMaximum
type restriction. Rendering a response with a status code 400 causes a strange validation error
{errors => [{message => "400 >= maximum(1)",path => "/body/status"}],status => 500}
The 1
in maximum(1)
is (with high probability) caused by the numification of JSON::PP::true. This looks like a bug.
Hello again :)
I have noticed that when i do a POST towards some function, the Content-Type: application/json header is not set. Shouldn't it always be set?
The missing header results in an error from the remote API I am using.
'headers' => bless( {
'headers' => {
'content-length' => [
83
],
'accept-encoding' => [
'gzip'
],
'user-agent' => [
'Mojo-OpenAPI (Perl)'
],
'host' => [
'<host>:<port>'
]
}
}, 'Mojo::Headers' ),
If i do a POST on the commandline, i.e.
curl -H "Content-Type: application/json" -X POST -d '{"foo":"bar"}' http://<host>:<port>/api/myfunction
It works.
So at least the service expects the header to be set.
/Jesper
I have a jenkins Open API specification. I am sending a Disable a job request
'/job/{name}/disable':
post:
description: Disable a job
operationId: postJobDisable
parameters:
- $ref: '#/components/parameters/jobName'
- $ref: '#/components/parameters/crumb'
responses:
'200':
description: Successfully disabled the job
'401':
$ref: '#/components/responses/unauthorized'
'403':
$ref: '#/components/responses/forbidden'
'404':
$ref: '#/components/responses/jobNotFound'
security:
- jenkins_auth: []
tags:
- remoteAccess
using the OpenAPI::Client
(version 1.0.3) implementation. Although the jenkins response code is 302 which is not one of the specified response codes (200, 401, 403, 404), the client does not complain (dies) or at least warns the user.
This warning is thrown on each API call for my use case:
Use of uninitialized value in string eq at /home/ahartmai/.plenv/versions/5.36.0/lib/perl5/site_perl/5.36.0/OpenAPI/Client.pm line 195.
Maybe I'm missing something in the docs but how do you pass API keys and other parameters defined in the security section?
Would it be better to return a rejected Mojo::Promise
rather than Carp::croak
in call_p
?
openapi-client/lib/OpenAPI/Client.pm
Lines 37 to 41 in 5b53b79
Changing to this.
my $code = $self->can("${op}_p")
or return Mojo::Promise->new->reject("[OpenAPI::Client] No such operationId");
Allows the usual
$api->call_p('incorrect?')->then(sub { ... }, sub { $error = shift; })->wait;
Latest versions of Mojolicious::Plugin::OpenAPI and OpenAPI::Client fails a test...
Adding a dump of return value being tested (line 20 of t/command-local-with-ref.t):
# failing test
ok !$oc->validator->schema->get('/definitions'), 'no definitions added';
warn "*********************\n";
my $r = $oc->validator->schema->get('/definitions');
warn Dumper($r);
Test output
t/command-local-with-ref.t .. 1/?
# Failed test 'no definitions added'
# at t/command-local-with-ref.t line 24.
*********************
$VAR1 = {
'DefaultResponse' => {
'required' => [
'errors'
],
'properties' => {
'errors' => {
'items' => {
'type' => 'object',
'required' => [
'message'
],
'properties' => {
'path' => {
'type' => 'string'
},
'message' => {
'type' => 'string'
}
}
},
'type' => 'array'
}
},
'type' => 'object'
}
};
```
The test suite fails on some of my smokers:
...
# Failed test 'valid loginUser'
# at t/body-validation.t line 30.
# got: '501'
# expected: '200'
# Failed test 'valid return'
# at t/body-validation.t line 31.
# got: undef
# expected: '[email protected]'
# Failed test 'only sent data to server once'
# at t/body-validation.t line 42.
# got: '0'
# expected: '1'
# Looks like you failed 3 tests of 10.
t/body-validation.t .........
Dubious, test returned 3 (wstat 768, 0x300)
Failed 3/10 subtests
... (etc) ...
This seems to happen if Mojolicious::Plugin::OpenAPI 2.x is installed:
****************************************************************
Regression 'mod:Mojolicious::Plugin::OpenAPI'
****************************************************************
Name Theta StdErr T-stat
[0='const'] 1.0000 0.0000 5791088394397843.00
[1='eq_1.26'] 0.0000 0.0000 1.36
[2='eq_1.28'] 0.0000 0.0000 1.11
[3='eq_1.30'] 0.0000 0.0000 1.27
[4='eq_2.00'] -1.0000 0.0000 -5752862970752844.00
R^2= 1.000, N= 119, K= 5
****************************************************************
Right now, query params are added to the URL object as an arrayref, which causes them to be rendered as repeated key/vals
openapi-client/lib/OpenAPI/Client.pm
Lines 163 to 168 in 7f6b81a
That means that if we have a query param called "things":
call({ things => [ qw/one two three/ ] })
It gets rendered to the URL as ?things=one&things=two&things=three
.
This is not the correct behavior. For OpenAPI v2, for example, the default (as per the docs) is to connect them via commas, i.e. ?things=one,two,three
.
I would love to write up a patch for this, I would just need some guidance as to how to get the correct format from the OpenAPI spec
I'm trying to pass filter variables in a GET request to an OpenAPI route with this specification:
- in: query
name: params
schema:
type: object
additionalProperties:
type: string
style: form
explode: true
I've tried various different ways of passing the data at request time:
my $client = OpenAPI::Client->new(...);
my $tx = $client->findApps({
params => { 'id' => '1' }
});
my $tx = $client->findApps({ 'id' => '1' });
What is the correct way to do this?
I'm currently getting this warning/error:
Use of uninitialized value $name in exists at /usr/local/share/perl/5.26.1/OpenAPI/Client.pm line 204
Hi,
t/command-local-with-ref.t fails with Mojolicious::Plugin::[email protected].
https://www.cpantesters.org/cpan/report/51c6c38a-c3e0-11e9-a97d-ebc033f4362a
I'm opening this ticket because this information unluckily can't be seen from the test report.
Versions:
Issue:
OpenAPI command does not work with specified pet store. Issue #8 said probably fixed in 0.24 but it is not fixed.
Example:
REST server code actually works and can be called from a REST call:
$ ./app get /api/pets
[2020-03-08 13:14:54.65099] [28570] [debug] GET "/api/pets" (9245ecb7)
[2020-03-08 13:14:55.44719] [28570] [debug] Routing to controller "App::Controller::Pet" and action "list"
[2020-03-08 13:14:55.46035] [28570] [debug] 200 OK (0.809346s, 1.236/s)
{ ... JSON I returned from the test function "list" .. }
OpenAPI client command cannot be executed due to validation issue:
$ app openapi /api
[2020-03-08 13:14:01.41765] [28565] [debug] GET "/api" (3635cea4)
[2020-03-08 13:14:01.41832] [28565] [debug] Routing to a callback
[2020-03-08 13:14:01.41971] [28565] [debug] 200 OK (0.002047s, 488.520/s)
Invalid JSON specification HASH(0x5571d8363290):
- /: Properties not allowed: components, openapi, servers. at /usr/local/share/perl/5.28.1/JSON/Validator.pm line 159.
JSON::Validator::load_and_validate_schema(JSON::Validator::OpenAPI::Mojolicious=HASH(0x5571d831bc98), "/api", HASH(0x5571d831bf08)) called at /usr/local/share/perl/5.28.1/JSON/Validator/OpenAPI/Mojolicious.pm line 63
JSON::Validator::OpenAPI::Mojolicious::load_and_validate_schema(JSON::Validator::OpenAPI::Mojolicious=HASH(0x5571d831bc98), "/api", HASH(0x5571d831bf08)) called at /usr/local/share/perl/5.28.1/OpenAPI/Client.pm line 52
OpenAPI::Client::new("OpenAPI::Client", "/api", "app",App=HASH(0x5571d5e201b0)) called at /usr/local/share/perl/5.28.1/Mojolicious/Command/openapi.pm line 57
Mojolicious::Command::openapi::run(Mojolicious::Command::openapi=HASH(0x5571d82e0ff8), "/api") called at /usr/share/perl5/Mojolicious/Commands.pm line 55
Mojolicious::Commands::run(Mojolicious::Commands=HASH(0x5571d83057f0), "openapi", "/api") called at /usr/share/perl5/Mojolicious.pm line 195
Mojolicious::start(App=HASH(0x5571d5e201b0)) called at /usr/share/perl5/Mojolicious/Commands.pm line 72
Mojolicious::Commands::start_app("Mojolicious::Commands", "App") called at ./app line 17
$ cat petstore.json
{
"openapi": "3.0.2",
"info": {
"version": "1.0",
"title": "Some awesome API"
},
"paths": {
"/pets": {
"get": {
"operationId": "getPets",
"x-mojo-name": "get_pets",
"x-mojo-to": "pet#list",
"summary": "Finds pets in the system",
"parameters": [
{
"in": "query",
"name": "age",
"schema": {
"type": "integer"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
},
"responses": {
"200": {
"description": "Pet response",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"pets": {
"type": "array",
"items": {
"type": "object"
}
}
}
}
}
}
}
}
}
}
},
"servers": [
{
"url": "/api"
}
]
}
It'd be really nice to support passing the server's URI to the constructor. As it stands it's pretty challenging to use this code against a test server running locally. I have to munge the swagger file to inject the proper host
into it.
Hello,
I'm running a local dev server on port 3000 with an openapi v3 spec. OpenAPI::Client was attempting to connect to localhost:80
The problem seems to strive in the fact that openapi v3 doesn't have basePath or host attributes, so OpenAPI::Client is defaulting to http://localhost, instead of picking up the info from the servers array.
I've "fixed" this problem for my specific use case with the following:
has base_url => sub {
my $self = shift;
my $schema = $self->validator;
my $schemes = $schema->get('/schemes') || [];
return Mojo::URL->new($schema->get('/servers/0/url'));
t/command-local-with-ref.t fails:
[2019-05-05 10:15:58.60959] [9463] [debug] GET "/api" (f832912d)
[2019-05-05 10:15:58.61073] [9463] [debug] Routing to a callback
[2019-05-05 10:15:58.61165] [9463] [debug] 200 OK (0.002408s, 415.282/s)
# Failed test 'no definitions added'
# at t/command-local-with-ref.t line 20.
[2019-05-05 10:15:58.68958] [9463] [debug] GET "/ext" (8b70be07)
[2019-05-05 10:15:58.69027] [9463] [debug] Routing to a callback
[2019-05-05 10:15:58.69130] [9463] [debug] 200 OK (0.002s, 500.000/s)
t/command-local-with-ref.t .. skipped: Invalid JSON specification HASH(0x498726a8):
...
Test Summary Report
-------------------
t/command-local-with-ref.t (Wstat: 256 Tests: 3 Failed: 1)
Failed test: 2
Non-zero exit status: 1
Parse errors: Bad plan. You planned 0 tests but ran 3.
Statistical analysis has two candidates:
****************************************************************
Regression 'mod:Mojolicious::Plugin::OpenAPI'
****************************************************************
Name Theta StdErr T-stat
[0='const'] 1.0000 0.0000 54064544849289584.00
[1='eq_2.14'] -1.0000 0.0000 -52858492505163784.00
R^2= 1.000, N= 68, K= 2
****************************************************************
****************************************************************
Regression 'mod:JSON::Validator'
****************************************************************
Name Theta StdErr T-stat
[0='const'] 1.0000 0.0000 88189917184593344.00
[1='eq_3.09'] -1.0000 0.0000 -86192905442162752.00
[2='eq_3.10'] -1.0000 0.0000 -44094958592296672.00
R^2= 1.000, N= 68, K= 3
****************************************************************
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.