diff --git a/rswag-specs/lib/rswag/specs/request_factory.rb b/rswag-specs/lib/rswag/specs/request_factory.rb index daae7da..6f7cf96 100644 --- a/rswag-specs/lib/rswag/specs/request_factory.rb +++ b/rswag-specs/lib/rswag/specs/request_factory.rb @@ -27,6 +27,7 @@ module Rswag end def build_body(example) + return build_form_data(example) if parameters_in(:formData).present? body_parameter = parameters_in(:body).first body_parameter.nil? ? '' : example.send(body_parameter[:name]).to_json end @@ -93,6 +94,10 @@ module Rswag "#{name}=#{value.join(',')}" # csv is default end end + + def build_form_data(example) + Hash[parameters_in(:formData).map { |p| [p[:name][/^\w+[^\[]/, 0], example.send(p[:name][/^\w+[^\[]/, 0])] }] + end end end end diff --git a/rswag-specs/spec/rswag/specs/request_factory_spec.rb b/rswag-specs/spec/rswag/specs/request_factory_spec.rb index c6791b1..1ba46eb 100644 --- a/rswag-specs/spec/rswag/specs/request_factory_spec.rb +++ b/rswag-specs/spec/rswag/specs/request_factory_spec.rb @@ -158,6 +158,17 @@ module Rswag end end + context "'formData' parameter" do + before do + api_metadata[:operation][:parameters] << { name: 'comment', in: :formData, type: 'string' } + allow(example).to receive(:comment).and_return('Some comment') + end + + it 'returns the example value as a hash' do + expect(body).to eq({"comment" => "Some comment"}) + end + end + context "referenced 'body' parameter" do before do api_metadata[:operation][:parameters] << { '$ref' => '#/parameters/comment' } @@ -171,6 +182,20 @@ module Rswag expect(body).to eq("{\"text\":\"Some comment\"}") end end + + context "referenced 'formData' parameter" do + before do + api_metadata[:operation][:parameters] << { '$ref' => '#/parameters/comment' } + global_metadata[:parameters] = { + 'comment' => { name: 'comment', in: :formData, type: 'string' } + } + allow(example).to receive(:comment).and_return('Some comment') + end + + it 'returns the example value as a json string' do + expect(body).to eq({"comment" => "Some comment"}) + end + end end describe '#build_headers' do diff --git a/test-app/app/controllers/blogs_controller.rb b/test-app/app/controllers/blogs_controller.rb index 261d718..90b7102 100644 --- a/test-app/app/controllers/blogs_controller.rb +++ b/test-app/app/controllers/blogs_controller.rb @@ -4,10 +4,19 @@ class BlogsController < ApplicationController # POST /blogs def create - @blog = Blog.create(params.require(:blog).permit(:title, :content)) + thumbnail = save_uploaded_file(params[:blog][:thumbnail]) + @blog = Blog.create(params.require(:blog).permit(:title, :content).merge(:thumbnail => thumbnail)) respond_with @blog end + # Put /blogs/1 + def upload + @blog = Blog.find_by_id(params[:id]) + return head :not_found if @blog.nil? + @blog.thumbnail = save_uploaded_file params[:file] + head @blog.save ? :ok : :unprocsessible_entity + end + # GET /blogs def index @blogs = Blog.all @@ -24,4 +33,14 @@ class BlogsController < ApplicationController respond_with @blog, status: :not_found and return unless @blog respond_with @blog end + + private + + def save_uploaded_file(field) + return if field.nil? + require 'fileutils' + file = File.join('tmp', field.original_filename) + FileUtils.cp field.tempfile.path, file + field.original_filename + end end diff --git a/test-app/app/models/blog.rb b/test-app/app/models/blog.rb index 51b0795..de7f8b5 100644 --- a/test-app/app/models/blog.rb +++ b/test-app/app/models/blog.rb @@ -5,7 +5,8 @@ class Blog < ActiveRecord::Base { id: id, title: title, - content: content + content: content, + thumbnail: thumbnail } end end diff --git a/test-app/config/routes.rb b/test-app/config/routes.rb index ed6b6ed..d0c4170 100644 --- a/test-app/config/routes.rb +++ b/test-app/config/routes.rb @@ -1,5 +1,6 @@ TestApp::Application.routes.draw do resources :blogs, defaults: { :format => :json } + put '/blogs/:id/upload', to: 'blogs#upload' mount Rswag::Api::Engine => 'api-docs' mount Rswag::Ui::Engine => 'api-docs' diff --git a/test-app/db/migrate/20160218212104_create_blogs.rb b/test-app/db/migrate/20160218212104_create_blogs.rb index 81d9813..db86f36 100644 --- a/test-app/db/migrate/20160218212104_create_blogs.rb +++ b/test-app/db/migrate/20160218212104_create_blogs.rb @@ -3,6 +3,7 @@ class CreateBlogs < ActiveRecord::Migration create_table :blogs do |t| t.string :title t.text :content + t.string :thumbnail t.timestamps end diff --git a/test-app/db/schema.rb b/test-app/db/schema.rb index 082bb8d..d7ecc76 100644 --- a/test-app/db/schema.rb +++ b/test-app/db/schema.rb @@ -16,6 +16,7 @@ ActiveRecord::Schema.define(:version => 20160218212104) do create_table "blogs", :force => true do |t| t.string "title" t.text "content" + t.string "thumbnail" t.datetime "created_at", :null => false t.datetime "updated_at", :null => false end diff --git a/test-app/spec/fixtures/thumbnail.png b/test-app/spec/fixtures/thumbnail.png new file mode 100644 index 0000000..5790d6c Binary files /dev/null and b/test-app/spec/fixtures/thumbnail.png differ diff --git a/test-app/spec/integration/blogs_spec.rb b/test-app/spec/integration/blogs_spec.rb index 0ae884c..9ef08a9 100644 --- a/test-app/spec/integration/blogs_spec.rb +++ b/test-app/spec/integration/blogs_spec.rb @@ -8,11 +8,13 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do tags 'Blogs' description 'Creates a new blog from provided data' operationId 'createBlog' - consumes 'application/json' - parameter name: :blog, :in => :body, schema: { '$ref' => '#/definitions/blog' } + consumes 'application/x-www-form-urlencoded' + parameter name: 'blog[title]', :in => :formData, type: 'string' + parameter name: 'blog[content]', :in => :formData, type: 'string' + parameter name: 'blog[thumbnail]', :in => :formData, type: 'file' response '201', 'blog created' do - let(:blog) { { title: 'foo', content: 'bar' } } + let(:blog) { { title: 'foo', content: 'bar', thumbnail: Rack::Test::UploadedFile.new(Rails.root.join("spec/fixtures/thumbnail.png")) } } run_test! end @@ -34,7 +36,7 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do response '200', 'success' do schema type: 'array', items: { '$ref' => '#/definitions/blog' } - let(:keywords) { 'foo bar' } + let(:keywords) { 'foo+bar' } run_test! end end @@ -59,10 +61,11 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do examples 'application/json' => { id: 1, title: 'Hello world!', - content: 'Hello world and hello universe. Thank you all very much!!!' + content: 'Hello world and hello universe. Thank you all very much!!!', + thumbnail: "thumbnail.png" } - let(:blog) { Blog.create(title: 'foo', content: 'bar') } + let(:blog) { Blog.create(title: 'foo', content: 'bar', thumbnail: 'thumbnail.png') } let(:id) { blog.id } run_test! end @@ -73,4 +76,29 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do end end end + + path '/blogs/{id}/upload' do + parameter name: :id, :in => :path, :type => :string + + put 'upload a blog thumbnail' do + tags 'Blogs' + description 'Upload a thumbnail for specific blog by id' + operationId 'uploadThumbnailBlog' + consumes 'application/x-www-form-urlencoded' + parameter name: :file, :in => :formData, :type => 'file', required: true + + response '200', 'blog updated' do + let(:blog) { Blog.create(title: 'foo', content: 'bar') } + let(:id) { blog.id } + let(:file) { Rack::Test::UploadedFile.new(Rails.root.join("spec/fixtures/thumbnail.png")) } + run_test! + end + + response '404', 'blog not found' do + let(:id) { 'invalid' } + let(:file) { Rack::Test::UploadedFile.new(Rails.root.join("spec/fixtures/thumbnail.png")) } + run_test! + end + end + end end diff --git a/test-app/spec/spec_helper.rb b/test-app/spec/spec_helper.rb index 8f698be..d20f071 100644 --- a/test-app/spec/spec_helper.rb +++ b/test-app/spec/spec_helper.rb @@ -47,6 +47,10 @@ RSpec.configure do |config| # triggering implicit auto-inclusion in groups with matching metadata. config.shared_context_metadata_behavior = :apply_to_host_groups + config.after(:suite) do + File.delete("#{Rails.root}/tmp/thumbnail.png") if File.file?("#{Rails.root}/tmp/thumbnail.png") + end + # The settings below are suggested to provide a good initial experience # with RSpec, but feel free to customize to your heart's content. =begin diff --git a/test-app/spec/swagger_helper.rb b/test-app/spec/swagger_helper.rb index a3b76b4..d98442a 100644 --- a/test-app/spec/swagger_helper.rb +++ b/test-app/spec/swagger_helper.rb @@ -39,9 +39,10 @@ RSpec.configure do |config| properties: { id: { type: 'integer' }, title: { type: 'string' }, - content: { type: 'string' } + content: { type: 'string' }, + thumbnail: { type: 'string'} }, - required: [ 'id', 'title', 'content' ] + required: [ 'id', 'title', 'content', 'thumbnail' ] } }, securityDefinitions: { diff --git a/test-app/swagger/v1/swagger.json b/test-app/swagger/v1/swagger.json index acbcd1e..f74398a 100644 --- a/test-app/swagger/v1/swagger.json +++ b/test-app/swagger/v1/swagger.json @@ -14,15 +14,23 @@ "description": "Creates a new blog from provided data", "operationId": "createBlog", "consumes": [ - "application/json" + "application/x-www-form-urlencoded" ], "parameters": [ { - "name": "blog", - "in": "body", - "schema": { - "$ref": "#/definitions/blog" - } + "name": "blog[title]", + "in": "formData", + "type": "string" + }, + { + "name": "blog[content]", + "in": "formData", + "type": "string" + }, + { + "name": "blog[thumbnail]", + "in": "formData", + "type": "file" } ], "responses": { @@ -107,7 +115,8 @@ "application/json": { "id": 1, "title": "Hello world!", - "content": "Hello world and hello universe. Thank you all very much!!!" + "content": "Hello world and hello universe. Thank you all very much!!!", + "thumbnail": "thumbnail.png" } } }, @@ -116,6 +125,43 @@ } } } + }, + "/blogs/{id}/upload": { + "parameters": [ + { + "name": "id", + "in": "path", + "type": "string", + "required": true + } + ], + "put": { + "summary": "upload a blog thumbnail", + "tags": [ + "Blogs" + ], + "description": "Upload a thumbnail for specific blog by id", + "operationId": "uploadThumbnailBlog", + "consumes": [ + "application/x-www-form-urlencoded" + ], + "parameters": [ + { + "name": "file", + "in": "formData", + "type": "file", + "required": true + } + ], + "responses": { + "200": { + "description": "blog updated" + }, + "404": { + "description": "blog not found" + } + } + } } }, "definitions": { @@ -147,12 +193,16 @@ }, "content": { "type": "string" + }, + "thumbnail": { + "type": "string" } }, "required": [ "id", "title", - "content" + "content", + "thumbnail" ] } },