-
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
Validate request body arguments #1489
Conversation
52fecaa
to
68ca845
Compare
68ca845
to
f2d1117
Compare
Done in 62ea12a. I updated the pull request description with additional tasks and also things out of scope. Next, I'll review the changes and look for places that need cleanup and/or better test coverage. |
@@ -178,16 +178,34 @@ function resolveControllerSpec(constructor: Function): ControllerSpec { | |||
if (!spec.components.schemas) { | |||
spec.components.schemas = {}; | |||
} | |||
if (p.name in spec.components.schemas) { | |||
// Preserve user-provided definitions |
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.
We don't have any test for this branch in openapi-v3
testsuite. Should we add one?
if (openapiSchema.definitions) { | ||
for (const key in openapiSchema.definitions) { | ||
spec.components.schemas[key] = openapiSchema.definitions[key]; | ||
// Preserve user-provided definitions | ||
if (key in outputSchemas) continue; |
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 implicitly tested by an acceptance test in @loopback/rest
. Should we add a more focused test to openapi-v3
too?
37b29df
to
267fd47
Compare
@jannyHou today, I cleaned up the implementation a bit and added few more tests, both at validation and unit level. As far as I am concerned, this pull request is good to be landed. Could you please review the final patch, perhaps get other team members to review it too, address any review feedback and land this pull request? I updated the pull request description with the next steps to work on after this PR is landed:
And also updated the list of things that are out of scope of DP3:
Could you please create follow-up issues where it makes sense? For example, I am not sure how to approach multiple content-type entries - do we actually support them in other places of the REST layer? Maybe we need an issue to address this aspect of OpenAPI spec holistically, not only for validation. |
@jannyHou last but not least - please preserve my authorship when cleaning up the git commit history, see https://help.github.com/articles/creating-a-commit-with-multiple-authors/ |
267fd47
to
f1ca281
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.
@bajtos your changes LGTM 👍 Thank you for refactoring the tests and added all those missing functionalities, a great effort and improvement!
*I could not approve my own PR that's why it's a comment, *
packages/rest/src/parser.ts
Outdated
if (contentType && !/json/.test(contentType)) { | ||
throw new HttpErrors.UnsupportedMediaType( | ||
`Content-type ${contentType} is not supported.`, | ||
); | ||
} | ||
|
||
return await parseJsonBody(request).catch((err: HttpError) => { | ||
debug('Cannot parse request body', err); |
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.
nitpick: debug('Cannot parse request body %j', err)
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 intentionally didn't use %j
because I want the error to be formatted using util.inspect
, not JSON.stringify
.
I think we should use %s
and inspect(err)
as we are already doing in other places?
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 will change it in #1495
@@ -23,6 +25,16 @@ export class RejectProvider implements Provider<Reject> { | |||
const err = <HttpError>error; | |||
const statusCode = err.statusCode || err.status || 500; | |||
writeErrorToResponse(response, err); | |||
|
|||
// Always log the error in debug mode, even when the application | |||
// has a custom error logger configured (e.g. in tests) |
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.
👍
if (!requestBodySpec) return; | ||
|
||
const content = requestBodySpec.content; | ||
// FIXME(bajtos) we need to find the entry matching the content-type |
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.
👍 Good catch, doesn't look a big code change, opened an issue to track it: #1494
Will try to address it in the next PR(in a reasonable time)
// A temporary solution for resolving schema references produced | ||
// by @loopback/repository-json-schema. In the future, we should | ||
// support arbitrary references anywhere in the OpenAPI spec. | ||
// See https://github.com/strongloop/loopback-next/issues/435 |
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.
👍
@@ -25,3 +30,16 @@ export function createUnexpectedHttpErrorLogger( | |||
); | |||
}; | |||
} | |||
|
|||
export function aBodySpec( |
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 the idea to create a function generating a body spec, and we can extend the function to allow multiple contents in story #1494
) { | ||
// workaround for Function.prototype.bind not preserving argument types | ||
function validateRequestBodyWithBoundArgs() { | ||
validateRequestBody(body, spec, schemas); |
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.
@bajtos Any reason why we don't run the unit tests with a mock request but testing validateRequestBody
directly? Is it because the e-2-e test takes the responsibility of testing a real request?
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.
Is it because the e-2-e test takes the responsibility of testing a real request?
Exactly.
Unit tests should be testing a single unit, in this case the function validateRequestBody
.
To verify how validateRequestBody
is integrated with the rest of REST layer, we can write few integration or acceptance tests. In this case, we have acceptance tests.
See also https://loopback.io/doc/en/lb4/Defining-your-testing-strategy.html
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.
LGTM
Co-authored-by: Miroslav Bajtos <[email protected]>
f1ca281
to
a66ebb4
Compare
Here is the end-user experience I am looking for: Imagine you are building a single-page web application with a registration form where the user enters email, password, name, etc. When they submit the form, the page makes a REST request to LB4 backend. When the request fails because of a validation error, the page needs to tell user what went wrong and how to fix the filled data. To do so, the page needs to know:
See how LB3.x/Juggler validation works and the example of a ValidationError shown here: https://loopback.io/doc/en/lb3/Validating-model-data.html#localizing-validation-messages From what I remember, AJV provides additional structured data about the validation errors, we just need to find out where it is stored (I think it's |
I know this is an example but for a login form it's bad practice to point out the field in which the error / validation failed due to security implications / hints it provides. For a registration form however it's not the case and individual fields should be pointed out. -- Something to keep in mind once we work on auth / User model. |
@virkt25 I agree that hiding sensitive info in response is important. While wondering should we do it or the lb app developer do it by filtering the full response data/error. IMO we should do the default error handling of "auth / User model", while for other cases, I prefer to let developers to filter the error. We can make it easy to add an action that applies the customized error filter. Feel free to move the discussion to #1511, up to you :) |
@virkt25 Do note: Most sites already expose whether an a username/email is already registered on the registration page, so
only applies to a subset of systems which do not have any public registration page. |
related to #118
Finished items are checked in #118 (comment)
Checklist
openapiSpec.components.schemas
Next steps
Open follow-up pull requests for the following items:
Edge cases
More descriptive error object including machine-readable details
Nice to have: unit-tests in openapi-v3 to verify that user-provided definitions are preserved - see the comments below
Out of scope of this initial pull request and DP3
requestBody.content
map (e.g.application/json
vs.application/xml
)$ref
references (see [SPIKE] Resolve$ref
in api/controller/route specs #435)