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
fix: proper form data decoding #649
Conversation
…to feat/allow-reserved
…to feat/allow-reserved
…er-form-data-decoding # Conflicts: # CHANGELOG.md # packages/http/src/validator/validators/body.ts # test-harness/specs/parameters-ac7.oas2.txt # test-harness/specs/parameters-ac8.oas2.txt # test-harness/specs/validate-body-params/form-byte-format-ok.oas2.txt # test-harness/specs/validate-body-params/form-data-invalid-request.oas2.txt # test-harness/specs/validate-body-params/form-data-invalid-request.oas3.txt
|
Now that I think about it, would it make sense to extract this logic into a different/external library? I am quite sure this could be used somewhere else in Stoplight @philsturgeon @marbemac |
I think about two library candidates: At least request-deserialization is a unique code. At the time of writing (1yr ago), there was no similar tool. |
| target: any, | ||
| ): Either.Either<IPrismDiagnostic[], any> { | ||
| if ( | ||
| Option.getEq(fromEquals((a: string, b: string) => typeIs.is(a, b) as boolean)).equals( |
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.
| Option.getEq(fromEquals((a: string, b: string) => typeIs.is(a, b) as boolean)).equals( | |
| Option.getEq(fromEquals((a: string, b: string) => !!typeIs.is(a, b))).equals( |
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.
Yes, please let's void casts.
I think we could hold off for a while on doing this. We have talked a bit recently about how the validation logic in general needs to be refactored, about how middlewares could be useful, and about how it could become its own Lerna package. I think all of these things could be done together in the future while we refactor things, and maybe it could become its own package outside of the repo. Either way we can kick that can down the road whilst we sort out Prism more IMO. |
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.
@StefanDywersant
Ok I took a very deep view at this PR to understand the Either situation and this validator is actually really difficult to manage properly.
As you probably noticed, this is not just validating the target, but it is also mutating the target — and then at the same time it's not really doing anything useful with it. And so while we should be using Either, for our particular use case (and make it compatible with the rest of the code base) we need to convert back to an Option
In general, reasoning about this function is hard and that led you to make some mistakes. I've pointed out some of them but iterating on this would have been just infinite and so I took some time and refactored (and prepared a PR) -> https://github.com/stoplightio/prism/pull/667/files
If you're ok with it, take some time to take a look at it and ask all the questions you want. If you won't be able to understand anything it's just fine; keep in mind that my code is still horrible and it's just trying to put some minimal order into something that needs to be refactored.
With the ball in the future, I would say the validator was nor worth converting, for the current state of the code.
| target: any, | ||
| ): Either.Either<IPrismDiagnostic[], any> { | ||
| if ( | ||
| Option.getEq(fromEquals((a: string, b: string) => typeIs.is(a, b) as boolean)).equals( |
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.
Yes, please let's void casts.
| const property = encoding.property; | ||
| const value = encodedUriParams[property]; | ||
|
|
||
| if (!allowReserved && typeof value === 'string' && value.match(/[\/?#\[\]@!$&'()*+,;=]/)) { |
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 would say we're trying to employ fp concepts where it makes sense; it does not have to be imperative and more importantly it's not a purity race. If it makes sense and it helps us to have a more predictable code — let's go for it.
| ), | ||
| ).toEqual([]); | ||
| }); | ||
| }); |
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.
Maybe we could
- add a failing test
- Break down this super test into a set of sub tests? (nested, array…)
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.
- added failing test
- I simplified the test to only ensure that deserializer works properly
| return pipe( | ||
| Either.fromNullable([])(schema), | ||
| Either.map(() => target), | ||
| Either.chain(partial(maybeValidateFormBody, schema!, content, Option.fromNullable(mediaType))), |
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 reason why we can't really use partial here is that we need to use the schema that's returned from the Either.fromNullable computation, not the one coming from the function parameters (which can be null and you're effectively using the ! to make it appear ok)
| } | ||
| return pipe( | ||
| Either.fromNullable([])(schema), | ||
| Either.map(() => target), |
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.
target is already available from the function parameter and does not need any change or "fp-isation", so we can just use that directly.
| return pipe( | ||
| schema ? Either.right(target) : Either.left([]), | ||
| Either.chain(partial(maybeValidateFormBody, schema!, content, mediaType)), | ||
| Either.chain(partial(validateBody, 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.
As I mentioned in the previous comment, the problem is that we need to use the schema parameter coming from the chain, not from the function's parameters, where the ! is required.
* refactor: stuff * refactor: separate in functions * refactor: correct things * file: restore
| Option.fromPredicate(mt => !!!typeIs.is(mt, ['application/x-www-form-urlencoded'])), | ||
| Option.chain(() => Option.some(validateBody(schema, target))), | ||
| Option.fromPredicate(mt => !typeIs.is(mt, ['application/x-www-form-urlencoded'])), | ||
| Option.map(() => validateBody(schema, target)), |
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.
Oh very nice catch, I didn't realise it!
| } | ||
|
|
||
| function validateBody(schema: JSONSchema, target: any): IPrismDiagnostic[] { | ||
| return validateAgainstSchema(target, 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.
We can probably remove this redundant thing.
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.
Thanks a lot for this, I hope it was not that painful to make it happen. I'm merging when tests will pass.
I learned lots while working on this + thank you for guiding while doing so! |
Checklist
What kind of change does this PR introduce?
Bugfix
What is the current behavior? What is the new behavior?
Currently, body encoded with form-data is improperly decoded. We are using
qspackage which is not capable of decoding all styles, especially those described in encoding > style.The new behavior is that we support all encoding styles through our deserializers.
Does this PR introduce a breaking change?
No.