mirror of
https://github.com/ditkrg/jsonapi-swagger.git
synced 2026-01-22 13:56:54 +00:00
support generate swagger.json without rswag
This commit is contained in:
parent
4fa533ea26
commit
3cfe68b421
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
319
lib/generators/jsonapi/swagger/templates/swagger.json.erb
Normal file
319
lib/generators/jsonapi/swagger/templates/swagger.json.erb
Normal 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'] ) %>
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
zh-CN:
|
||||
jsonapi_swagger:
|
||||
page_num: '页码'
|
||||
page_size: '每页条数'
|
||||
include_related_data: '包含关联数据'
|
||||
sortable_fields: '排序字段'
|
||||
display_field: '显示字段'
|
||||
|
||||
@ -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
|
||||
|
||||
31
lib/jsonapi/swagger/json.rb
Normal file
31
lib/jsonapi/swagger/json.rb
Normal 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
|
||||
Loading…
Reference in New Issue
Block a user