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

cast_and_validate purges file upload struct from controller parameters #370

Open
superruzafa opened this issue Jun 14, 2021 · 6 comments
Open

Comments

@superruzafa
Copy link

Hi.

This is more a question than an issue.

In the Phoenix framework, when doing a file upload I used to fetch the %Plug.Upload structure from the 2nd parameter passed to the controller's action:

def action(conn, %{"file_upload" => %Plug.Upload{}}) do
...
end

I created a spec in order to allow file uploads as noted in similar issues:

OpenApiSpex.schema(%{
  title: "FileUpload",
  type: :object,
  properties: %{
    file_upload: %Schema{
      type: :string,
      format: :binary
    }
  }
})

After calling OpenApiSpex.cast_and_validate/4 I noticed that the upload is deleted from the 2nd parameter passed to the controller.

I also check that the upload can be accessed from the conn's body_params field, but my question is: is there any way to preserve it in the controller's 2nd parameter?

Thanks.

@lucacorti
Copy link
Contributor

lucacorti commented Jul 24, 2021

@superruzafa Same here, looks like the root cause is the same as #92. OpenApiSpex is changing the phoenix params and this causes issues with anything expecting them to be there.

@mbuhot
Copy link
Collaborator

mbuhot commented Aug 2, 2021

Looks like when we CastAndValidate a schema with type: :string, format: :binary it should allow a %Plug.Upload{} struct to be passed through.

https://swagger.io/docs/specification/describing-request-body/file-upload/

@petermueller
Copy link

Hey, coming across this a year+ later. Is it possible that the OpenApiSpex.Cast.OneOf is not handling it correctly. I'm pretty sure I have my schema correct, but when I don't use a oneOf, it works, but otherwise I get an "Enum. protocol not implemented for Plug.Upload" error.

image: %Schema{
  oneOf: [%Schema{type: :string, format: :binary}, Base64ObjectImageUpload]
}

I am also using a MediaType with a custom override encoding:

content: %{
  "multipart/form-data" => %OpenApiSpex.MediaType{
    schema: __MODULE__,
    encoding: %{"image" => %OpenApiSpex.Encoding{contentType: "image/png"}}
  },
  "application/json" => %OpenApiSpex.MediaType{schema: __MODULE__}
},

Does this seem likely, or more likely that I just messed up my oneOf 😅

@lucacorti
Copy link
Contributor

@petermueller can you test #455 and see if it solves your issue?

@petermueller
Copy link

@lucacorti, can do 👍🏻
I'll report back in the morning

@petermueller
Copy link

petermueller commented Oct 4, 2022

Unfortunately no. Same error :-/

(EDIT: This was after I blew away the deps and _build just in case)

Stack

[error] #PID<0.4803.0> running MyApp.Endpoint (connection #PID<0.4802.0>, stream id 1) terminated
Server: local.myapp.com:80 (http)
Request: PUT /api/v2/my_resource/55/
** (exit) an exception was raised:
    ** (Protocol.UndefinedError) protocol Enumerable not implemented for %Plug.Upload{content_type: "image/png", filename: "C84A8E27-EEE4-4439-8007-4AC937E9AD93.png", path: "/var/folders/wh/s4xndcwj56vdh19q0cd9628w0000gn/T/plug-1664/multipart-1664895328-565347161995327-1"} of type Plug.Upload (a struct)
        (elixir 1.13.4) lib/enum.ex:1: Enumerable.impl_for!/1
        (elixir 1.13.4) lib/enum.ex:143: Enumerable.reduce/3
        (elixir 1.13.4) lib/enum.ex:4144: Enum.reduce/3
        (open_api_spex 3.13.0) lib/open_api_spex/cast/object.ex:150: OpenApiSpex.Cast.Object.get_additional_properties/2
        (open_api_spex 3.13.0) lib/open_api_spex/cast/object.ex:132: OpenApiSpex.Cast.Object.cast_additional_properties/2
        (open_api_spex 3.13.0) lib/open_api_spex/cast/object.ex:24: OpenApiSpex.Cast.Object.cast/1
        (open_api_spex 3.13.0) lib/open_api_spex/cast/one_of.ex:17: anonymous fn/2 in OpenApiSpex.Cast.OneOf.cast/1
        (elixir 1.13.4) lib/enum.ex:2396: Enum."-reduce/3-lists^foldl/2-0-"/3
        (open_api_spex 3.13.0) lib/open_api_spex/cast/one_of.ex:13: OpenApiSpex.Cast.OneOf.cast/1
        (open_api_spex 3.13.0) lib/open_api_spex/cast/object.ex:170: OpenApiSpex.Cast.Object.cast_property/2
        (open_api_spex 3.13.0) lib/open_api_spex/cast/object.ex:113: anonymous fn/4 in OpenApiSpex.Cast.Object.cast_properties/1
        (stdlib 4.0.1) maps.erl:411: :maps.fold_1/3
        (open_api_spex 3.13.0) lib/open_api_spex/cast/object.ex:112: OpenApiSpex.Cast.Object.cast_properties/1
        (open_api_spex 3.13.0) lib/open_api_spex/cast/object.ex:28: OpenApiSpex.Cast.Object.cast/1
        (open_api_spex 3.13.0) lib/open_api_spex/operation2.ex:37: OpenApiSpex.Operation2.cast/5
        (open_api_spex 3.13.0) lib/open_api_spex/plug/cast_and_validate.ex:82: OpenApiSpex.Plug.CastAndValidate.call/2
        (myapp 0.1.0) lib/my_app/controllers/my_resource_controller.ex:1: MyApp.MyResourceController.phoenix_controller_pipeline/2
        (phoenix 1.6.11) lib/phoenix/router.ex:354: Phoenix.Router.__call__/2
        (myapp 0.1.0) lib/my_app/endpoint.ex:1: MyApp.Endpoint.plug_builder_call/2
        (myapp 0.1.0) lib/my_app/endpoint.ex:1: MyApp.Endpoint."call (overridable 3)"/2

Operation

  operation :update,
    summary: "My Description",
    parameters: [MyApp.Schemas.PathParameters.id_param(:id, "My Resource ID")],
    request_body: UpdateMyResourceRequest.request_body()

  def update(conn, %{id: id}) do
   # ...

Schema and custom request_body function

defmodule MyApp.Schemas.UpdateMyResourceRequest. do
  @moduledoc """
  The `UpdateMyResourceRequest` `OpenApiSpex` Schema
  """

  alias OpenApiSpex.Schema

  require OpenApiSpex

  OpenApiSpex.schema(%{
    description: "Updates my resources",
    type: :object,
    properties: %{
      thing_a: %Schema{
        type: :string,
        description: "thing_a"
      },
      thing_b: %Schema{
        type: :string,
        description: "thing_b"
      },
      thing_c_dt: %Schema{
        type: :string,
        description: "thing_c_dt",
        format: :"date-time",
        nullable: false
      },
      thing_d_enum: %Schema{
        type: :string,
        enum: [:asdf, :qwer]
      },
      # image: %Schema{type: :string, format: :binary}
      image: %Schema{
        oneOf: [%Schema{type: :string, format: :binary}, Base64ObjectImageUpload]
      }
    },
    example: %{
      "thing_a" => "asdf",
      "thing_c_dt" => "2021-01-30 15:00:00-04:00",
      "image" => "thisistheimagebinary"
    }
  })

  def request_body do
    %OpenApiSpex.RequestBody{
      description: "Update My Resource",
      content: %{
        "multipart/form-data" => %OpenApiSpex.MediaType{
          schema: __MODULE__,
          encoding: %{"image" => %OpenApiSpex.Encoding{contentType: "image/png"}}
        },
        "application/json" => %OpenApiSpex.MediaType{schema: __MODULE__}
      },
      required: true
    }
  end
end

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

No branches or pull requests

4 participants