Rswag: POST json body not appearing in params

Created on 24 Mar 2020  路  6Comments  路  Source: rswag/rswag

I have this test.

require 'swagger_helper'
require 'mock_session_helper'

describe 'API Task', swagger_doc: CORE_DOC do
  let(:user) { users(:support) }

  path '/tasks' do
    post 'Create a task' do
      tags 'Tasks'
      operationId 'task_create'
      description <<~MARKDOWN
        [redacted]
      MARKDOWN
      consumes 'application/json'
      produces 'application/json'
      parameter name: 'session_key', in: :query, type: :string, required: true
      parameter name: 'path', in: :query, type: :string, required: true
      parameter name: 'task', in: :body, required: true, schema: {
        '$ref' => '#/definitions/Task'
      }

      response '201', :success do
        let(:session_key) { mock_session.session_key }
        let(:path) { 'all' }
        let(:task) do
          json = <<~JSON
          {
            "name": "task_flat_new",
            "path": "all/task_flat",
            "description": "x101description",
            "state_name": "planning",
            "element_type_name": "ResultTask",
            "process_path": "main/flat:1",
            "membership_path": "all/dana scully",
            "started_at": "2019-09-23T17:09:45.000Z",
            "expected_at": "2019-09-24T17:09:45.000Z",
            "custom_properties": {},
            "rows": [{
              "label": "top.1",
              "values": {
                "_method_id": null,
                "_valid": 1,
                "plate_id": "Text",
                "comment": "Text",
                "note": "Text",
                "conc": "42",
                "dose": "42",
                "high": "42",
                "high_sd": "42",
                "low": "42",
                "low_sd": "42",
                "z_prime": "42",
                "date": "2019-09-23",
                "time": "2019-09-23 18:09:46",
                "compound": "ED10-1:1",
                "batch": "ED10-1:1",
                "sample": "ED10-1:1",
                "url": "http://localhost/folders/10000",
                "file": "/project_assets/10018",
                "fit": "42",
                "user": "ED10-1:1"
              }
            }]
          }
          JSON
          JSON.parse(json)
        end
        schema '$ref' => '#/definitions/Folder'
        run_test!
      end
      # more tests
end

When I run it in debug mode, I expect params[:task] to be the value of the task I set in this test. However:

0> params
=> {"name"=>"task_flat_new", "path"=>"all", "description"=>"x101description", "state_name"=>"planning", "element_type_name"=>"ResultTask", "process_path"=>"main/flat:1", "membership_path"=>"all/dana scully", "started_at"=>"2019-09-23T17:09:45.000Z", "expected_at"=>"2019-09-24T17:09:45.000Z", "custom_properties"=>{}, "rows"=>[{"label"=>"top.1", "values"=>{"_method_id"=>nil, "_valid"=>1, "plate_id"=>"Text", "comment"=>"Text", "note"=>"Text", "conc"=>"42", "dose"=>"42", "high"=>"42", "high_sd"=>"42", "low"=>"42", "low_sd"=>"42", "z_prime"=>"42", "date"=>"2019-09-23", "time"=>"2019-09-23 18:09:46", "compound"=>"ED10-1:1", "batch"=>"ED10-1:1", "sample"=>"ED10-1:1", "url"=>"http://localhost/folders/10000", "file"=>"/project_assets/10018", "fit"=>"42", "user"=>"ED10-1:1"}}], "session_key"=>"7b579415a79cdd1d1916e23e22eeb36369f2fdd958e71981dbb9a8d4042b6afb32c1c14c3c5ce3a44e9640717aebfe37", "controller"=>"api/r5/tasks", "action"=>"create", "task"=>{"name"=>"task_flat_new", "description"=>"x101description", "started_at"=>"2019-09-23T17:09:45.000Z", "expected_at"=>"2019-09-24T17:09:45.000Z"}}

0> params[:task]
=> {"name"=>"task_flat_new", "description"=>"x101description", "started_at"=>"2019-09-23T17:09:45.000Z", "expected_at"=>"2019-09-24T17:09:45.000Z"}

This does not look like what I supplied. Why. Currently I am having to use request.raw_post in the controller and parse the json manually. It believe consumes 'application/json' is declared in the right places, as should be evident above, as well as in the CORE_DOC:

  config.swagger_docs = {
    CORE_DOC => {
      swagger: '2.0',
      info: {
        title: '[redacted]',
        version: 'r5',
        description: Api::DESCRIPTION
      },
      host: '',
      basePath: '/api/r5',
      paths: {},
      schemes: ['https','http'],
      consumes: ['application/json'],
      produces: ['application/json'],
      definitions: Api::SCHEMA
    }
  }

I have looked at issues raised here that appear to be similar, but none provide any solutions. #148 #191

Most helpful comment

@anjum-ahmed @LewisYoul I've run into the same problem, but I think I've managed to solve it. The DSL for defining params and the documentation around it is a bit lackluster. The following example works as expected:

# frozen_string_literal: true

require "swagger_helper"

describe "API" do
  path "/organization" do
    post "Create an Organization with a User account as an Owner" do
      consumes "application/json"
      produces "application/json"

      parameter name: :organization_with_owner, in: :body, schema: {
        type: :object,
        properties: {
          organization: {
            type: :object,
            properties: {
              name: { type: :string }
            },
            required: %w(name)
          },
          user: {
            type: :object,
            properties: {
              email: { type: :string },
              password: { type: :string },
              password_confirmation: { type: :string }
            },
            required: %w(email password password_confirmation)
          }
        },
        required: %(organization user)
      }

      response 201, "Organization and User account created" do
        let!(:organization_with_owner) do
          {
            organization: {
              name: "Acme Corp"
            },
            user: {
              email: "[email protected]",
              password: "foobar12",
              password_confirmation: "foobar12"
            }
          }
        end


        schema type: :object, properties: {
          organization_id: { type: :string },
          user_id: { type: :string }
        }, required: %w(organization_id user_id)

        run_test!
      end
    end
  end
end

This produces the following params in the controller:

[2] pry(#<OrganizationsController>)> params.to_unsafe_h
=> {"organization"=>{"name"=>"Acme Corp"},
 "user"=>{"email"=>"[email protected]", "password"=>"foobar12", "password_confirmation"=>"foobar12"},
 "controller"=>"organizations",
 "action"=>"create"}

All 6 comments

Hi I am also having this issue. Parameters being specified as in: :body are not being sent to my controller. I am using grape. Did you find a solution to this?

@LewisYoul I still haven't find a solution for this :pensive: ...only the workaround that I described. I still have a fair bit of time left until the api I'm writing goes into production, so I'm still waiting for someone else to explain what's going on, or maybe I need to seriously commit some time into figuring it out myself.

@anjum-ahmed @LewisYoul I've run into the same problem, but I think I've managed to solve it. The DSL for defining params and the documentation around it is a bit lackluster. The following example works as expected:

# frozen_string_literal: true

require "swagger_helper"

describe "API" do
  path "/organization" do
    post "Create an Organization with a User account as an Owner" do
      consumes "application/json"
      produces "application/json"

      parameter name: :organization_with_owner, in: :body, schema: {
        type: :object,
        properties: {
          organization: {
            type: :object,
            properties: {
              name: { type: :string }
            },
            required: %w(name)
          },
          user: {
            type: :object,
            properties: {
              email: { type: :string },
              password: { type: :string },
              password_confirmation: { type: :string }
            },
            required: %w(email password password_confirmation)
          }
        },
        required: %(organization user)
      }

      response 201, "Organization and User account created" do
        let!(:organization_with_owner) do
          {
            organization: {
              name: "Acme Corp"
            },
            user: {
              email: "[email protected]",
              password: "foobar12",
              password_confirmation: "foobar12"
            }
          }
        end


        schema type: :object, properties: {
          organization_id: { type: :string },
          user_id: { type: :string }
        }, required: %w(organization_id user_id)

        run_test!
      end
    end
  end
end

This produces the following params in the controller:

[2] pry(#<OrganizationsController>)> params.to_unsafe_h
=> {"organization"=>{"name"=>"Acme Corp"},
 "user"=>{"email"=>"[email protected]", "password"=>"foobar12", "password_confirmation"=>"foobar12"},
 "controller"=>"organizations",
 "action"=>"create"}

@anjum-ahmed I had the same problem. As a workaround I managed to make it work by changing
let(:task) { 'my task here' }
to
let(:task) { { 'task' => 'my task here' } }

Hey guys. The issue probably comes from 2 things

wrapped params

In rails, there is a wrapping param system that wrapp your "root" params under a specific key.
this key and the params that are wrapped (or not wrapped) depend on the controller model unless you specify it :
https://api.rubyonrails.org/v6.0.3.3/classes/ActionController/ParamsWrapper.html

If you created an API only rails app, an initializer has been automatically generated that do exactly that: config/initializers/wrap_parameters.rb

That's the reason any keys that are not on your "basic" model list of attributes disappear when pass "flat" params in your spec, and look out for params you passed when you do params[:my_model]. Even if the key exists it may not contain everything

class Book
  # attributes : title, content
end

# request with params { "title": "Bernard et Bianca", "intro": "Il 茅tait une fois" }

class BooksController < ApiController
  def create
    params[:title]        # "Bernard et Bianca"
    params[:book][:title] # "Bernard et Bianca"
    params[:intro]        # "Il 茅tait une fois"
    params[:book][:intro] # nil
  end
end

rswag "root" param is misguiding

It's counterintuitive, but when you declare

     parameter name: :blog, in: :body, schema: {
        type: :object,
        properties: {
          title: { type: :string },
          content: { type: :string }
        },
        required: [ 'title', 'content' ]
      }

      response '201', 'blog created' do
        let(:blog) { { title: 'foo', content: 'bar' } }

Basically you can "name" your root parameter whatever you want, it will in fact never appear:

  • nor in the doc (your requested params won't be { blog: { title: "something", content: "something" })
  • nor in the params your controller receive. If the blog key appears in your controller params, it's only thanks to the point above (wrapped params).

And it took me a while but it kinda make sense. You can't name your "root" object.

{ // root object, it has no key to identify itself
  "attribute1": { "type": "integer" },
  "attribute2": { "type": "object", properties: {...} } // here your object can have a name, "attribute2"
}

the only usage of the "root" param name that you give (here blog) is for rswag to know to which variable read the data

If you want to wrapp your attributes under a specific key in both your schema (and thus doc) and your params to override the wrapp_param that may not include all your attributes, you should go with something like:

     parameter name: :params, in: :body, schema: {
        type: :object,
        properties: {
          blog: {
            type: :object,
            properties: {
              title: { type: :string },
              content: { type: :string }
            },
            required: [ 'title' ],
          },
        },
        required: [ 'blog' ]
      }

      response '201', 'blog created' do
        let(:blog) { { blog: { title: 'foo', content: 'bar' } } }

be careful 鈿狅笍

As some comments pointed out

I had the same problem. As a workaround I managed to make it work by changing
let(:task) { 'my task here' }
to
let(:task) { { 'task' => 'my task here' } }

This solution would indeed work, but it only works because rswag doesn't bother to validate the params you give against the schema you provide

So it's like you didn't have any schema at all, and can put whatever you want in params. Rswag will do the request and will be happy if your server is ok too.

So 鈿狅笍 your doc will show something to your consumers that may not work 鈿狅笍 (as the doc will include the "flat" schema, which, depending on your code, could not work if one of your params is not a native attribute of your targeted model)

Hope it helps some1 it took me some time to understand what was happening

To make wrap_parameters work in API constollers I usually add this:

class ApiController < ActionController::API
  respond_to :json
  wrap_parameters format: [:json]
end
Was this page helpful?
0 / 5 - 0 ratings

Related issues

abevoelker picture abevoelker  路  4Comments

bspellacy picture bspellacy  路  3Comments

douglasrlee picture douglasrlee  路  4Comments

ManevilleF picture ManevilleF  路  4Comments

hannesstruss picture hannesstruss  路  6Comments