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

Params by plug convention should contain merged path, body and query parameters #588

Closed
hamir-suspect opened this issue Jan 3, 2024 · 6 comments · Fixed by #589
Closed

Comments

@hamir-suspect
Copy link
Contributor

According to Plug documentation conn.params is

the request params, the result of merging the :path_params on top of :body_params on top of :query_params

When using OpenApiSpex.Plug.CastAndValidate plug the params will overwrite only with params from :path | :query | :header | :cookie meaning that there is never parameters from request_body there.

This should be clearly stated in CastAndValidate plug like it is for cast plug as it can cause some confusion.

@mbuhot
Copy link
Collaborator

mbuhot commented May 12, 2024

The library was initially implemented with all params merged into conn.params as per the docs. Unfortunately it is problematic with struct based body schemas.

If your body schema is a struct, the adding additional query params creates a struct with undeclared fields set. It technically works since structs are just maps, but it is confusing and may cause type errors in future elixir versions.

@mbuhot mbuhot closed this as completed May 12, 2024
@PJUllrich
Copy link

If I may, I'd like to add a +1 here that this is confusing and I'd argue to revert it. I don't care whether the request_body is a struct or a map, but I do care that OpenApiSpex doesn't break the Phoenix convention of merging all parameters into params. Now, I need to add a custom plug to merge them all back together again because I don't want to rely on conn.body_params but continue the Phoenix custom of having all params in the second argument of a def action(conn, params) call.

@PJUllrich
Copy link

PJUllrich commented Mar 4, 2025

FYI for whomever is interested, this plug merges the params again. Just add it behind the CastAndValidate plug:

defmodule JadeWeb.Plugs.MergeParamsFromOpenApiSpex do
  @moduledoc """
  Merges the `body_params` into the `params` field if the request was validated by OpenApiSpex.

  For context, see: https://github.com/open-api-spex/open_api_spex/issues/588
  """
  @behaviour Plug

  @impl Plug
  def init(opts), do: opts

  @impl Plug
  def call(
        %Plug.Conn{
          params: params,
          body_params: body_params,
          private: private
        } = conn,
        _opts
      )
      when is_map_key(private, :open_api_spex) do
    body_params = if is_struct(body_params), do: Map.from_struct(body_params), else: body_params
    params = Map.merge(params, body_params)
    Map.put(conn, :params, params)
  end

  def call(conn, _opts), do: conn
end

@mbuhot
Copy link
Collaborator

mbuhot commented Mar 5, 2025

You can also use replace_params: false to preserve the original behaviour:

Casted params and body params are always stored in `conn.private`.
The option `:replace_params` can be set to false to avoid overwriting conn `:body_params` and `:params`
with their casted version.

@PJUllrich
Copy link

Ah interesting! I'd still like to use the casted parameters though, just not split between params and body_params. So, I'm okay with my plug mentioned above.

@zorbash
Copy link
Contributor

zorbash commented Mar 5, 2025

We've also faced this problem and in our controllers replace_params defaults to false. I think it would be convenient this behaviour configurable through a OpenApiSpex.Plug.CastAndValidate option.

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