support generate swagger.json without rswag

This commit is contained in:
YingRui Lu 2019-06-19 14:06:56 +08:00
parent 4fa533ea26
commit 3cfe68b421
8 changed files with 441 additions and 18 deletions

View File

@ -13,15 +13,6 @@ Gem::Specification.new do |spec|
spec.summary = 'JSON API Swagger Doc Generator'
spec.homepage = 'https://github.com/superiorlu/jsonapi-swagger'
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
# to allow pushing to a single host or delete this section to allow pushing to any host.
if spec.respond_to?(:metadata)
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
else
raise 'RubyGems 2.0 or newer is required to protect against ' \
'public gem pushes.'
end
# Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
spec.files = Dir['lib/**/*', 'LICENSE.md', 'README.md']
@ -32,6 +23,5 @@ Gem::Specification.new do |spec|
spec.add_development_dependency 'bundler', '~> 2.0'
spec.add_development_dependency 'rake', '~> 10.0'
spec.add_development_dependency 'rubocop', '~> 0.67'
spec.add_dependency 'rswag', '~>2.0'
spec.add_development_dependency 'rswag', '~>2.0'
end

View File

@ -4,15 +4,54 @@ module Jsonapi
source_root File.expand_path('templates', __dir__)
def create_swagger_file
swagger_file = File.join(
if Jsonapi::Swagger.use_rswag
template 'swagger.rb.erb', spec_file
else
template 'swagger.json.erb', json_file
end
end
private
def doc
@doc ||= swagger_json.parse_doc
end
def spec_file
@spec_file ||= File.join(
'spec/requests',
class_path,
spec_file_name
)
template 'swagger.rb.erb', swagger_file
end
private
def json_file
@json_file ||= File.join(
'swagger',
class_path,
swagger_file_path
)
end
def swagger_version
Jsonapi::Swagger.version
end
def swagger_info
JSON.pretty_generate(Jsonapi::Swagger.info)
end
def swagger_base_path
Jsonapi::Swagger.base_path
end
def swagger_file_path
Jsonapi::Swagger.file_path
end
def swagger_json
@swagger_json ||= Jsonapi::Swagger::Json.new(json_file)
end
def spec_file_name
"#{file_name.downcase.pluralize}_spec.rb"
@ -38,6 +77,10 @@ module Jsonapi
t(:sortable_fields) + ': (-)' + sortable_fields.join(',')
end
def ori_sortable_fields_desc
tt(:sortable_fields) + ': (-)' + sortable_fields.join(',')
end
def model_klass
model_class_name.safe_constantize
end
@ -70,10 +113,11 @@ module Jsonapi
resource_klass.filters
end
def columns_with_comment
def columns_with_comment(need_encoding: true)
@columns_with_comment ||= {}.tap do |clos|
model_klass.columns.each do |col|
clos[col.name.to_sym] = { type: swagger_type(col), items_type: col.type, is_array: col.array, nullable: col.null, comment: safe_encode(col.comment) }
clos[col.name.to_sym] = { type: swagger_type(col), items_type: col.type, is_array: col.array, nullable: col.null, comment: col.comment }
clos[col.name.to_sym][:comment] = safe_encode(col.comment) if need_encoding
end
end
end
@ -89,10 +133,14 @@ module Jsonapi
end
def t(key, options={})
content = tt(key, options)
safe_encode(content)
end
def tt(key, options={})
options[:scope] = :jsonapi_swagger
options[:default] = key.to_s.humanize
content = I18n.t(key, options)
safe_encode(content)
I18n.t(key, options)
end
def safe_encode(content)

View File

@ -0,0 +1,319 @@
{
"swagger": "<%= swagger_version %>",
"info": <%= swagger_info %>,
"basePath" : "<%= swagger_base_path %>",
<%-
def list_resource_parameters
[].tap do |parameters|
parameters << { name: 'page[number]', in: :query, type: :string, description: tt(:page_num), required: false }
parameters << { name: 'page[size]', in: :query, type: :string, description: tt(:page_size), required: false }
if sortable_fields.present?
parameters << { name: 'sort', in: :query, type: :string, description: ori_sortable_fields_desc, required: false }
end
if relationships.present?
parameters << { name: :include, in: :query, type: :string, description: tt(:include_related_data), required: false }
end
filters.each do |filter_attr, filter_config|
parameters << { name: :"filter[#{filter_attr}]", in: :query, type: :string, description: tt(:filter_field), required: false}
end
parameters << { name: :"fields[#{route_resouces}]", in: :query, type: :string, description: tt(:display_field), required: false }
relationships.each_value do |relation|
parameters << { name: :"fields[#{relation.class_name.tableize}]", in: :query, type: :string, description: tt(:display_field), required: false }
end
end
end
def show_resource_parameters
[].tap do |parameters|
parameters << { name: :id, in: :path, type: :integer, description: 'ID', required: true }
if relationships.present?
parameters << { name: :include, in: :query, type: :string, description: tt(:include_related_data), required: false }
end
parameters << { name: :"fields[#{route_resouces}]", in: :query, type: :string, description: tt(:display_field), required: false }
relationships.each_value do |relation|
parameters << { name: :"fields[#{relation.class_name.tableize}]", in: :query, type: :string, description: tt(:display_field), required: false }
end
end
end
def create_resource_parameters
parameters = {
name: :data,
in: :body,
type: :object,
properties: {
data: {
type: :object,
properties: {
type: { type: :string, default: route_resouces },
attributes: {
type: :object,
properties: properties(attrs: creatable_fields)
}
}
},
},
description: tt(:request_body)
}
parameters[:properties][:data][:properties][:relationships] ||= {}
parameters[:properties][:data][:properties][:relationships] = { type: :object, properties: create_relationships_properties }
parameters
end
def patch_resource_parameters
patch_parameters = create_resource_parameters.dup
patch_parameters[:properties][:data][:properties][:id] ||= {}
patch_parameters[:properties][:data][:properties][:id].merge!({ type: :integer, description: 'ID' })
parameters = [{ name: :id, in: :path, type: :integer, description: 'ID', required: true }]
parameters << patch_parameters
parameters
end
def delete_resource_parameters
[{ name: :id, in: :path, type: :integer, description: 'ID', required: true }]
end
def properties(attrs: [])
Hash.new{|h, k| h[k] = {}} .tap do |props|
attrs.each do |attr|
columns = columns_with_comment(need_encoding: false)
props[attr][:type] = columns[attr][:type]
props[attr][:items] = { type: columns[attr][:items_type] } if columns[attr][:is_array]
props[attr][:'x-nullable'] = columns[attr][:nullable]
props[attr][:description] = columns[attr][:comment]
end
end
end
def relationships_properties
{}.tap do |relat_props|
relationships.each do |relation_name, relation|
relation_name_camelize = relation_name.to_s.camelize
relat_props[relation_name] = {
type: :object,
properties: {
links: {
type: :object,
properties: {
self: { type: :string, description: tt(:associate_list_link, model: relation_name_camelize) },
related: { type: :string, description: tt(:related_link, model: relation_name_camelize) },
},
description: tt(:related_link, model: relation_name_camelize)
},
},
description: tt(:related_model, model: relation_name_camelize)
}
end
end
end
def create_relationships_properties
{}.tap do |relat_props|
relationships.each do |relation_name, relation|
relation_name_camelize = relation_name.to_s.camelize
relat_props[relation_name] = {
type: :object,
properties: {
data: {
type: :array,
items: {
type: :object,
properties: {
type: { type: :string, default: relation.table_name },
id: { type: :string, description: "#{relation_name_camelize} ID" },
},
},
description: tt(:related_ids, model: relation_name_camelize)
}
},
description: tt(:related_ids, model: relation_name_camelize)
}
if relation.belongs_to?
relat_props[relation_name][:properties][:data] = {
type: :object,
properties: {
type: { type: :string, default: relation.table_name },
id: { type: :string, description: "#{relation_name_camelize} ID" },
},
description: tt(:related_id, model: relation_name_camelize)
}
end
end
end
end
def list_resource_responses
{
'200' => {
description: tt(:get_list),
schema: {
type: :object,
properties: {
data: {
type: :array,
items: {
type: :object,
properties: {
id: { type: :string, description: 'ID'},
links: {
type: :object,
properties: {
self: { type: :string, description: tt(:detail_link) },
},
description: tt(:detail_link)
},
attributes: {
type: :object,
properties: properties(attrs: attributes.each_key),
description: tt(:attributes)
},
relationships: {
type: :object,
properties: relationships_properties,
description: tt(:associate_data)
}
},
},
description: tt(:data)
},
meta: {
type: :object,
properties: {
record_count: { type: :integer, description: tt(:record_count)},
page_count: { type: :integer, description: tt(:page_count)},
},
description: tt(:meta)
},
links: {
type: :object,
properties: {
first: { type: :string, description: tt(:first_page_link) },
next: { type: :string, description: tt(:next_page_link) },
last: { type: :string, description: tt(:last_page_link) },
},
description: tt(:page_links) },
},
required: [:data]
}
}
}
end
def show_resource_responses
{
'200' => {
description: tt(:get_detail),
schema: show_resource_schema
}
}
end
def create_resource_responses
{
'201' => {
description: tt(:create),
schema: show_resource_schema
}
}
end
def delete_resource_responses
{
'204' => { description: tt(:delete) }
}
end
def show_resource_schema
{
type: :object,
properties: {
data: {
type: :object,
properties: {
id: { type: :string, description: 'ID'},
type: { type: :string, description: 'Type'},
links: {
type: :object,
properties: {
self: { type: :string, description: tt(:detail_link) },
},
description: tt(:detail_link)
},
attributes: {
type: :object,
properties: properties(attrs: attributes.each_key),
description: tt(:attributes)
},
relationships: {
type: :object,
properties: relationships_properties,
description: tt(:associate_data)
}
},
description: tt(:data)
}
},
required: [:data]
}
end
doc['paths']["/#{route_resouces}"] = {
get: {
summary: "#{route_resouces} #{tt(:list)}",
tags: [route_resouces],
produces: ['application/vnd.api+json'],
parameters: list_resource_parameters,
responses: list_resource_responses
}
}
doc['paths']["/#{route_resouces}/{id}"] = {
get: {
summary: "#{route_resouces} #{tt(:detail)}",
tags: [route_resouces],
produces: ['application/vnd.api+json'],
parameters: show_resource_parameters,
responses: show_resource_responses
}
}
if resource_klass.mutable?
doc['paths']["/#{route_resouces}"].merge!({
post: {
summary: "#{route_resouces} #{tt(:create)}",
tags: [route_resouces],
consumes: ['application/vnd.api+json'],
produces: ['application/vnd.api+json'],
parameters: [create_resource_parameters],
responses: create_resource_responses
}
})
doc['paths']["/#{route_resouces}/{id}"].merge!({
patch: {
summary: "#{route_resouces} #{tt(:patch)}",
tags: [route_resouces],
consumes: ['application/vnd.api+json'],
produces: ['application/vnd.api+json'],
parameters: patch_resource_parameters,
responses: show_resource_responses
}
})
doc['paths']["/#{route_resouces}/{id}"].merge!({
delete: {
summary: "#{route_resouces} #{tt(:delete)}",
tags: [route_resouces],
produces: ['application/vnd.api+json'],
parameters: delete_resource_parameters,
responses: delete_resource_responses
}
})
else
doc['paths']["/#{route_resouces}"].delete(:post)
doc['paths']["/#{route_resouces}/{id}"].delete(:patch)
doc['paths']["/#{route_resouces}/{id}"].delete(:delete)
end
-%>
"paths": <%= JSON.pretty_generate(doc['paths'] ) %>
}

View File

@ -3,7 +3,11 @@ RSpec.describe '<%= resouces_name %>', type: :request do
let(:include) {''} #see https://github.com/domaindrivendev/rswag/issues/188
before(:each) do
<% if defined?(FactoryBot) -%>
@<%= model_name %> = create :<%= model_name %>
<% else -%>
@<%= model_name %> = <%= model_class_name %>.create
<% end -%>
end
path '/<%= route_resouces %>' do

View File

@ -1,6 +1,7 @@
en:
jsonapi_swagger:
page_num: 'Page Number'
page_size: 'Page Size'
include_related_data: 'Include Related Data'
sortable_fields: 'Sortable Fields'
display_field: 'Display Field'

View File

@ -1,6 +1,7 @@
zh-CN:
jsonapi_swagger:
page_num: '页码'
page_size: '每页条数'
include_related_data: '包含关联数据'
sortable_fields: '排序字段'
display_field: '显示字段'

View File

@ -2,9 +2,38 @@
require 'jsonapi/swagger/version'
require 'jsonapi/swagger/railtie' if defined?(Rails)
require 'jsonapi/swagger/json'
module Jsonapi
module Swagger
class Error < StandardError; end
class << self
attr_accessor :version, :info, :file_path, :base_path, :use_rswag
def config
yield self
end
def version
@version ||= '2.0'
end
def info
@info ||= { title: 'API V1', version: 'V1' }
end
def file_path
@file_path ||= 'v1/swagger.json'
end
def base_path
@base_path
end
def use_rswag
@use_rswag ||= false
end
end
end
end

View File

@ -0,0 +1,31 @@
module Jsonapi
module Swagger
class Json
attr_accessor :path
def initialize(path = 'swagger/v1/swagger.json')
@path = path
end
def parse_doc
@doc ||= JSON.parse(load) rescue Hash.new{ |h, k| h[k]= {} }
end
def base_path
Jsonapi::Swagger.base_path
end
def load
@data ||= if File.exist?(path)
IO.read(path)
else
puts "create swagger.json in #{path}"
'{}'
end
end
end
end
end