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

Do not set content-type on body-less requests #1826

Merged

Conversation

goce-cz
Copy link
Contributor

@goce-cz goce-cz commented Aug 7, 2024

Changes

Resolves #1694

Important: This PR touches the same code as #1825 and in-fact includes the same fix by itself. I recommend merging #1825 first.

This PR prevents Content-Type header from being set on requests with no body. This is required to ensure compatibility with strict server impls. such as fastify.

How to Review

  • Consider checking the included tests and docs to understand the desired behavior.
  • The change in the implementation is rather simple.

Checklist

  • Unit tests updated
  • docs/ updated (if necessary)
  • pnpm run update:examples run (only applicable for openapi-typescript)

@goce-cz goce-cz requested a review from a team as a code owner August 7, 2024 10:58
Copy link

changeset-bot bot commented Aug 7, 2024

🦋 Changeset detected

Latest commit: 1812aff

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
openapi-fetch Minor
openapi-react-query Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Comment on lines 153 to 154
or when instantiating the client. Setting `Content-Type` to `null` will omit the header, however the native fetch API
will likely set it to `text/plain` in this case.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure we really need to mention the null thing anymore, when this behaves properly now. 🤷‍♂️

Opinions?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah just remove it 🔪

Copy link
Contributor Author

Choose a reason for hiding this comment

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

BTW Just a thought... I wonder whether allowing the bodySerializer to return {contentType: "...", body: ...} wouldn't be the best way to allow transport customization 🤔

Comment on lines +900 to +938
const BODIES = [{ prop: "a" }, {}, "", "str", null, false, 0, 1, new Date("2024-08-07T09:52:00.836Z")] as const;
const METHOD_BODY_COMBINATIONS = BODY_ACCEPTING_METHODS.flatMap(([method]) =>
BODIES.map((body) => [method, body] as const),
);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@kerwanp Guessing from #1825 (comment) you won't like this 🤣

Happy to ditch the methods, but I'd rather keep the various bodies as this was broken until #1825.

Copy link
Contributor

Choose a reason for hiding this comment

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

Aha I'm not against this when behavior have to be different between cases, the pattern you use looks good to me! The methods constants could be moved at the top of the file to be reused in other tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Until they are actually reused in other tests, they IMO make more sense when they are collocated with the test itself. It's just less scrolling to understand how they are used. Anyway this is just my opinion I've grown over the years, nothing I would fight for.

I guess we are in agreement that this file already does too much and deserves some larger refactoring and splitting. Let's maybe open a separate issue and discuss a bit what the desired structure could look like?

Copy link
Contributor

@kerwanp kerwanp left a comment

Choose a reason for hiding this comment

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

Great work! Same thing as the other pull request, I'll let @drwpow review this

// the fetch implementation won't allow sending a body without content-type,
// so it invents one up and sends it, hopefully this will be consistent across
// local environments and won't make the tests flaky
expect(contentType).toBe("text/plain;charset=UTF-8");
Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah this one is a bit scary, it's a potential IWOMM scenario.
It seems the test ensure that the default content-type is basically removed?

Maybe we can simply do something like this?

Suggested change
expect(contentType).toBe("text/plain;charset=UTF-8");
expect(contentType).not.toBe("application/json");

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, that sounds better. Good idea.

Comment on lines +900 to +938
const BODIES = [{ prop: "a" }, {}, "", "str", null, false, 0, 1, new Date("2024-08-07T09:52:00.836Z")] as const;
const METHOD_BODY_COMBINATIONS = BODY_ACCEPTING_METHODS.flatMap(([method]) =>
BODIES.map((body) => [method, body] as const),
);
Copy link
Contributor

Choose a reason for hiding this comment

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

Aha I'm not against this when behavior have to be different between cases, the pattern you use looks good to me! The methods constants could be moved at the top of the file to be reused in other tests.

@@ -859,6 +855,138 @@ describe("client", () => {
});
});

describe("content-type", () => {
const BODY_ACCEPTING_METHODS = [["PUT"], ["POST"], ["DELETE"], ["OPTIONS"], ["PATCH"]] as const;
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Is there a reason to have two dims arrays here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is what the each method officially expects. It's a list of param sets where each set can consist of multiple params. The API supports a flat array, where [x] is treated as [[x]], but I don't really fancy these magical interfaces and prefer to be explicit. Imagine what happens when x is actually an array?

@kerwanp kerwanp added the openapi-fetch Relevant to the openapi-fetch library label Aug 10, 2024
@drwpow
Copy link
Contributor

drwpow commented Aug 12, 2024

See Martin’s comments/questions on tests. The implementation seems to solve the original issue. But in order to approve/merge we’ll need a patch changeset (see comment). Thanks for contributing!

@goce-cz
Copy link
Contributor Author

goce-cz commented Aug 14, 2024

But in order to approve/merge we’ll need a patch changeset (see comment).

I'll address this once I'm back from my vacation. Thanks!

@goce-cz
Copy link
Contributor Author

goce-cz commented Aug 28, 2024

@drwpow should this really be a patch release? IIRC, in the issue, you mentioned that you would release it as a breaking change, just to be sure.

@drwpow
Copy link
Contributor

drwpow commented Aug 29, 2024

@drwpow should this really be a patch release? IIRC, in the issue, you mentioned that you would release it as a breaking change, just to be sure.

So I did 😄! Thank you, yes this should be a minor (“breaking”) release.

@goce-cz goce-cz force-pushed the 1694-do-not-set-content-type-on-undefined-body branch from e2779cd to 502cc70 Compare August 30, 2024 07:57
@goce-cz
Copy link
Contributor Author

goce-cz commented Aug 30, 2024

minor (“breaking”)

Unfamiliar combination 😄 , but here you go.

@drwpow
Copy link
Contributor

drwpow commented Aug 30, 2024

minor (“breaking”)

Unfamiliar combination 😄 , but here you go.

Thanks! This is actually following standard semver. Pre-1.0, the minor almost always signals breaking changes. Technically you can choose to either put features in minor releases or patches, but I choose the latter so a minor predictably means some breaking change.

@goce-cz goce-cz force-pushed the 1694-do-not-set-content-type-on-undefined-body branch from 502cc70 to fdeecfe Compare August 30, 2024 13:50
@goce-cz
Copy link
Contributor Author

goce-cz commented Aug 30, 2024

Pre-1.0, the minor almost always signals breaking changes.

This didn't make sense to me, until I stopped looking at (and releasing 🤦 ) a wrong package. (openapi-typescript v7).

Rebased and hopefully ready for merging.

Copy link
Contributor

@drwpow drwpow left a comment

Choose a reason for hiding this comment

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

Great addition and tests, thank you!

`Content-Type` is omitted, allowing the browser to set it automatically with the correct message part boundary.

You can also set `Content-Type` manually through `headers` object either in the fetch options,
or when instantiating the client.
Copy link
Contributor

Choose a reason for hiding this comment

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

I forget if I said this already, but thanks for adding this! Little docs improvements go a long way and are always welcome ❤️

@drwpow drwpow merged commit b893c44 into openapi-ts:main Aug 30, 2024
8 checks passed
@github-actions github-actions bot mentioned this pull request Aug 30, 2024
@goce-cz goce-cz deleted the 1694-do-not-set-content-type-on-undefined-body branch August 30, 2024 19:14
@bkis
Copy link

bkis commented Sep 20, 2024

The documentation states

For convenience, openapi-fetch sets Content-Type to application/json automatically for any request that provides value for the body parameter. When the bodySerializer returns an instance of FormData, Content-Type is omitted, allowing the browser to set it automatically with the correct message part boundary.

You can also set Content-Type manually through headers object either in the fetch options, or when instantiating the client.

It seems like I actually can't set the header manually anymore. I have a fetch options object

{
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    bodySerializer: (body: Record<string, string | number | boolean | undefined | null>) => {
      return new URLSearchParams(
        Object.entries(body).map(([key, value]) => [String(key), String(value)])
      ).toString();
    },
  }

...but the Content-Type header is omitted. This happens since these changes were made. When returning a FormData object in the bodySerializer, the API I target doesn't understand the request.
It does work with the above options including the header, though. How can I force this header to be set?

EDIT: Weirdly, this seems to happen since 0.9.x. Sorry – it may not be because of this PR as it was merged and released with 0.12.0, right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
openapi-fetch Relevant to the openapi-fetch library
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Empty body should not result in Content-Type header being set
4 participants