use
5.020;
use
if
"$]"
>= 5.022,
experimental
=>
're_strict'
;
no
if
"$]"
>= 5.031009,
feature
=>
'indirect'
;
no
if
"$]"
>= 5.033001,
feature
=>
'multidimensional'
;
no
if
"$]"
>= 5.033006,
feature
=>
'bareword_filehandles'
;
use
open
':std'
,
':encoding(UTF-8)'
;
use
constant {
true
=> JSON::PP::true,
false
=> JSON::PP::false };
my
$openapi_preamble
=
<<'YAML';
---
openapi: 3.1.0
info:
title: Test API
version: 1.2.3
YAML
my
$doc_uri_rel
= Mojo::URL->new(
'/api'
);
my
$yamlpp
= YAML::PP->new(
boolean
=>
'JSON::PP'
);
my
$type_index
= 0;
START:
$::TYPE = $::TYPES[
$type_index
];
note
'REQUEST/RESPONSE TYPE: '
.$::TYPE;
subtest
'request is parsed to get path information'
=>
sub
{
my
$openapi
= OpenAPI::Modern->new(
openapi_uri
=>
'/api'
,
openapi_schema
=>
$yamlpp
->load_string(
<<YAML));
$openapi_preamble
paths:
/foo/{foo_id}:
post:
operationId: my-post-path
/foo/bar:
get:
operationId: my-get-path
webhooks:
my_hook:
description: I like webhooks
post:
operationId: hooky
YAML
ok(!
$openapi
->find_path(
my
$options
= {
request
=>
$request
,
path_template
=>
'/foo/baz'
,
path_captures
=> {} }),
'find_path returns false'
);
cmp_deeply(
$options
,
{
request
=> isa(
'Mojo::Message::Request'
),
path_template
=>
'/foo/baz'
,
path_captures
=> {},
method
=>
'get'
,
errors
=> [
methods(
TO_JSON
=> {
instanceLocation
=>
'/request/uri/path'
,
keywordLocation
=>
'/paths'
,
absoluteKeywordLocation
=>
$doc_uri
->clone->fragment(
'/paths'
)->to_string,
error
=>
'missing path-item "/foo/baz"'
,
}),
],
},
'unsuccessful path extraction results in the error being returned in the options hash'
,
);
ok(!
$openapi
->find_path(
$options
= {
request
=>
$request
,
operation_id
=>
'bloop'
,
path_captures
=> {} }),
'find_path returns false'
);
cmp_deeply(
$options
,
{
request
=> isa(
'Mojo::Message::Request'
),
method
=>
'get'
,
path_captures
=> {},
operation_id
=>
'bloop'
,
errors
=> [
methods(
TO_JSON
=> {
instanceLocation
=>
'/request/uri/path'
,
keywordLocation
=> jsonp(
'/paths'
),
absoluteKeywordLocation
=>
$doc_uri
->clone->fragment(jsonp(
'/paths'
))->to_string,
error
=>
'unknown operation_id "bloop"'
,
}),
],
},
'path template does not exist under /paths'
,
);
ok(!
$openapi
->find_path(
$options
= {
request
=>
$request
,
operation_id
=>
'hooky'
,
path_captures
=> {} }),
'find_path returns false'
);
cmp_deeply(
$options
,
{
request
=> isa(
'Mojo::Message::Request'
),
method
=>
'get'
,
path_captures
=> {},
operation_id
=>
'hooky'
,
errors
=> [
methods(
TO_JSON
=> {
instanceLocation
=>
'/request/uri/path'
,
keywordLocation
=>
'/webhooks/my_hook/post/operationId'
,
absoluteKeywordLocation
=>
$doc_uri
->clone->fragment(jsonp(
'/webhooks/my_hook/post/operationId'
))->to_string,
error
=>
'operation id does not have an associated path'
,
}),
],
},
'path template does not exist under /paths'
,
);
'find_path returns false'
);
cmp_deeply(
$options
,
{
request
=> isa(
'Mojo::Message::Request'
),
method
=>
'get'
,
path_template
=>
'/foo/{foo_id}'
,
path_captures
=> {
foo_id
=>
'bloop'
},
errors
=> [
methods(
TO_JSON
=> {
instanceLocation
=>
'/request/method'
,
keywordLocation
=> jsonp(
qw(/paths /foo/{foo_id} get)
),
absoluteKeywordLocation
=>
$doc_uri
->clone->fragment(jsonp(
qw(/paths /foo/{foo_id} get)
))->to_string,
error
=>
'missing operation for HTTP method "get"'
,
}),
],
},
'operation does not exist under /paths/<path_template>/<method>'
,
);
path_template
=>
'/foo/{foo_id}'
,
operation_id
=>
'my-get-path'
,
path_captures
=> {} }),
'find_path returns false'
);
cmp_deeply(
$options
,
{
request
=> isa(
'Mojo::Message::Request'
),
method
=>
'post'
,
path_template
=>
'/foo/{foo_id}'
,
path_captures
=> {},
operation_id
=>
'my-get-path'
,
errors
=> [
methods(
TO_JSON
=> {
instanceLocation
=>
'/request/uri/path'
,
keywordLocation
=>
'/paths/~1foo~1bar'
,
absoluteKeywordLocation
=>
$doc_uri
->clone->fragment(jsonp(
'/paths'
,
qw(/foo/bar)
))->to_string,
error
=>
'operation does not match provided path_template'
,
}),
],
},
'path_template and operation_id are inconsistent'
,
);
operation_id
=>
'my-get-path'
,
path_captures
=> {} }),
'find_path returns false'
);
cmp_deeply(
$options
,
{
request
=> isa(
'Mojo::Message::Request'
),
method
=>
'post'
,
path_captures
=> {},
operation_id
=>
'my-get-path'
,
errors
=> [
methods(
TO_JSON
=> {
instanceLocation
=>
'/request/method'
,
keywordLocation
=>
'/paths/~1foo~1bar/get'
,
absoluteKeywordLocation
=>
$doc_uri
->clone->fragment(jsonp(
'/paths'
,
qw(/foo/bar get)
))->to_string,
error
=>
'wrong HTTP method post'
,
}),
],
},
'request HTTP method does not match operation'
,
);
'find_path returns false'
);
cmp_deeply(
$options
,
{
request
=> isa(
'Mojo::Message::Request'
),
method
=>
'GET'
,
errors
=> [
methods(
TO_JSON
=> {
instanceLocation
=>
'/request/method'
,
keywordLocation
=>
''
,
absoluteKeywordLocation
=>
$doc_uri
->clone->to_string,
error
=>
'wrong HTTP method POST'
,
}),
],
},
'request HTTP method does not match method option'
,
);
path_template
=>
'/foo/{foo_id}'
,
path_captures
=> {
bloop
=>
'bar'
} }),
'find_path returns false'
);
cmp_deeply(
$options
,
{
request
=> isa(
'Mojo::Message::Request'
),
method
=>
'post'
,
path_template
=>
'/foo/{foo_id}'
,
path_captures
=> {
bloop
=>
'bar'
},
operation_path
=>
'/paths/~1foo~1{foo_id}/post'
,
errors
=> [
methods(
TO_JSON
=> {
instanceLocation
=>
'/request/uri/path'
,
keywordLocation
=>
'/paths/~1foo~1{foo_id}'
,
absoluteKeywordLocation
=>
$doc_uri
->clone->fragment(jsonp(
qw(/paths /foo/{foo_id})
))->to_string,
error
=>
'provided path_captures names do not match path template "/foo/{foo_id}"'
,
}),
],
},
'path template does not match path captures'
,
);
path_template
=>
'/foo/bar'
,
operation_id
=>
'my-get-path'
,
path_captures
=> {} }),
'find_path returns successfully'
);
cmp_deeply(
$options
,
{
request
=> isa(
'Mojo::Message::Request'
),
method
=>
'get'
,
path_template
=>
'/foo/bar'
,
path_captures
=> {},
operation_id
=>
'my-get-path'
,
operation_path
=>
'/paths/~1foo~1bar/get'
,
errors
=> [],
},
'path_template and operation_id can both be passed, if consistent'
,
);
path_template
=>
'/foo/bar'
,
path_captures
=> {} }),
'find_path returns false'
);
cmp_deeply(
$options
,
{
request
=> isa(
'Mojo::Message::Request'
),
method
=>
'get'
,
path_template
=>
'/foo/bar'
,
path_captures
=> {},
operation_path
=>
'/paths/~1foo~1bar/get'
,
errors
=> [
methods(
TO_JSON
=> {
instanceLocation
=>
'/request/uri/path'
,
keywordLocation
=> jsonp(
qw(/paths /foo/bar)
),
absoluteKeywordLocation
=>
$doc_uri
->clone->fragment(jsonp(
qw(/paths /foo/bar)
))->to_string,
error
=>
'provided path_template does not match request URI'
,
}),
],
},
'path_template is not consistent with request URI, with no captures'
,
);
path_template
=>
'/foo/{foo_id}'
,
path_captures
=> {
foo_id
=> 123 } }),
'find_path returns false'
);
cmp_deeply(
$options
,
{
request
=> isa(
'Mojo::Message::Request'
),
method
=>
'post'
,
path_template
=>
'/foo/{foo_id}'
,
path_captures
=> {
foo_id
=> 123 },
operation_path
=>
'/paths/~1foo~1{foo_id}/post'
,
errors
=> [
methods(
TO_JSON
=> {
instanceLocation
=>
'/request/uri/path'
,
keywordLocation
=> jsonp(
qw(/paths /foo/{foo_id})
),
absoluteKeywordLocation
=>
$doc_uri
->clone->fragment(jsonp(
qw(/paths /foo/{foo_id})
))->to_string,
error
=>
'provided path_template does not match request URI'
,
}),
],
},
'path_template is not consistent with request URI, with captures'
,
);
path_template
=>
'/foo/{foo_id}'
}),
'find_path returns false'
);
cmp_deeply(
$options
,
{
request
=> isa(
'Mojo::Message::Request'
),
method
=>
'post'
,
path_template
=>
'/foo/{foo_id}'
,
operation_path
=>
'/paths/~1foo~1{foo_id}/post'
,
errors
=> [
methods(
TO_JSON
=> {
instanceLocation
=>
'/request/uri/path'
,
keywordLocation
=> jsonp(
qw(/paths /foo/{foo_id})
),
absoluteKeywordLocation
=>
$doc_uri
->clone->fragment(jsonp(
qw(/paths /foo/{foo_id})
))->to_string,
error
=>
'provided path_template does not match request URI'
,
}),
],
},
'path_template is not consistent with request URI, captures not provided'
,
);
operation_id
=>
'my-get-path'
,
path_captures
=> {} }),
'find_path returns false'
);
cmp_deeply(
$options
,
{
request
=> isa(
'Mojo::Message::Request'
),
method
=>
'get'
,
path_captures
=> {},
operation_id
=>
'my-get-path'
,
operation_path
=>
'/paths/~1foo~1bar/get'
,
errors
=> [
methods(
TO_JSON
=> {
instanceLocation
=>
'/request/uri/path'
,
keywordLocation
=> jsonp(
qw(/paths /foo/bar)
),
absoluteKeywordLocation
=>
$doc_uri
->clone->fragment(jsonp(
qw(/paths /foo/bar)
))->to_string,
error
=>
'provided operation_id does not match request URI'
,
}),
],
},
'operation_id is not consistent with request URI'
,
);
operation_id
=>
'my-post-path'
,
path_captures
=> {
foo_id
=>
'goodbye'
} }),
'find_path returns false'
);
cmp_deeply(
$options
,
{
request
=> isa(
'Mojo::Message::Request'
),
method
=>
'post'
,
path_captures
=> {
foo_id
=>
'goodbye'
},
operation_id
=>
'my-post-path'
,
operation_path
=>
'/paths/~1foo~1{foo_id}/post'
,
errors
=> [
methods(
TO_JSON
=> {
instanceLocation
=>
'/request/uri/path'
,
keywordLocation
=> jsonp(
qw(/paths /foo/{foo_id})
),
absoluteKeywordLocation
=>
$doc_uri
->clone->fragment(jsonp(
qw(/paths /foo/{foo_id})
))->to_string,
error
=>
'provided path_captures values do not match request URI'
,
}),
],
},
'path_captures values are not consistent with request URI'
,
);
$openapi
= OpenAPI::Modern->new(
openapi_uri
=>
'/api'
,
openapi_schema
=>
$yamlpp
->load_string(
<<YAML));
$openapi_preamble
paths:
/foo: {}
YAML
path_template
=>
'/foo'
,
path_captures
=> {} }),
'find_path returns false'
);
cmp_deeply(
$options
,
{
request
=> isa(
'Mojo::Message::Request'
),
method
=>
'post'
,
path_template
=>
'/foo'
,
path_captures
=> {},
errors
=> [
methods(
TO_JSON
=> {
instanceLocation
=>
'/request/method'
,
keywordLocation
=> jsonp(
qw(/paths /foo post)
),
absoluteKeywordLocation
=>
$doc_uri
->clone->fragment(jsonp(
qw(/paths /foo post)
))->to_string,
error
=>
'missing operation for HTTP method "post"'
,
}),
],
},
'operation does not exist under /paths/<path-template>'
,
);
$openapi
= OpenAPI::Modern->new(
openapi_uri
=>
'/api'
,
openapi_schema
=>
$yamlpp
->load_string(
<<YAML));
$openapi_preamble
paths:
/foo/{foo_id}:
get:
operationId: my-get-path
YAML
path_template
=>
'/foo/{foo_id}'
}),
'find_path returns successfully'
,
);
cmp_deeply(
$options
,
{
request
=> isa(
'Mojo::Message::Request'
),
operation_id
=>
'my-get-path'
,
path_template
=>
'/foo/{foo_id}'
,
path_captures
=> {
foo_id
=>
'123'
},
method
=>
'get'
,
operation_path
=>
'/paths/~1foo~1{foo_id}/get'
,
errors
=> [],
},
'path capture values and method are extracted from the path template and request uri'
,
);
operation_id
=>
'my-get-path'
}),
'find_path returns successfully'
,
);
cmp_deeply(
$options
,
{
request
=> isa(
'Mojo::Message::Request'
),
operation_id
=>
'my-get-path'
,
path_template
=>
'/foo/{foo_id}'
,
path_captures
=> {
foo_id
=>
'123'
},
method
=>
'get'
,
operation_path
=>
'/paths/~1foo~1{foo_id}/get'
,
errors
=> [],
},
'path capture values are extracted from the operation id and request uri'
,
);
'find_path returns successfully'
);
cmp_deeply(
$options
,
{
request
=> isa(
'Mojo::Message::Request'
),
path_template
=>
'/foo/{foo_id}'
,
path_captures
=> {
foo_id
=>
'123'
},
method
=>
'get'
,
operation_id
=>
'my-get-path'
,
operation_path
=>
'/paths/~1foo~1{foo_id}/get'
,
errors
=> [],
},
'path_item and path_capture variables are returned in the provided options hash'
,
);
path_captures
=> {
foo_id
=>
'a'
} }),
'find_path returns false'
);
cmp_deeply(
$options
,
{
request
=> isa(
'Mojo::Message::Request'
),
method
=>
'get'
,
path_template
=>
'/foo/{foo_id}'
,
path_captures
=> {
foo_id
=>
'a'
},
errors
=> [
methods(
TO_JSON
=> {
instanceLocation
=>
'/request/uri/path'
,
keywordLocation
=> jsonp(
'/paths'
),
absoluteKeywordLocation
=>
$doc_uri
->clone->fragment(jsonp(
'/paths'
))->to_string,
error
=>
'provided path_captures values do not match request URI'
,
}),
],
},
'request URI is inconsistent with provided path captures'
,
);
'find_path returns false'
);
cmp_deeply(
$options
,
{
request
=> isa(
'Mojo::Message::Request'
),
method
=>
'get'
,
errors
=> [
methods(
TO_JSON
=> {
instanceLocation
=>
'/request/uri/path'
,
keywordLocation
=> jsonp(
'/paths'
),
absoluteKeywordLocation
=>
$doc_uri
->clone->fragment(jsonp(
'/paths'
))->to_string,
error
=>
'no match found for URI path "/bloop/blah"'
,
}),
],
},
'failure to extract path template and capture values from the request uri'
,
);
ok(
$openapi
->find_path(
$options
= {
request
=> request(
'GET'
,
$uri
),
path_template
=>
'/foo/{foo_id}'
,
path_captures
=> {
foo_id
=>
'hello // there ಠ_ಠ!'
} }),
'find_path returns successfully'
);
cmp_deeply(
$options
,
{
request
=> isa(
'Mojo::Message::Request'
),
method
=>
'get'
,
path_template
=>
'/foo/{foo_id}'
,
path_captures
=> {
foo_id
=>
'hello // there ಠ_ಠ!'
},
operation_id
=>
'my-get-path'
,
operation_path
=>
'/paths/~1foo~1{foo_id}/get'
,
errors
=> [],
},
'path capture variables are found to be consistent with the URI when some values are url-escaped'
,
);
ok(
$openapi
->find_path(
$options
= {
request
=> request(
'GET'
,
$uri
) } ),
'find_path returns successfully'
);
cmp_deeply(
$options
,
{
request
=> isa(
'Mojo::Message::Request'
),
operation_id
=>
'my-get-path'
,
path_captures
=> {
foo_id
=>
'hello // there ಠ_ಠ!'
},
path_template
=>
'/foo/{foo_id}'
,
method
=>
'get'
,
operation_path
=>
'/paths/~1foo~1{foo_id}/get'
,
errors
=> [],
},
'path captures can be properly extracted from the URI when some values are url-escaped'
,
);
$openapi
= OpenAPI::Modern->new(
openapi_uri
=>
'/api'
,
openapi_schema
=>
$yamlpp
->load_string(
<<YAML));
$openapi_preamble
paths:
/foo/{foo_id}/bar/{foo_id}:
get: {}
YAML
ok(!
$openapi
->find_path(
$options
= {
request
=>
$request
}),
'find_path returns false'
);
cmp_deeply(
$options
,
{
request
=> isa(
'Mojo::Message::Request'
),
method
=>
'get'
,
path_template
=>
'/foo/{foo_id}/bar/{foo_id}'
,
errors
=> [
methods(
TO_JSON
=> {
instanceLocation
=>
'/request/uri/path'
,
keywordLocation
=> jsonp(
'/paths'
),
absoluteKeywordLocation
=>
$doc_uri
->clone->fragment(jsonp(
'/paths'
))->to_string,
error
=>
'duplicate path capture name foo_id'
,
}),
],
},
'duplicate capture names are detected'
,
);
$openapi
= OpenAPI::Modern->new(
openapi_uri
=>
'/api'
,
openapi_schema
=>
$yamlpp
->load_string(
<<YAML));
$openapi_preamble
paths:
/foo/{foo_id}:
get: {}
YAML
ok(
$openapi
->find_path(
$options
= {
request
=>
$request
}),
'find_path returns successfully'
);
cmp_deeply(
$options
,
{
request
=> isa(
'Mojo::Message::Request'
),
path_template
=>
'/foo/{foo_id}'
,
path_captures
=> {
foo_id
=>
'bar'
},
method
=>
'get'
,
operation_path
=>
'/paths/~1foo~1{foo_id}/get'
,
errors
=> [],
},
'no path_template provided; no operation_id is recorded, because one does not exist in the schema document'
,
);
ok(
$openapi
->find_path(
$options
= {
request
=>
$request
,
path_template
=>
'/foo/{foo_id}'
}),
'find_path returns successfully'
);
cmp_deeply(
$options
,
{
request
=> isa(
'Mojo::Message::Request'
),
path_template
=>
'/foo/{foo_id}'
,
path_captures
=> {
foo_id
=>
'bar'
},
method
=>
'get'
,
operation_path
=>
'/paths/~1foo~1{foo_id}/get'
,
errors
=> [],
},
'path_template provided; no operation_id is recorded, because one does not exist in the schema document'
,
);
};
subtest
'no request is provided: options are relied on as the sole source of truth'
=>
sub
{
my
$openapi
= OpenAPI::Modern->new(
openapi_uri
=>
'/api'
,
openapi_schema
=>
$yamlpp
->load_string(
<<YAML));
$openapi_preamble
paths:
/foo/{foo_id}:
get:
operationId: my-get-path
YAML
like(
exception { ()=
$openapi
->find_path(
my
$options
= {
path_captures
=> {} }) },
qr/^at least one of \$options->\{request\}, \$options->\{method\} and \$options->\{operation_id\} must be provided/
,
'method can only be derived from request or operation_id'
,
);
ok(!
$openapi
->find_path(
my
$options
= {
operation_id
=>
'my-get-path'
,
method
=>
'POST'
,
path_captures
=> {} }),
'find_path returns false'
);
cmp_deeply(
$options
,
{
operation_id
=>
'my-get-path'
,
method
=>
'POST'
,
path_captures
=> {},
errors
=> [
methods(
TO_JSON
=> {
instanceLocation
=>
'/request/method'
,
keywordLocation
=> jsonp(
qw(/paths /foo/{foo_id} get)
),
absoluteKeywordLocation
=>
$doc_uri_rel
->clone->fragment(jsonp(
'/paths'
,
qw(/foo/{foo_id} get)
))->to_string,
error
=>
'wrong HTTP method POST'
,
}),
],
},
'no request provided; operation method does not match passed-in method'
,
);
like(
exception { ()=
$openapi
->find_path(
$options
= {
method
=>
'get'
,
path_captures
=> {} }) },
qr/^at least one of \$options->\{request\}, \$options->\{path_template\} and \$options->\{operation_id\} must be provided/
,
'path_template can only be derived from request or operation_id'
,
);
ok(!
$openapi
->find_path(
$options
= {
path_template
=>
'/foo/{foo_id}'
,
path_captures
=> {},
method
=>
'get'
}),
'find_path failed'
);
cmp_deeply(
$options
,
{
path_template
=>
'/foo/{foo_id}'
,
path_captures
=> {},
method
=>
'get'
,
operation_path
=>
'/paths/~1foo~1{foo_id}/get'
,
errors
=> [
methods(
TO_JSON
=> {
instanceLocation
=>
'/request/uri/path'
,
keywordLocation
=>
'/paths/~1foo~1{foo_id}'
,
absoluteKeywordLocation
=>
$doc_uri_rel
->clone->fragment(jsonp(
qw(/paths /foo/{foo_id})
))->to_string,
error
=>
'provided path_captures names do not match path template "/foo/{foo_id}"'
,
}),
],
},
'no request provided; path template does not match path captures'
,
);
ok(
$openapi
->find_path(
$options
= {
operation_id
=>
'my-get-path'
,
path_captures
=> {
foo_id
=>
'a'
} }),
'find_path succeeded'
);
cmp_deeply(
$options
,
{
operation_id
=>
'my-get-path'
,
path_captures
=> {
foo_id
=>
'a'
},
path_template
=>
'/foo/{foo_id}'
,
method
=>
'get'
,
operation_path
=>
'/paths/~1foo~1{foo_id}/get'
,
errors
=> [],
},
'no request provided; path_template and method are extracted from operation_id and path_captures'
,
);
ok(
$openapi
->find_path(
$options
= {
method
=>
'get'
,
path_template
=>
'/foo/{foo_id}'
,
path_captures
=> {
foo_id
=>
'a'
} }),
'find_path succeeded'
);
cmp_deeply(
$options
,
{
operation_id
=>
'my-get-path'
,
path_captures
=> {
foo_id
=>
'a'
},
path_template
=>
'/foo/{foo_id}'
,
method
=>
'get'
,
operation_path
=>
'/paths/~1foo~1{foo_id}/get'
,
errors
=> [],
},
'no request provided; operation_id are extracted from method and path_template'
,
);
$openapi
= OpenAPI::Modern->new(
openapi_uri
=>
'/api'
,
openapi_schema
=>
$yamlpp
->load_string(
<<YAML));
$openapi_preamble
paths:
/foo/{foo_id}:
get: {}
YAML
ok(
$openapi
->find_path(
$options
= {
method
=>
'get'
,
path_template
=>
'/foo/{foo_id}'
,
path_captures
=> {
foo_id
=>
'bar'
} }),
'find_path succeeded'
);
cmp_deeply(
$options
,
{
path_template
=>
'/foo/{foo_id}'
,
path_captures
=> {
foo_id
=>
'bar'
},
method
=>
'get'
,
operation_path
=>
'/paths/~1foo~1{foo_id}/get'
,
errors
=> [],
},
'no operation_id is recorded, because one does not exist in the schema document'
,
);
};
goto
START
if
++
$type_index
< @::TYPES;
done_testing;