-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: spike on API Explorer supporting complex objects in query param #4141
Conversation
b9d1dc3
to
7f6f73d
Compare
38e4f3e
to
2649c50
Compare
Thank you for providing test instruction and a screenshot of the outcome. Just FYI, the instructions could be simplified by taking one of our example applications (e.g. |
It's great to see that swagger-ui can handle json-encoded query parameters. Now we need to carefully consider backwards compatibility, see #3770 (comment).
|
@@ -189,7 +189,7 @@ describe('TodoApplication', () => { | |||
|
|||
await client | |||
.get('/todos') | |||
.query({filter: {where: {isComplete: false}}}) | |||
.query({filter: encodeURIComponent(JSON.stringify({where: {isComplete: false}})) }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally, both flavors should be supported:
filter: {where: {isComplete: false}}
filter: encodeURIComponent(JSON.stringify(...))
Instead of changing this existing test, we should add a new test to verify the other flavor. I believe these two flavors are already supported now, in which case it would be great to have tests to verify all different ways how to provide the filter
argument.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(This code change & discussion thread is directly related to my previous comment about backwards compatibility.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An idea to consider & try: can we mix deepObject
style with json
style in a single parameter? Something along the following lines:
object: function(
name: string,
schema: SchemaObject | ReferenceObject = {
type: 'object',
additionalProperties: true,
},
spec?: Partial<ParameterObject>,
) {
schema = {
type: 'object',
...schema,
};
return param({
name,
in: 'query',
// deepObject
style: 'deepObject',
explode: true,
// new addition - json encoding
content: {
"application/json": {
schema
}
},
// the rest
schema,
...spec,
});
},
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 Let's make sure the default signature filter: {where: {isComplete: false}}
still works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally, both flavors should be supported:
filter: {where: {isComplete: false}}
filter: encodeURIComponent(JSON.stringify(...))
@bajtos while using super-test both the above are the same , ie, using encodeURIComponent(JSON.stringify(
for super test is redundant, while for other http client invocations it is required. sending just filter: {where: {isComplete: false}}
in url will not work.
I suppose you are asking to support exploded query params like filter[limit]=1
Instead of changing this existing test, we should add a new test to verify the other flavor. I believe these two flavors are already supported now, in which case it would be great to have tests to verify all different ways how to provide the
filter
argument.
yes both the exploded and complex objects are already supported in loopback, even without the changes in this PR. The suggested changes in this PR is only for API Explorer to support complex objects.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Last but not least, please check why Travis CI is failing and try to make all tests pass. I see the following compilation error, there is also a linting error.
|
2649c50
to
91980b6
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@deepakrkris Great findings! commented. The solution is elegant and LGTM in general.
* @param name - Parameter name | ||
* @param schema - Optional OpenAPI Schema describing the JSON object. | ||
*/ | ||
jsonObject: function( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The solution is cool!
Implementation details...I think we don't need a new decorator to support the multi contents, @bajtos 's comment is sth we could try https://github.com/strongloop/loopback-next/pull/4141/files#r347767523.
Or provide the content
object as the 3rd parameter spec?: Partial<ParameterObject>
@deepakrkris You may want to check is it a valid spec to have schema
and content
both defined, like:
// new addition - json encoding
content: {
"application/json": {
schema
}
},
// the rest
// @jannyHou: Is it valid to have both `schema` and `content` defined?
schema,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And I feel the new signature of param.query.object()
would be very similar to @requestBody
, which reminds me to have a chat with @bajtos on #3493, I will schedule a meeting asap and update our conclusion here. Anyway the spike is good enough to me :) We can determine the signature in the follow-up stories.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jannyHou I have modified existing decorator with backward compatibility and also fix API Explorer for complex objects in query params.
@@ -189,7 +189,7 @@ describe('TodoApplication', () => { | |||
|
|||
await client | |||
.get('/todos') | |||
.query({filter: {where: {isComplete: false}}}) | |||
.query({filter: encodeURIComponent(JSON.stringify({where: {isComplete: false}})) }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 Let's make sure the default signature filter: {where: {isComplete: false}}
still works.
cd00442
to
303d3ff
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great, 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I love this solution too ❤️
Do we have any automated tests to verify that our applications are emitted valid OpenAPI spec documents? Can you please double-check?
From what I remember, there are two OpenAPI validators and sometimes one of them reports error for spec documents that are considered as valid by the other.
When I paste the spec for examples/todo-list
to Swagger Editor (see https://editor.swagger.io), I get several "structural errors" reported, presumably one for each use of the new query param object style:
Structural error at paths./todos.get.parameters.0
should have either a `schema` or `content` property
Jump to line 868
The same error is reported by swagger validator (see https://apitools.dev/swagger-parser/online/):
Swagger schema validation failed.
Data does not match any schemas from 'oneOf' at #/paths//todos/get/parameters/0
Data matches schema from 'not' at #/paths//todos/get/parameters/0
Missing required property: $ref at #/paths//todos/get/parameters/0
JSON_OBJECT_VALIDATION_FAILED
See also https://github.com/OAI/OpenAPI-Specification/blob/3.0.1/versions/3.0.1.md#parameter-object, emphasis is mine:
The rules for serialization of the parameter are specified in one of two ways. For simpler scenarios, a schema and style can describe the structure and syntax of the parameter.
(...)
For more complex scenarios, the content property can define the media type and schema of the parameter. A parameter MUST contain either a schema property, or a content property, but not both. When example or examples are provided in conjunction with the schema object, the example MUST follow the prescribed serialization strategy for the parameter.
Let's find a way how to emit OpenAPI spec that's valid.
await givenTodoInstance(todoRepo, {title: 'todo2', todoListId: list.id}); | ||
await givenTodoInstance(todoRepo, {title: 'todo3', todoListId: list.id}); | ||
|
||
const response = await client.get('/todos').query('filter[limit]=2'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a bit redundant, because query({filter: {limit: 2}})
is automatically converted by supertest to ?filter[limit]=2
. Having said that, I think it's a good idea to have tests that are explicitly verifying different encoding styles, as opposed to the current status, where such encodings are tested implicitly, see:
Writing such tests is out of scope of this spike, but please do include a task/todo-item in the acceptance criteria of the story that will track the real implementation.
Considering that runtime supports both This can break existing applications (for example when using clients code-generated by a tool that may not support json encoding), therefore this change needs to be published as semver-major of Thoughts? /cc @strongloop/loopback-next |
@bajtos I am glad you verified manually that
Sounds good to me.
Fair enough to have a semver-major release to support querying nested filter. |
@bajtos @jannyHou , I have fixed the parameter spec. removed style , explode and schema fields. Now schema passes https://editor.swagger.io/ . But there are additional changes required in loopback to support extracting param schema from |
@bajtos I have also removed the |
3355247
to
5604f7b
Compare
5604f7b
to
9218d22
Compare
All the suggestions are implemented and tested and the CI passes, so I am closing this PR. findings of this spike are updated in the spike PR as well as the linked original issue. |
if (!spec.schema && spec.content) { | ||
const content = spec.content; | ||
const jsonSpec = content['application/json']; | ||
spec.schema = jsonSpec.schema; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is ok in a spike, but please note that the real implementation must not mutate (modify) the spec
object given to us by the caller.
#3770
Scope:
Currently loopback supports both exploded and uri encoded complex objects in query params, even without the changes suggested in this PR. The purpose of this PR is to ONLY fix API Explorer to support complex objects in query params.
To test this spike,