Skip to content
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

Merged
merged 41 commits into from Oct 1, 2019
Merged

Conversation

karol-maciaszek
Copy link
Contributor

@karol-maciaszek karol-maciaszek commented Sep 25, 2019

Checklist

  • Tests have been added (for bug fixes / features)
  • Docs have been added / updated (for bug fixes / features)

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 qs package 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.

@karol-maciaszek karol-maciaszek added t/bug WIP Work In Progress labels Sep 25, 2019
@karol-maciaszek karol-maciaszek self-assigned this Sep 25, 2019
…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
@karol-maciaszek karol-maciaszek changed the title [WIP] fix: proper form data decoding fix: proper form data decoding Sep 27, 2019
@karol-maciaszek karol-maciaszek removed the WIP Work In Progress label Sep 27, 2019
@XVincentX
Copy link
Contributor

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

@karol-maciaszek
Copy link
Contributor Author

karol-maciaszek commented Sep 30, 2019

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: request-validation, request-deserialize.
Former checks req against spec, latter deals with all those crazy serialization styles.

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(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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(

Copy link
Contributor

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.

@philsturgeon
Copy link
Collaborator

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 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.

Copy link
Contributor

@XVincentX XVincentX left a 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(
Copy link
Contributor

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(/[\/?#\[\]@!$&'()*+,;=]/)) {
Copy link
Contributor

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([]);
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could

  1. add a failing test
  2. Break down this super test into a set of sub tests? (nested, array…)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. added failing test
  2. 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))),
Copy link
Contributor

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),
Copy link
Contributor

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!)),
Copy link
Contributor

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.

XVincentX and others added 4 commits October 1, 2019 16:36
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)),
Copy link
Contributor

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);
Copy link
Contributor

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.

Copy link
Contributor

@XVincentX XVincentX left a 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.

@XVincentX XVincentX merged commit 9b0763e into master Oct 1, 2019
@XVincentX XVincentX deleted the fix/proper-form-data-decoding branch October 1, 2019 16:52
@karol-maciaszek
Copy link
Contributor Author

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants