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

Add options to OpenApiSpex.schema/1 #312

Merged
merged 6 commits into from
Dec 27, 2020
Merged

Add options to OpenApiSpex.schema/1 #312

merged 6 commits into from
Dec 27, 2020

Conversation

moxley
Copy link
Contributor

@moxley moxley commented Dec 13, 2020

Introduce options in OpenApiSpex.schema/2:

Example

defmodule MyAppWeb.Schemas.User do
  require OpenApiSpex
  alias OpenApiSpex.Schema

  OpenApiSpex.schema(
    %{
      type: :object,
      properties: %{
        name: %Schema{type: :string}
      }
    },
    struct?: false,
    derive?: false
  )
end

:struct?

When false, :struct? prevents a struct definition for the schema module. This is useful for PATCH endpoints where the caller wants to provide a subset of the possible request params accepted by the endpoint. When the params are casted to a struct, it appears from the endpoint's perspective that the client provided all the request fields when it may have only provided a subset.

This causes certain bugs when trying to update a resource, as seen on some projects that use Ecto changesets. Any param that was not passed in the update will be updated to nil or whatever the default value is defined for the corresponding schema property.

In this example, we only want to update a user's email, but we end up updating their name to nil:

iex> params = %{"email" => "user@example.com"}
iex> params_as_struct = OpenApiSpex.Cast.cast(UserSchema.schema(), params,%{})
%UserSchema{email: "user@example.com", name: nil}
iex> user = Users.get_user(user_id)
%MyApp.User{email: "old-email@example.com", name: "José"} # name is José
iex> User.changeset(user, params_as_struct) # Raises exception! Let's use Map.from_struct/1
iex> User.changeset(user, Map.from_struct(params_as_struct)) |> Repo.update!()
%MyApp.User{email: "user@example.com", name: nil} # name is nil!

:derive?

This option resolves the issue described in #310. As stated in that issue, it is not clear what the value of defining @derive is, and it does present problems as described in the issue.

Testing

My project has over 60 schema modules defined. After adding struct?: false, derive?: false to all of them, there were a few test failures due to calling Map.from_struct/1 unnecessarily. Once that call was removed, everything worked perfectly.

@moxley moxley changed the title Control construction of schema module Add options to OpenApiSpex.schema/1 Dec 13, 2020
@mbuhot
Copy link
Collaborator

mbuhot commented Dec 14, 2020

If struct/derives aren't required, then is the macro necessary at all?

defmodule MyAppWeb.Schemas.User do
  require OpenApiSpex
  alias OpenApiSpex.Schema

  OpenApiSpex.schema(
    %{
      type: :object,
      properties: %{
        name: %Schema{type: :string}
      }
    },
    struct?: false,
    derive?: false
  )
end

vs

defmodule MyAppWeb.Schemas.User do
  alias OpenApiSpex.Schema 
  @behaviour OpenApiSpex.Schema

  def schema do
    %Schema{
      type: :object,
      properties: %{
        name: %Schema{type: :string}
      }
    }
  end
end

@moxley
Copy link
Contributor Author

moxley commented Dec 15, 2020

If struct/derives aren't required, then is the macro necessary at all?

The macro calls OpenApiSpex.validate_compiled_schema/1 and SchemaConsistency.warnings/1. These functions check the validity of the schema at compile time. Also, the macro auto-populates the schema title based on the module name. Without the macro call, the user does not get these benefits.

@moxley moxley merged commit a4bf2a2 into master Dec 27, 2020
@moxley moxley deleted the schema-opts branch December 27, 2020 23:04
@moxley moxley mentioned this pull request Jan 12, 2021
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.

2 participants