Skip to content

Empty body sent to POST that expects JSON, if all parameters are omitted #7178

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

Open
hniksic opened this issue May 15, 2025 · 0 comments
Open
Labels
feature/openapi-parsing The parsing logic for OpenAPI product/sdk-generator Fern's SDK Generator that outputs client libraries in 7 languages

Comments

@hniksic
Copy link

hniksic commented May 15, 2025

Which Fern component?

SDK Generator

How urgent is this?

P0 - Critical (Blocking work)

What's the issue?

Summary: When you define a POST endpoint that accepts a JSON body, and all its fields are optional, fern-generated SDK sends empty body to the endpoint, instead of sending the expected {}.

Details:

We define an OpenAPI schema with a POST endpoint that accepts a JSON object with all fields optional. Something like:

"requestBody": {
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "properties": {
          "ids": {
            "description": "...",
            "type": ["array", "null"],
            "items": {
              "type": "integer",
              "format": "uint32",
              "minimum": 0.0
            }
          },
          "end_date": {
            "description": "...",
            "type": "string",
            "format": "date"
          },
          ...
        }
      }
    }
  },
  "required": true
},
...

Note, in particular, that none of the fields is marked as required, so they're all optional. The generated Python API respects that, and creates a function such as:

    def search_campaigns(
        self,
        *,
        ids: typing.Optional[typing.Sequence[int]] = OMIT,
        end_date: typing.Optional[str] = OMIT,
        ...
    ) -> PaginatedForCampaign:

Calling the function works as expected as long as I pass at least one parameter. But, if I call it without parameters, the backend complains of EOF while parsing JSON in request body. Adding further debugging to the backend shows that, indeed, an empty (zero-bytes) body is being sent, which is not valid JSON.

That's a bit surprising, because the generated code certainly looks like it's doing the right thing:

        _response = self._client_wrapper.httpx_client.request(
            "campaigns/processed/campaign",
            method="POST",
            json={
                "ids": ids,
                "end_date": end_date,
                ...
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )

However, HttpClient.request generates JSON body by calling:

json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit)

and get_request_body does this:

    json_body = None
    data_body = None
    if data is not None:
        data_body = maybe_filter_request_body(data, request_options, omit)
    else:
        # If both data and json are None, we send json data in the event extra properties are specified
        json_body = maybe_filter_request_body(json, request_options, omit)

    # If you have an empty JSON body, you should just send None
    return (json_body if json_body != {} else None), data_body if data_body != {} else None

The last line is causing the issue. "Just sending None" means sending empty POST body instead of the expected {}. An empty document isn't even JSON, and this will cause problems on any POST endpoint that parses JSON with a JSON parser. For example, in Python:

>>> import json
>>> json.loads('{}')
{}
>>> json.loads('')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/nix/store/ibz5rabb9z7hl5vy4w57q8dil5fqysjm-python3-3.11.11/lib/python3.11/json/__init__.py", line 346, in loads
    return _default_decoder.decode(s)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/nix/store/ibz5rabb9z7hl5vy4w57q8dil5fqysjm-python3-3.11.11/lib/python3.11/json/decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/nix/store/ibz5rabb9z7hl5vy4w57q8dil5fqysjm-python3-3.11.11/lib/python3.11/json/decoder.py", line 355, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

Could this behavior be changed? Or, if it is needed to make something else work, could it be made optional?

Fern CLI & Generator Versions

Fern CLI version: 0.57.17

@dannysheridan dannysheridan added product/sdk-generator Fern's SDK Generator that outputs client libraries in 7 languages feature/openapi-parsing The parsing logic for OpenAPI labels May 18, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature/openapi-parsing The parsing logic for OpenAPI product/sdk-generator Fern's SDK Generator that outputs client libraries in 7 languages
Development

No branches or pull requests

2 participants