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

Detect and serialize objects with null prototype; provide shouldJSONSerializeBody hook to override behavior #90

Merged
merged 12 commits into from
Nov 30, 2022

Conversation

trevor-scheer
Copy link
Member

@trevor-scheer trevor-scheer commented Nov 11, 2022

This PR refines the body serialization conditional to be more straightforward (rather than a confusing mix of do's and don'ts). Bodies are JSON serialized if:

  • It's an array or a plain object
  • It has a .toJSON() function on it (with the exception of Buffer which we do not want to stringify)

Lodash utilities are widely used and well-tested, so I went with the isPlainObject implementation they provide in order to correctly detect objects. The existing conditional falls short for objects with a null prototype.

For users desiring to override this behavior, we've provided the new protected method shouldJSONSerializeBody. This method should return a boolean in order to tell RESTDataSource whether or not to call JSON.stringify on the body in question.

Some implementations of fetch support different data types (such as FormData). You're welcome to use those, but it's your responsibility to use libraries that are compatible with each other, i.e. node-fetch is compatible with form-data.

Fixes #67
Fixes #68
Ref: apollographql/apollo-server#5070

@codesandbox-ci
Copy link

codesandbox-ci bot commented Nov 11, 2022

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

@aeddie-zapidhire
Copy link

Looks good to me. Only comment I might possibly make is that https://github.com/sindresorhus/is-plain-obj could be a smidge cleaner and more performant.

@glasser
Copy link
Member

glasser commented Nov 14, 2022

What about taking the opposite approach: serialize anything truthy that is not a string or Buffer?

@glasser
Copy link
Member

glasser commented Nov 14, 2022

(That's a good fit for our FetcherRequestInit body?: string | Buffer: anything that can't go directly here (ie, not string or Buffer or undefined) gets treated as JSON. So probably "not undefined" rather than "truthy" in my previous suggestion.)

@trevor-scheer
Copy link
Member Author

trevor-scheer commented Nov 15, 2022

@glasser that change will fail one of the existing tests (that I fixed in this PR):
does not serialize a request body that is not an object

There's some additional context, particularly in these 2:
apollographql/apollo-server#1316
apollographql/apollo-server#5070

So...if you still think we should go that route I think we'd have to add some error handling around the stringify call and possibly add a hook for handling those errors?

@glasser
Copy link
Member

glasser commented Nov 17, 2022

Hmm. Should we consider just explicitly supporting form-data in Fetcher (even though the Apollo Server/Gateway use cases don't need it)? Would require us to have a reasonably portable way of describing a FormData in TypeScript and a reasonable level of confidence that all the important implementations support it with portability between FormData implementations. (My vague memory is of looking into this at some point and deciding that they do mostly support it.)

It just seems odd to introduce a bunch of complexity just in order to ensure we can pass a FormData to an API that is defined as only taking string or Buffer.

@trevor-scheer trevor-scheer force-pushed the trevor/detect-object-null-prototype branch from 7763c99 to 941f1b6 Compare November 30, 2022 18:11
Simplify the conditional to the inverse:
Serialize if...
* it's an array or plain object
* if it has a toJSON() property on it (and isn't a Buffer)

Test explicitly that FormData is passed through rather than
all non-plain-objects. Passing in a random class is unexpected
unless it's meant to be serialized with a toJSON function.
The logic to decide on JSON serialization is currently baked in
to RESTDataSource. In case customization is desired, we've provided
this overridable method as an escape hatch for other use cases.
@trevor-scheer trevor-scheer changed the title Detect and serialize objects with null prototype Detect and serialize objects with null prototype; provide shouldJSONSerializeBody hook to override behavior Nov 30, 2022
// We accept arbitrary objects and arrays as body and serialize them as JSON.
(
Array.isArray(body) ||
isPlainObject(body) ||
Copy link
Member

Choose a reason for hiding this comment

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

i still kinda feel like "anything but string or Buffer" or "anything but string, Buffer, or FormData" would be a better default — the goal of this is to produce something that the Fetch API understands, and so it feels like we should serialize anything other than what Fetch understands.

On the other hand, real Fetch APIs also take other stuff like readable streams, URLSearchParameters, TypedArray, Blob, etc, and perhaps your choice here is better for those things (and we're already moving away from "serialize anything that our Fetcher interface doesn't expect" due to FormData).

So I guess this does seem fine, given that it's just a default people can change.

src/RESTDataSource.ts Outdated Show resolved Hide resolved
@trevor-scheer trevor-scheer merged commit b66da37 into main Nov 30, 2022
@trevor-scheer trevor-scheer deleted the trevor/detect-object-null-prototype branch November 30, 2022 22:26
@github-actions github-actions bot mentioned this pull request Nov 30, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

RESTDataSource - cannot convert object to primitive Evaluate AS PR 5070 (null prototype issue)
3 participants