diff --git a/CHANGELOG.md b/CHANGELOG.md index 9498cad..cd97a37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.7 + +- Make camelizing of model properties configurable. #55 + ## 0.1.6 - Document notes DSL diff --git a/README.md b/README.md index ec65a17..a218035 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,12 @@ The following table shows all the current configuration options and their defaul :pretty + +camelize_model_properties +Camelizes property names of models. For example, a property name called first_name would be converted to firstName. +true + + @@ -174,19 +180,19 @@ end ### DRYing up common documentation Suppose you have a header or a parameter that must be present on several controllers and methods. Instead of duplicating it on all the controllers you can do this on your API base controller: - + ```ruby class Api::BaseController < ActionController::Base class << self Swagger::Docs::Generator::set_real_methods - + def inherited(subclass) super subclass.class_eval do setup_basic_api_documentation end end - + private def setup_basic_api_documentation [:index, :show, :create, :update, :delete].each do |api_action| @@ -198,7 +204,7 @@ class Api::BaseController < ActionController::Base end end ``` - + And then use it as a superclass to all you API controllers. All the subclassed controllers will have the same documentation applied to them. ### DSL Methods @@ -608,7 +614,7 @@ users.json output: ## Thanks to our contributors -Thanks to jdar, fotinakis, stevschmid, ldnunes and all of our contributors for making swagger-docs even better. +Thanks to jdar, fotinakis, stevschmid, ldnunes, aaronrenner and all of our contributors for making swagger-docs even better. ## Contributing diff --git a/lib/swagger/docs.rb b/lib/swagger/docs.rb index 92129d2..82dca82 100644 --- a/lib/swagger/docs.rb +++ b/lib/swagger/docs.rb @@ -1,5 +1,7 @@ require "swagger/docs/config" require "swagger/docs/dsl" +require "swagger/docs/api_declaration_file_metadata" +require "swagger/docs/api_declaration_file" require "swagger/docs/generator" require "swagger/docs/impotent_methods" require "swagger/docs/methods" diff --git a/lib/swagger/docs/api_declaration_file.rb b/lib/swagger/docs/api_declaration_file.rb new file mode 100644 index 0000000..e161a70 --- /dev/null +++ b/lib/swagger/docs/api_declaration_file.rb @@ -0,0 +1,120 @@ +module Swagger + module Docs + class ApiDeclarationFile + attr_reader :metadata, :apis + + def initialize(metadata, apis, models) + @metadata = metadata + @apis = camelize_keys_deep apis + @models = models + end + + def generate_resource + resource = build_resource_root_hash + # Add the already-normalized models to the resource. + resource = resource.merge({:models => models}) if models.present? + resource + end + + def base_path + metadata.base_path + end + + def path + metadata.path + end + + def swagger_version + metadata.swagger_version + end + + def api_version + metadata.api_version + end + + def controller_base_path + metadata.controller_base_path + end + + def camelize_model_properties + metadata.camelize_model_properties + end + + def resource_path + demod + end + + def resource_file_path + trim_leading_slash(debased_path.to_s.underscore) + end + + def models + normalize_model_properties @models + end + + private + + def build_resource_root_hash + { + "apiVersion" => api_version, + "swaggerVersion" => swagger_version, + "basePath" => base_path, + "resourcePath" => resource_path, + "apis" => apis, + "resourceFilePath" => resource_file_path + } + end + + def normalize_model_properties(models) + Hash[ + models.map do |k, v| + if camelize_model_properties + [k.to_s, camelize_keys_deep(v)] + else + [k.to_s, stringify_keys_deep(v)] + end + end] + end + + def demod + "#{debased_path.to_s.camelize}".demodulize.camelize.underscore + end + + def debased_path + path.gsub("#{controller_base_path}", "") + end + + def trim_leading_slash(str) + return str if !str + str.gsub(/\A\/+/, '') + end + + def camelize_keys_deep(obj) + process_keys_deep(obj){|key| key.to_s.camelize(:lower)} + end + + def stringify_keys_deep(obj) + process_keys_deep(obj){|key| key.to_s} + end + + def process_keys_deep(obj, &block) + if obj.is_a? Hash + Hash[ + obj.map do |k, v| + new_key = block.call(k) + new_value = process_keys_deep v, &block + [new_key, new_value] + end + ] + elsif obj.is_a? Array + new_value = obj.collect do |a| + process_keys_deep a, &block + end + else + obj + end + end + + end + end +end diff --git a/lib/swagger/docs/api_declaration_file_metadata.rb b/lib/swagger/docs/api_declaration_file_metadata.rb new file mode 100644 index 0000000..ecf6730 --- /dev/null +++ b/lib/swagger/docs/api_declaration_file_metadata.rb @@ -0,0 +1,18 @@ +module Swagger + module Docs + class ApiDeclarationFileMetadata + DEFAULT_SWAGGER_VERSION = "1.2" + + attr_reader :api_version, :path, :base_path, :controller_base_path, :swagger_version, :camelize_model_properties + + def initialize(api_version, path, base_path, controller_base_path, options={}) + @api_version = api_version + @path = path + @base_path = base_path + @controller_base_path = controller_base_path + @swagger_version = options.fetch(:swagger_version, DEFAULT_SWAGGER_VERSION) + @camelize_model_properties = options.fetch(:camelize_model_properties, true) + end + end + end +end diff --git a/lib/swagger/docs/generator.rb b/lib/swagger/docs/generator.rb index 50b47c5..844f467 100644 --- a/lib/swagger/docs/generator.rb +++ b/lib/swagger/docs/generator.rb @@ -54,7 +54,7 @@ def generate_docs(apis=nil) end def generate_doc(api_version, settings, config) - root = { :api_version => api_version, :swagger_version => "1.2", :base_path => settings[:base_path] + "/", :apis => []} + root = { "apiVersion" => api_version, "swaggerVersion" => "1.2", "basePath" => settings[:base_path] + "/", :apis => []} results = {:processed => [], :skipped => []} resources = [] @@ -71,8 +71,7 @@ def generate_doc(api_version, settings, config) root[:apis] << resource_api end end - root[:resources] = resources - camelize_keys_deep!(root) + root['resources'] = resources results[:root] = root results end @@ -134,15 +133,12 @@ def process_path(path, root, config, settings) end def generate_resource(path, apis, models, settings, root, config) - debased_path = get_debased_path(path, settings[:controller_base_path]) - demod = "#{debased_path.to_s.camelize}".demodulize.camelize.underscore - resource_path = trim_leading_slash(debased_path.to_s.underscore) - resource = root.merge({:resource_path => "#{demod}", :apis => apis}) - camelize_keys_deep!(resource) - # Add the already-normalized models to the resource. - resource = resource.merge({:models => models}) if models.present? - resource[:resource_file_path] = resource_path - resource + metadata = ApiDeclarationFileMetadata.new(root["apiVersion"], path, root["basePath"], + settings[:controller_base_path], + camelize_model_properties: config.fetch(:camelize_model_properties, true), + swagger_version: root["swaggerVersion"]) + declaration = ApiDeclarationFile.new(metadata, apis, models) + declaration.generate_resource end def get_route_path_apis(path, route, klass, settings, config) diff --git a/lib/swagger/docs/version.rb b/lib/swagger/docs/version.rb index 13d9e88..a64e304 100644 --- a/lib/swagger/docs/version.rb +++ b/lib/swagger/docs/version.rb @@ -1,5 +1,5 @@ module Swagger module Docs - VERSION = "0.1.6" + VERSION = "0.1.7" end end diff --git a/spec/lib/swagger/docs/api_declaration_file_metadata_spec.rb b/spec/lib/swagger/docs/api_declaration_file_metadata_spec.rb new file mode 100644 index 0000000..74f2807 --- /dev/null +++ b/spec/lib/swagger/docs/api_declaration_file_metadata_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' + +describe Swagger::Docs::ApiDeclarationFileMetadata do + + describe "#initialize" do + it "sets the api_version property" do + metadata = described_class.new("1.0", "path", "basePath", "controllerBasePath") + + expect(metadata.api_version).to eq("1.0") + end + + it "sets the path property" do + metadata = described_class.new("1.0", "path", "basePath", "controllerBasePath") + + expect(metadata.path).to eq("path") + end + + it "sets the base_path property" do + metadata = described_class.new("1.0", "path", "basePath", "controllerBasePath") + + expect(metadata.base_path).to eq("basePath") + end + + it "sets the controller_base_path property" do + metadata = described_class.new("1.0", "path", "basePath", "controllerBasePath") + + expect(metadata.controller_base_path).to eq("controllerBasePath") + end + + it "defaults the swagger_version property to DEFAULT_SWAGGER_VERSION" do + metadata = described_class.new("1.0", "path", "basePath", "controllerBasePath") + + expect(metadata.swagger_version).to eq(described_class::DEFAULT_SWAGGER_VERSION) + end + + it "allows the swagger_version property to be_overriden" do + metadata = described_class.new("1.0", "path", "basePath", "controllerBasePath", swagger_version: "2.0") + + expect(metadata.swagger_version).to eq("2.0") + end + + + it "defaults the camelize_model_properties property to true" do + metadata = described_class.new("1.0", "path", "basePath", "controllerBasePath") + + expect(metadata.camelize_model_properties).to eq(true) + end + + it "allows the camelize_model_properties property to be overidden" do + metadata = described_class.new("1.0", "path", "basePath", "controllerBasePath", camelize_model_properties: false) + + expect(metadata.camelize_model_properties).to eq(false) + end + end +end diff --git a/spec/lib/swagger/docs/api_declaration_file_spec.rb b/spec/lib/swagger/docs/api_declaration_file_spec.rb new file mode 100644 index 0000000..b2b2574 --- /dev/null +++ b/spec/lib/swagger/docs/api_declaration_file_spec.rb @@ -0,0 +1,198 @@ +require "spec_helper" + +describe Swagger::Docs::ApiDeclarationFile do + let(:apis) do + [ + { + :path=>"sample/{id}", + :operations=>[ + { + :summary=>"Updates an existing User", + :parameters=>[ + {:param_type=>:path, :name=>:id, :type=>:integer, :description=>"User Id", :required=>true}, + {:param_type=>:form, :name=>:first_name, :type=>:string, :description=>"First name", :required=>false}, + {:param_type=>:form, :name=>:last_name, :type=>:string, :description=>"Last name", :required=>false}, + {:param_type=>:form, :name=>:email, :type=>:string, :description=>"Email address", :required=>false}, + {:param_type=>:form, :name=>:tag, :type=>:Tag, :description=>"Tag object", :required=>true} + ], + :response_messages=>[ + {:code=>401, :message=>"Unauthorized"}, + {:code=>404, :message=>"Not Found"}, + {:code=>406, :message=>"Not Acceptable"} + ], + :notes=>"Only the given fields are updated.", + :method=>:put, + :nickname=>"Api::V1::Sample#update" + } + ] + } + ] + end + + let(:models) do + { + :Tag=> + { + :id=>:Tag, + :required=>[:id], + :properties=> + { + :id=>{:type=>:integer, :description=>"User Id"}, + :first_name=>{:type=>:string, :description=>"First Name"}, + :last_name=>{:type=>:string, :description=>"Last Name"} + }, + :description=>"A Tag object." + } + } + end + + let(:metadata) do + Swagger::Docs::ApiDeclarationFileMetadata.new("1.0", "api/v1/sample", "http://api.no.where/", "") + end + + describe "#generate_resource" do + + it "generates the appropriate response" do + declaration = described_class.new(metadata, apis, models) + + expected_response = { + "apiVersion"=> declaration.api_version, + "swaggerVersion"=> declaration.swagger_version, + "basePath"=> declaration.base_path, + "apis"=> declaration.apis, + "resourcePath"=> declaration.resource_path, + :models=> declaration.models, + "resourceFilePath" => declaration.resource_file_path + } + expect(declaration.generate_resource).to eq(expected_response) + end + end + + describe "#base_path" do + it "returns metadata.base_path" do + metadata = double("metadata", base_path: "/hello") + declaration = described_class.new(metadata, apis, models) + expect(declaration.base_path).to eq(metadata.base_path) + end + end + + describe "#path" do + it "returns metadata.path" do + metadata = double("metadata", path: "/hello") + declaration = described_class.new(metadata, apis, models) + expect(declaration.path).to eq(metadata.path) + end + end + + describe "#controller_base_path" do + it "returns metadata.controller_base_path" do + metadata = double("metadata", controller_base_path: "/hello") + declaration = described_class.new(metadata, apis, models) + expect(declaration.controller_base_path).to eq(metadata.controller_base_path) + end + end + + describe "#swagger_version" do + it "returns metadata.swagger_version" do + metadata = double("metadata", swagger_version: "1.2") + declaration = described_class.new(metadata, apis, models) + expect(declaration.swagger_version).to eq(metadata.swagger_version) + end + end + + describe "#api_version" do + it "returns metadata.api_version" do + metadata = double("metadata", api_version: "1.0") + declaration = described_class.new(metadata, apis, models) + expect(declaration.api_version).to eq(metadata.api_version) + end + end + + describe "#camelize_model_properties" do + it "returns metadata.camelize_model_properties" do + metadata = double("metadata", camelize_model_properties: false) + declaration = described_class.new(metadata, apis, models) + expect(declaration.camelize_model_properties).to eq(metadata.camelize_model_properties) + end + end + + describe "#models" do + context "with camelize_model_properties set to true" do + it "returns a models hash that's ready for output" do + declaration = described_class.new(metadata, apis, models) + allow(declaration).to receive(:camelize_model_properties).and_return(true) + expected_models_hash = { + "Tag" => + { + "id" => :Tag, + "required" =>[:id], + "properties" => + { + "id" =>{"type"=>:integer, "description"=>"User Id"}, + "firstName"=>{"type"=>:string, "description"=>"First Name"}, + "lastName"=>{"type"=>:string, "description"=>"Last Name"}, + }, + "description"=>"A Tag object." + } + } + + expect(declaration.models).to eq(expected_models_hash) + end + end + + context "with camelize_model_properties set to false" do + it "returns a models hash that's ready for output" do + declaration = described_class.new(metadata, apis, models) + allow(declaration).to receive(:camelize_model_properties).and_return(false) + expected_models_hash = { + "Tag" => + { + "id" => :Tag, + "required" =>[:id], + "properties" => + { + "id" =>{"type"=>:integer, "description"=>"User Id"}, + "first_name"=>{"type"=>:string, "description"=>"First Name"}, + "last_name"=>{"type"=>:string, "description"=>"Last Name"}, + }, + "description"=>"A Tag object." + } + } + + expect(declaration.models).to eq(expected_models_hash) + end + end + end + + describe "#apis" do + it "returns a api hash that's ready for output" do + declaration = described_class.new(metadata, apis, models) + expected_apis_array = [ + { + "path"=>"sample/{id}", + "operations"=>[ + { + "summary"=>"Updates an existing User", + "parameters"=>[ + {"paramType"=>:path, "name"=>:id, "type"=>:integer, "description"=>"User Id", "required"=>true}, + {"paramType"=>:form, "name"=>:first_name, "type"=>:string, "description"=>"First name", "required"=>false}, + {"paramType"=>:form, "name"=>:last_name, "type"=>:string, "description"=>"Last name", "required"=>false}, + {"paramType"=>:form, "name"=>:email, "type"=>:string, "description"=>"Email address", "required"=>false}, + {"paramType"=>:form, "name"=>:tag, "type"=>:Tag, "description"=>"Tag object", "required"=>true} + ], + "responseMessages"=>[ + {"code"=>401, "message"=>"Unauthorized"}, + {"code"=>404, "message"=>"Not Found"}, + {"code"=>406, "message"=>"Not Acceptable"} + ], + "notes"=>"Only the given fields are updated.", + "method"=>:put, + "nickname"=>"Api::V1::Sample#update" + } + ] + } + ] + expect(declaration.apis).to eq(expected_apis_array) + end + end +end diff --git a/spec/lib/swagger/docs/generator_spec.rb b/spec/lib/swagger/docs/generator_spec.rb index c660cfd..16d7eb3 100644 --- a/spec/lib/swagger/docs/generator_spec.rb +++ b/spec/lib/swagger/docs/generator_spec.rb @@ -321,7 +321,7 @@ } } } - expect(models['tag']).to eq expected_model + expect(models['Tag']).to eq expected_model end end end