Compare commits

...

28 Commits

Author SHA1 Message Date
352fd3da35
Update version.rb 2022-01-19 16:36:26 +03:00
bee05fea2b
Uses only jsonapi-rb 2022-01-19 16:36:12 +03:00
b301d53f02
Update version.rb 2022-01-19 16:30:01 +03:00
c7eaef79ab
Update swagger_generator.rb 2022-01-19 16:28:58 +03:00
YingRui Lu
8efe5ff488 bump version 0.8.1 2020-11-21 06:23:14 +08:00
YingRui Lu
1b01e6a31a
Merge pull request #10 from jksy/feature/fix-raise-error-on-mysql
Fixed an error that occurred when using this on mysql.
2020-11-21 05:47:32 +08:00
Junichiro Kasuya
a0f7422793 fix "undefined method array or #<ActiveRecord::ConnectionAdapters::MySQL::Column" 2020-11-11 11:49:23 +09:00
YingRui Lu
9eae4f0b0b update rake version 2020-03-11 13:19:18 +08:00
YingRui Lu
62ceb48307 bump version 0.8.0 2020-01-05 20:34:31 +08:00
YingRui Lu
5f7c3b03fa Merge branch 'master' of github.com:superiorlu/jsonapi-swagger 2020-01-05 20:32:49 +08:00
YingRui Lu
4c428d90b9 rename delegate instance name 2020-01-05 20:32:07 +08:00
YingRui Lu
a4c974ab9e support fast_jsonapi 2020-01-05 20:26:02 +08:00
YingRui Lu
c24a3eec37
Update gempush.yml 2019-08-27 00:27:17 +08:00
YingRui Lu
157735a266
Update gempush.yml 2019-08-27 00:02:52 +08:00
YingRui Lu
b8e688165b bump version 0.7.1 2019-07-20 00:36:56 +08:00
YingRui Lu
f2e93dd395 fix model class constantize 2019-07-20 00:22:33 +08:00
YingRui Lu
e5fba11472 bump version 0.7.0 2019-06-30 09:54:12 +08:00
YingRui Lu
8e32f7dc88 support jsonapi rails 2019-06-30 09:51:22 +08:00
YingRui Lu
1150c75b00 update README.md 2019-06-19 14:28:58 +08:00
YingRui Lu
288f9f89cd update README.md 2019-06-19 14:24:20 +08:00
YingRui Lu
a5fe2f7fdc bump version 0.6.0 2019-06-19 14:11:56 +08:00
YingRui Lu
3cfe68b421 support generate swagger.json without rswag 2019-06-19 14:06:56 +08:00
YingRui Lu
4fa533ea26 update README.md 2019-05-14 14:09:49 +08:00
YingRui Lu
0ed11cceb0 bump version 0.5.0 2019-05-08 18:21:07 +08:00
YingRui Lu
fc51075db7 update sortable fields desc 2019-05-08 18:18:10 +08:00
YingRui Lu
af6f6208ee support create update and delete resource 2019-05-08 18:16:28 +08:00
YingRui Lu
4ca14d7cdf support sortable fields 2019-05-08 14:01:34 +08:00
YingRui Lu
17a263a9d1 update README 2019-05-05 18:57:08 +08:00
15 changed files with 958 additions and 36 deletions

32
.github/workflows/gempush.yml vendored Normal file
View File

@ -0,0 +1,32 @@
name: jsonapi-swagger
on:
pull_request:
branches:
- master
push:
branches:
- master
jobs:
build:
name: Build + Publish
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Set up Ruby 2.6
uses: actions/setup-ruby@v1
with:
version: 2.6.x
- name: Publish to RubyGems
run: |
mkdir -p $HOME/.gem
touch $HOME/.gem/credentials
chmod 0600 $HOME/.gem/credentials
printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
gem build *.gemspec
gem push *.gem
env:
GEM_HOST_API_KEY: ${{secrets.RUBYGEMS_AUTH_TOKEN}}

View File

@ -2,9 +2,10 @@
Generate JSONAPI Swagger Doc.
[![Gem Version](https://img.shields.io/gem/v/jsonapi-swagger.svg)](https://rubygems.org/gems/jsonapi-swagger) [![GitHub license](https://img.shields.io/github/license/superiorlu/jsonapi-swagger.svg)](https://github.com/superiorlu/jsonapi-swagger/blob/master/LICENSE)
[![Gem Version](https://img.shields.io/gem/v/jsonapi-swagger.svg)](https://rubygems.org/gems/jsonapi-swagger)
[![GitHub license](https://img.shields.io/github/license/superiorlu/jsonapi-swagger.svg)](https://github.com/superiorlu/jsonapi-swagger/blob/master/LICENSE)
![jsonapi-swagger](https://i.loli.net/2019/05/03/5ccbf89ed2b9a.gif)
[![jsonapi-swagger-4-2-9.gif](https://i.loli.net/2019/05/05/5ccebf5e782b7.gif)](https://i.loli.net/2019/05/05/5ccebf5e782b7.gif)
## Installation
@ -24,10 +25,40 @@ Or install it yourself as:
## Usage
```sh
rails generate jsonapi:swagger User # UserResponse < JSONAPI::Resource
1. config jsonapi swagger
```rb
# config/initializers/swagger.rb
Jsonapi::Swagger.config do |config|
config.use_rswag = false
config.version = '2.0'
config.info = { title: 'API V1', version: 'V1'}
config.file_path = 'v1/swagger.json'
end
```
2. generate swagger.json
```sh
# gen swagger/v1/swagger.json
bundle exec rails generate jsonapi:swagger User # UserResource < JSONAPI::Resource
```
3. additional
use `rswag`, have to run
```sh
# gen swagger/v1/swagger.json
bundle exec rails rswag:specs:swaggerize
```
## RoadMap
- [x] immutable resources
- [x] filter/sort resources
- [x] mutable resources
- [x] generate swagger.json without rswag
## Resource
- [JSONAPI](https://jsonapi.org/)
@ -36,4 +67,5 @@ rails generate jsonapi:swagger User # UserResponse < JSONAPI::Resource
## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/superiorlu/jsonapi-swagger.
Bug reports and pull requests are welcome on GitHub at
https://github.com/superiorlu/jsonapi-swagger.

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']
@ -30,8 +21,7 @@ Gem::Specification.new do |spec|
spec.licenses = ['MIT']
spec.add_development_dependency 'bundler', '~> 2.0'
spec.add_development_dependency 'rake', '~> 10.0'
spec.add_development_dependency 'rake', '>= 12.3.3'
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"
@ -34,36 +73,74 @@ module Jsonapi
(class_path + [file_name]).map!(&:camelize).join("::")
end
def sortable_fields_desc
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
file_name.camelize.safe_constantize
end
def resource_klass
"#{model_class_name}Resource".safe_constantize
@resource_klass ||= Jsonapi::Swagger::Resource.with(model_class_name)
end
def attributes
resource_klass._attributes.except(:id)
resource_klass.attributes.except(:id)
end
def relationships
resource_klass._relationships
resource_klass.relationships
end
def sortable_fields
resource_klass.sortable_fields
end
def creatable_fields
resource_klass.creatable_fields - relationships.keys
end
def updatable_fields
resource_klass.updatable_fields - relationships.keys
end
def filters
resource_klass.filters
end
def columns_with_comment
def mutable?
resource_klass.mutable?
end
def attribute_default
Jsonapi::Swagger.attribute_default
end
def transform_method
@transform_method ||= resource_klass.transform_method if resource_klass.respond_to?(:transform_method)
end
def columns_with_comment(need_encoding: true)
@columns_with_comment ||= {}.tap do |clos|
clos.default_proc = proc do |h, k|
h[k] = attribute_default
end
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) }
col_name = transform_method ? col.name.send(transform_method) : col.name
is_array = col.respond_to?(:array) ? col.array : false
clos[col_name.to_sym] = { type: swagger_type(col), items_type: col.type, is_array: is_array, nullable: col.null, comment: col.comment }
clos[col_name.to_sym][:comment] = safe_encode(col.comment) if need_encoding
end
end
end
def swagger_type(column)
return 'array' if column.array
return 'array' if column.respond_to?(:array) && column.array
case column.type
when :bigint, :integer then 'integer'
@ -72,11 +149,20 @@ module Jsonapi
end
end
def relation_table_name(relation)
return relation.class_name.tableize if relation.respond_to?(:class_name)
return relation.name if relation.respond_to?(:name)
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_table_name(relation)}]", 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_table_name(relation)}]", 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.try(: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 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
@ -11,6 +15,9 @@ RSpec.describe '<%= resouces_name %>', type: :request do
tags '<%= route_resouces %>'
produces 'application/vnd.api+json'
parameter name: :'page[number]', in: :query, type: :string, description: '<%= t(:page_num) %>', required: false
<% if sortable_fields.present? -%>
parameter name: :'sort', in: :query, type: :string, description: '<%= sortable_fields_desc %>', required: false
<% end -%>
<% if relationships.present? -%>
parameter name: :include, in: :query, type: :string, description: '<%= t(:include_related_data) %>', required: false
<% end -%>
@ -19,7 +26,7 @@ RSpec.describe '<%= resouces_name %>', type: :request do
<% end -%>
parameter name: :'fields[<%= route_resouces %>]', in: :query, type: :string, description: '<%= t(:display_field) %>', required: false
<% relationships.each_value do |relation| -%>
parameter name: :'fields[<%= relation.class_name.tableize %>]', in: :query, type: :string, description: '<%= t(:display_field) %>', required: false
parameter name: :'fields[<%= relation_table_name(relation) %>]', in: :query, type: :string, description: '<%= t(:display_field) %>', required: false
<% end -%>
response '200', '<%= t(:get_list) %>' do
schema type: :object,
@ -106,7 +113,7 @@ RSpec.describe '<%= resouces_name %>', type: :request do
<% end -%>
parameter name: :'fields[<%= route_resouces %>]', in: :query, type: :string, description: '<%= t(:display_field) %>', required: false
<% relationships.each_value do |relation| -%>
parameter name: :'fields[<%= relation.class_name.tableize %>]', in: :query, type: :string, description: '<%= t(:display_field) %>', required: false
parameter name: :'fields[<%= relation_table_name(relation) %>]', in: :query, type: :string, description: '<%= t(:display_field) %>', required: false
<% end -%>
response '200', '<%= t(:get_detail) %>' do
schema type: :object,
@ -165,6 +172,264 @@ RSpec.describe '<%= resouces_name %>', type: :request do
end
end
end
<% unless resource_klass.immutable -%>
<% if mutable? -%>
path '/<%= route_resouces %>' do
post '<%= route_resouces %> <%= t(:create) %>' do
tags '<%= route_resouces %>'
consumes 'application/vnd.api+json'
produces 'application/vnd.api+json'
parameter name: :data,
in: :body,
type: :object,
properties: {
data: {
type: :object,
properties: {
type: { type: :string, default: '<%= route_resouces %>' },
attributes: {
type: :object,
properties: {
<% creatable_fields.each do |field| -%>
<%= field %>: { type: :<%= columns_with_comment[field][:type] %>, <%if columns_with_comment[field][:is_array] -%> items: { type: :<%= columns_with_comment[field][:items_type] %>},<% end -%>'x-nullable': <%= columns_with_comment[field][:nullable] %>, description: '<%= columns_with_comment[field][:comment] %>'},
<% end -%>
}
},
<% if relationships.present? -%>
relationships: {
type: :object,
properties: {
<% relationships.each do |relation_name, relation| -%>
<% relation_name_camelize = relation_name.to_s.camelize -%>
<%= relation_name %>: {
type: :object,
properties: {
<% if relation.try(:belongs_to?) -%>
data: {
type: :object,
properties: {
type: { type: :string, default: '<%= relation.table_name %>' },
id: { type: :string, description: '<%= relation_name_camelize %> ID' },
},
description: '<%= t(:related_id, model: relation_name_camelize) %>'
},
<% else -%>
data: {
type: :array,
items: {
type: :object,
properties: {
type: { type: :string, default: '<%= relation.table_name %>' },
id: { type: :string, description: '<%= relation_name_camelize %> ID' },
},
},
description: '<%= t(:related_ids, model: relation_name_camelize) %>'
},
<% end -%>
},
description: '<%= t(:related_ids, model: relation_name_camelize) %>'
},
<% end -%>
}
}
<% end -%>
}
},
},
description: '<%= t(:request_body) %>'
response '201', '<%= t(:create) %>' do
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: '<%= t(:detail_link) %>'},
},
description: '<%= t(:detail_link) %>'
},
attributes: {
type: :object,
properties: {
<% attributes.each_key.each do |attr| -%>
<%= attr %>: { type: :<%= columns_with_comment[attr][:type] %>, <%if columns_with_comment[attr][:is_array] -%> items: { type: :<%= columns_with_comment[attr][:items_type] %>},<% end -%>'x-nullable': <%= columns_with_comment[attr][:nullable] %>, description: '<%= columns_with_comment[attr][:comment] %>'},
<% end -%>
},
description: '<%= t(:attributes) %>'
},
relationships: {
type: :object,
properties: {
<% relationships.each do |relation_name, relation| -%>
<% relation_name_camelize = relation_name.to_s.camelize -%>
<%= relation_name %>: {
type: :object,
properties: {
links: {
type: :object,
properties: {
self: { type: :string, description: '<%= t(:associate_list_link, model: relation_name_camelize) %>' },
related: { type: :string, description: '<%= t(:related_link, model: relation_name_camelize )%>' },
},
description: '<%= t(:related_link, model: relation_name_camelize) %>'
},
},
description: '<%= t(:related_link, model: relation_name_camelize) %>'
},
<% end -%>
},
description: '<%= t(:associate_data) %>'
}
},
description: '<%= t(:data) %>'
},
},
required: [:data]
run_test!
end
end
end
path '/<%= route_resouces %>/{id}' do
patch '<%= route_resouces %> <%= t(:patch) %>' do
tags '<%= route_resouces %>'
consumes 'application/vnd.api+json'
produces 'application/vnd.api+json'
parameter name: :id, in: :path, type: :integer, description: 'ID', required: true
parameter name: :data,
in: :body,
type: :object,
properties: {
data: {
type: :object,
properties: {
type: { type: :string, default: '<%= route_resouces %>' },
id: { type: :string },
attributes: {
type: :object,
properties: {
<% creatable_fields.each do |field| -%>
<%= field %>: { type: :<%= columns_with_comment[field][:type] %>, <%if columns_with_comment[field][:is_array] -%> items: { type: :<%= columns_with_comment[field][:items_type] %>},<% end -%>'x-nullable': <%= columns_with_comment[field][:nullable] %>, description: '<%= columns_with_comment[field][:comment] %>'},
<% end -%>
}
},
<% if relationships.present? -%>
relationships: {
type: :object,
properties: {
<% relationships.each do |relation_name, relation| -%>
<% relation_name_camelize = relation_name.to_s.camelize -%>
<%= relation_name %>: {
type: :object,
properties: {
<% if relation.belongs_to? -%>
data: {
type: :object,
properties: {
type: { type: :string, default: '<%= relation.table_name %>' },
id: { type: :string, description: '<%= relation_name_camelize %> ID' },
},
description: '<%= t(:related_id, model: relation_name_camelize) %>'
},
<% else -%>
data: {
type: :array,
items: {
type: :object,
properties: {
type: { type: :string, default: '<%= relation.table_name %>' },
id: { type: :string, description: '<%= relation_name_camelize %> ID' },
},
},
description: '<%= t(:related_ids, model: relation_name_camelize) %>'
},
<% end -%>
},
description: '<%= t(:related_ids, model: relation_name_camelize) %>'
},
<% end -%>
}
}
<% end -%>
}
},
},
description: '<%= t(:request_body) %>'
response '200', '<%= t(:patch) %>' do
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: '<%= t(:detail_link) %>'},
},
description: '<%= t(:detail_link) %>'
},
attributes: {
type: :object,
properties: {
<% attributes.each_key.each do |attr| -%>
<%= attr %>: { type: :<%= columns_with_comment[attr][:type] %>, <%if columns_with_comment[attr][:is_array] -%> items: { type: :<%= columns_with_comment[attr][:items_type] %>},<% end -%>'x-nullable': <%= columns_with_comment[attr][:nullable] %>, description: '<%= columns_with_comment[attr][:comment] %>'},
<% end -%>
},
description: '<%= t(:attributes) %>'
},
relationships: {
type: :object,
properties: {
<% relationships.each do |relation_name, relation| -%>
<% relation_name_camelize = relation_name.to_s.camelize -%>
<%= relation_name %>: {
type: :object,
properties: {
links: {
type: :object,
properties: {
self: { type: :string, description: '<%= t(:associate_list_link, model: relation_name_camelize) %>' },
related: { type: :string, description: '<%= t(:related_link, model: relation_name_camelize )%>' },
},
description: '<%= t(:related_link, model: relation_name_camelize) %>'
},
},
description: '<%= t(:related_link, model: relation_name_camelize) %>'
},
<% end -%>
},
description: '<%= t(:associate_data) %>'
}
},
description: '<%= t(:data) %>'
},
},
required: [:data]
let(:id) { @<%= model_name %>.id }
run_test!
end
end
end
path '/<%= route_resouces %>/{id}' do
delete '<%= route_resouces %> <%= t(:delete) %>' do
tags '<%= route_resouces %>'
produces 'application/vnd.api+json'
parameter name: :id, in: :path, type: :integer, description: 'ID', required: true
response '204', '<%= t(:delete) %>' do
let(:id) { @<%= model_name %>.id }
run_test!
end
end
end
<% end -%>
end

View File

@ -1,7 +1,9 @@
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'
filter_field: 'Filter Field'
list: 'List'
@ -23,3 +25,9 @@ en:
detail: 'Detail'
get_dtail: 'Fetch Detail'
detail_link: 'Detail Link'
create: 'Create'
patch: 'Patch'
delete: 'Delete'
related_id: 'Related %{model} ID'
related_ids: 'Related %{model} IDs'
request_body: 'Request Body'

View File

@ -1,7 +1,9 @@
zh-CN:
jsonapi_swagger:
page_num: '页码'
page_size: '每页条数'
include_related_data: '包含关联数据'
sortable_fields: '排序字段'
display_field: '显示字段'
filter_field: '过滤字段'
list: '列表'
@ -23,3 +25,9 @@ zh-CN:
detail: '详情'
get_dtail: '获取详情'
detail_link: '详情链接'
create: '创建'
patch: '更新'
delete: '删除'
related_id: '相关%{model}ID'
related_ids: '相关%{model}IDs'
request_body: '请求body'

View File

@ -2,9 +2,43 @@
require 'jsonapi/swagger/version'
require 'jsonapi/swagger/railtie' if defined?(Rails)
require 'jsonapi/swagger/json'
require 'jsonapi/swagger/resource'
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
def attribute_default
@attribute_default ||= { type: :string, nullable: true, comment: nil }
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

View File

@ -0,0 +1,15 @@
require 'forwardable'
module Jsonapi
module Swagger
class Resource
def self.with(model_class_name)
@resource_class = model_class_name.safe_constantize
unless @resource_class < JSONAPI::Serializable::Resource
raise Jsonapi::Swagger::Error, "#{@resource_class.class} is not Subclass of JSONAPI::Serializable::Resource!"
end
require 'jsonapi/swagger/resources/serializable_resource'
return Jsonapi::Swagger::SerializableResource.new(@resource_class)
end
end
end
end

View File

@ -0,0 +1,39 @@
require 'forwardable'
module Jsonapi
module Swagger
class FastJsonapiResource
extend Forwardable
def_delegators :@fr, :attributes_to_serialize, :relationships_to_serialize, :sortable_fields,
:creatable_fields, :updatable_fields, :filters, :mutable?, :transform_method
def initialize(fr)
@fr = fr
end
alias attributes attributes_to_serialize
alias relationships relationships_to_serialize
# TODO: fast_jsonapi resource
def sortable_fields
[]
end
def creatable_fields
[]
end
def updatable_fields
[]
end
def filters
[]
end
def mutable?
false
end
end
end
end

View File

@ -0,0 +1,18 @@
require 'forwardable'
module Jsonapi
module Swagger
class JsonapiResource
extend Forwardable
def_delegators :@jr, :_attributes, :_relationships, :sortable_fields,
:creatable_fields, :updatable_fields, :filters, :mutable?
def initialize(jr)
@jr = jr
end
alias attributes _attributes
alias relationships _relationships
end
end
end

View File

@ -0,0 +1,45 @@
require 'forwardable'
module Jsonapi
module Swagger
class SerializableResource
extend Forwardable
def_delegators :@sr, :type_val, :attribute_blocks, :relationship_blocks, :link_blocks
def initialize(sr)
@sr = sr
end
alias attributes attribute_blocks
def relationships
{}.tap do |relations|
relationship_blocks.each do |rel, block|
relations[rel] = OpenStruct.new(class_name: rel.to_s)
end
end
end
# TODO: from jsonapi serializable resource
def sortable_fields
[]
end
def creatable_fields
[]
end
def updatable_fields
[]
end
def filters
[]
end
def mutable?
false
end
end
end
end

View File

@ -2,6 +2,6 @@
module Jsonapi
module Swagger
VERSION = '0.4.1'
VERSION = '0.8.3'
end
end