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
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
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
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:
{ blog: { title: "something", content: "something" })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' } } }
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
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:
This produces the following params in the controller: