jsonapi-swagger 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 92c1984cded48de28c42fc2c6cea1942f17a985a485c0620941a55e8a6d8259c
4
- data.tar.gz: 5ecc81a8c945a282346a0f20420be0cd047fcf715abea97986cf374285b780d1
3
+ metadata.gz: 5ac2522e526917a176239ca6a576b1313b935b13e5f1abd6c361a4d2650867e4
4
+ data.tar.gz: cba8776fff22be079490f5c450e707b2b94b08da277b58f5fa88ebbdda7bdc77
5
5
  SHA512:
6
- metadata.gz: b1918de62437786e214d29396a254693899d34325d3762a20c0327bf8f340b848a36a92ae3815e16b607fbb7b07957e7d2fc7ca3b842b9f991750edcc084f6cc
7
- data.tar.gz: 8f93aa0c863bc241859ca952f2d103346552af40882bbfa35d68900561a832e0af3daf4c131c361e0bced923ccf1d35e3347828fe88565bb23663cc3805ebfde
6
+ metadata.gz: c776c446efd665e72bbbb54595984fa37c92a250c351a4f237eebcc83f73505f63eb18574ccde7b18bc9695ac155c2d3bdd12697dd445faff56a8871c4e74994
7
+ data.tar.gz: ebf4e2010729433bc3b8e2af940d744b33810bf1a5579e24c0db61bf51b65c0196092110528ff9fe97132f52bee508d57029c18aa3af13897096c4607e0583db
data/README.md CHANGED
@@ -2,7 +2,8 @@
2
2
 
3
3
  Generate JSONAPI Swagger Doc.
4
4
 
5
- [![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)
5
+ [![Gem Version](https://img.shields.io/gem/v/jsonapi-swagger.svg)](https://rubygems.org/gems/jsonapi-swagger)
6
+ [![GitHub license](https://img.shields.io/github/license/superiorlu/jsonapi-swagger.svg)](https://github.com/superiorlu/jsonapi-swagger/blob/master/LICENSE)
6
7
 
7
8
  [![jsonapi-swagger-4-2-9.gif](https://i.loli.net/2019/05/05/5ccebf5e782b7.gif)](https://i.loli.net/2019/05/05/5ccebf5e782b7.gif)
8
9
 
@@ -24,10 +25,40 @@ Or install it yourself as:
24
25
 
25
26
  ## Usage
26
27
 
28
+ 1. config jsonapi swagger
29
+ ```rb
30
+ # config/initializers/swagger.rb
31
+ Jsonapi::Swagger.config do |config|
32
+ config.use_rswag = false
33
+ config.version = '2.0'
34
+ config.info = { title: 'API V1', version: 'V1'}
35
+ config.file_path = 'v1/swagger.json'
36
+ end
37
+ ```
38
+
39
+ 2. generate swagger.json
40
+
41
+ ```sh
42
+ # gen swagger/v1/swagger.json
43
+ bundle exec rails generate jsonapi:swagger User # UserResponse < JSONAPI::Resource
44
+ ```
45
+
46
+ 3. additional
47
+
48
+ use `rswag`, have to run
49
+
27
50
  ```sh
28
- rails generate jsonapi:swagger User # UserResponse < JSONAPI::Resource
51
+ # gen swagger/v1/swagger.json
52
+ bundle exec rails rswag:specs:swaggerize
29
53
  ```
30
54
 
55
+ ## RoadMap
56
+
57
+ - [x] immutable resources
58
+ - [x] filter/sort resources
59
+ - [x] mutable resources
60
+ - [x] generate swagger.json without rswag
61
+
31
62
  ## Resource
32
63
 
33
64
  - [JSONAPI](https://jsonapi.org/)
@@ -36,4 +67,5 @@ rails generate jsonapi:swagger User # UserResponse < JSONAPI::Resource
36
67
 
37
68
  ## Contributing
38
69
 
39
- Bug reports and pull requests are welcome on GitHub at https://github.com/superiorlu/jsonapi-swagger.
70
+ Bug reports and pull requests are welcome on GitHub at
71
+ https://github.com/superiorlu/jsonapi-swagger.
@@ -4,15 +4,54 @@ module Jsonapi
4
4
  source_root File.expand_path('templates', __dir__)
5
5
 
6
6
  def create_swagger_file
7
- swagger_file = File.join(
7
+ if Jsonapi::Swagger.use_rswag
8
+ template 'swagger.rb.erb', spec_file
9
+ else
10
+ template 'swagger.json.erb', json_file
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def doc
17
+ @doc ||= swagger_json.parse_doc
18
+ end
19
+
20
+ def spec_file
21
+ @spec_file ||= File.join(
8
22
  'spec/requests',
9
23
  class_path,
10
24
  spec_file_name
11
25
  )
12
- template 'swagger.rb.erb', swagger_file
13
26
  end
14
27
 
15
- private
28
+ def json_file
29
+ @json_file ||= File.join(
30
+ 'swagger',
31
+ class_path,
32
+ swagger_file_path
33
+ )
34
+ end
35
+
36
+ def swagger_version
37
+ Jsonapi::Swagger.version
38
+ end
39
+
40
+ def swagger_info
41
+ JSON.pretty_generate(Jsonapi::Swagger.info)
42
+ end
43
+
44
+ def swagger_base_path
45
+ Jsonapi::Swagger.base_path
46
+ end
47
+
48
+ def swagger_file_path
49
+ Jsonapi::Swagger.file_path
50
+ end
51
+
52
+ def swagger_json
53
+ @swagger_json ||= Jsonapi::Swagger::Json.new(json_file)
54
+ end
16
55
 
17
56
  def spec_file_name
18
57
  "#{file_name.downcase.pluralize}_spec.rb"
@@ -38,6 +77,10 @@ module Jsonapi
38
77
  t(:sortable_fields) + ': (-)' + sortable_fields.join(',')
39
78
  end
40
79
 
80
+ def ori_sortable_fields_desc
81
+ tt(:sortable_fields) + ': (-)' + sortable_fields.join(',')
82
+ end
83
+
41
84
  def model_klass
42
85
  model_class_name.safe_constantize
43
86
  end
@@ -70,10 +113,11 @@ module Jsonapi
70
113
  resource_klass.filters
71
114
  end
72
115
 
73
- def columns_with_comment
116
+ def columns_with_comment(need_encoding: true)
74
117
  @columns_with_comment ||= {}.tap do |clos|
75
118
  model_klass.columns.each do |col|
76
- 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) }
119
+ clos[col.name.to_sym] = { type: swagger_type(col), items_type: col.type, is_array: col.array, nullable: col.null, comment: col.comment }
120
+ clos[col.name.to_sym][:comment] = safe_encode(col.comment) if need_encoding
77
121
  end
78
122
  end
79
123
  end
@@ -89,10 +133,14 @@ module Jsonapi
89
133
  end
90
134
 
91
135
  def t(key, options={})
136
+ content = tt(key, options)
137
+ safe_encode(content)
138
+ end
139
+
140
+ def tt(key, options={})
92
141
  options[:scope] = :jsonapi_swagger
93
142
  options[:default] = key.to_s.humanize
94
- content = I18n.t(key, options)
95
- safe_encode(content)
143
+ I18n.t(key, options)
96
144
  end
97
145
 
98
146
  def safe_encode(content)
@@ -0,0 +1,319 @@
1
+ {
2
+ "swagger": "<%= swagger_version %>",
3
+ "info": <%= swagger_info %>,
4
+ "basePath" : "<%= swagger_base_path %>",
5
+ <%-
6
+ def list_resource_parameters
7
+ [].tap do |parameters|
8
+ parameters << { name: 'page[number]', in: :query, type: :string, description: tt(:page_num), required: false }
9
+ parameters << { name: 'page[size]', in: :query, type: :string, description: tt(:page_size), required: false }
10
+ if sortable_fields.present?
11
+ parameters << { name: 'sort', in: :query, type: :string, description: ori_sortable_fields_desc, required: false }
12
+ end
13
+ if relationships.present?
14
+ parameters << { name: :include, in: :query, type: :string, description: tt(:include_related_data), required: false }
15
+ end
16
+ filters.each do |filter_attr, filter_config|
17
+ parameters << { name: :"filter[#{filter_attr}]", in: :query, type: :string, description: tt(:filter_field), required: false}
18
+ end
19
+ parameters << { name: :"fields[#{route_resouces}]", in: :query, type: :string, description: tt(:display_field), required: false }
20
+ relationships.each_value do |relation|
21
+ parameters << { name: :"fields[#{relation.class_name.tableize}]", in: :query, type: :string, description: tt(:display_field), required: false }
22
+ end
23
+ end
24
+ end
25
+
26
+ def show_resource_parameters
27
+ [].tap do |parameters|
28
+ parameters << { name: :id, in: :path, type: :integer, description: 'ID', required: true }
29
+ if relationships.present?
30
+ parameters << { name: :include, in: :query, type: :string, description: tt(:include_related_data), required: false }
31
+ end
32
+ parameters << { name: :"fields[#{route_resouces}]", in: :query, type: :string, description: tt(:display_field), required: false }
33
+ relationships.each_value do |relation|
34
+ parameters << { name: :"fields[#{relation.class_name.tableize}]", in: :query, type: :string, description: tt(:display_field), required: false }
35
+ end
36
+ end
37
+ end
38
+
39
+ def create_resource_parameters
40
+ parameters = {
41
+ name: :data,
42
+ in: :body,
43
+ type: :object,
44
+ properties: {
45
+ data: {
46
+ type: :object,
47
+ properties: {
48
+ type: { type: :string, default: route_resouces },
49
+ attributes: {
50
+ type: :object,
51
+ properties: properties(attrs: creatable_fields)
52
+ }
53
+ }
54
+ },
55
+ },
56
+ description: tt(:request_body)
57
+ }
58
+ parameters[:properties][:data][:properties][:relationships] ||= {}
59
+ parameters[:properties][:data][:properties][:relationships] = { type: :object, properties: create_relationships_properties }
60
+ parameters
61
+ end
62
+
63
+ def patch_resource_parameters
64
+ patch_parameters = create_resource_parameters.dup
65
+ patch_parameters[:properties][:data][:properties][:id] ||= {}
66
+ patch_parameters[:properties][:data][:properties][:id].merge!({ type: :integer, description: 'ID' })
67
+ parameters = [{ name: :id, in: :path, type: :integer, description: 'ID', required: true }]
68
+ parameters << patch_parameters
69
+ parameters
70
+ end
71
+
72
+ def delete_resource_parameters
73
+ [{ name: :id, in: :path, type: :integer, description: 'ID', required: true }]
74
+ end
75
+
76
+ def properties(attrs: [])
77
+ Hash.new{|h, k| h[k] = {}} .tap do |props|
78
+ attrs.each do |attr|
79
+ columns = columns_with_comment(need_encoding: false)
80
+ props[attr][:type] = columns[attr][:type]
81
+ props[attr][:items] = { type: columns[attr][:items_type] } if columns[attr][:is_array]
82
+ props[attr][:'x-nullable'] = columns[attr][:nullable]
83
+ props[attr][:description] = columns[attr][:comment]
84
+ end
85
+ end
86
+ end
87
+
88
+ def relationships_properties
89
+ {}.tap do |relat_props|
90
+ relationships.each do |relation_name, relation|
91
+ relation_name_camelize = relation_name.to_s.camelize
92
+ relat_props[relation_name] = {
93
+ type: :object,
94
+ properties: {
95
+ links: {
96
+ type: :object,
97
+ properties: {
98
+ self: { type: :string, description: tt(:associate_list_link, model: relation_name_camelize) },
99
+ related: { type: :string, description: tt(:related_link, model: relation_name_camelize) },
100
+ },
101
+ description: tt(:related_link, model: relation_name_camelize)
102
+ },
103
+ },
104
+ description: tt(:related_model, model: relation_name_camelize)
105
+ }
106
+ end
107
+ end
108
+ end
109
+
110
+ def create_relationships_properties
111
+ {}.tap do |relat_props|
112
+ relationships.each do |relation_name, relation|
113
+ relation_name_camelize = relation_name.to_s.camelize
114
+ relat_props[relation_name] = {
115
+ type: :object,
116
+ properties: {
117
+ data: {
118
+ type: :array,
119
+ items: {
120
+ type: :object,
121
+ properties: {
122
+ type: { type: :string, default: relation.table_name },
123
+ id: { type: :string, description: "#{relation_name_camelize} ID" },
124
+ },
125
+ },
126
+ description: tt(:related_ids, model: relation_name_camelize)
127
+ }
128
+ },
129
+ description: tt(:related_ids, model: relation_name_camelize)
130
+ }
131
+ if relation.belongs_to?
132
+ relat_props[relation_name][:properties][:data] = {
133
+ type: :object,
134
+ properties: {
135
+ type: { type: :string, default: relation.table_name },
136
+ id: { type: :string, description: "#{relation_name_camelize} ID" },
137
+ },
138
+ description: tt(:related_id, model: relation_name_camelize)
139
+ }
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ def list_resource_responses
146
+ {
147
+ '200' => {
148
+ description: tt(:get_list),
149
+ schema: {
150
+ type: :object,
151
+ properties: {
152
+ data: {
153
+ type: :array,
154
+ items: {
155
+ type: :object,
156
+ properties: {
157
+ id: { type: :string, description: 'ID'},
158
+ links: {
159
+ type: :object,
160
+ properties: {
161
+ self: { type: :string, description: tt(:detail_link) },
162
+ },
163
+ description: tt(:detail_link)
164
+ },
165
+ attributes: {
166
+ type: :object,
167
+ properties: properties(attrs: attributes.each_key),
168
+ description: tt(:attributes)
169
+ },
170
+ relationships: {
171
+ type: :object,
172
+ properties: relationships_properties,
173
+ description: tt(:associate_data)
174
+ }
175
+ },
176
+ },
177
+ description: tt(:data)
178
+ },
179
+ meta: {
180
+ type: :object,
181
+ properties: {
182
+ record_count: { type: :integer, description: tt(:record_count)},
183
+ page_count: { type: :integer, description: tt(:page_count)},
184
+ },
185
+ description: tt(:meta)
186
+ },
187
+ links: {
188
+ type: :object,
189
+ properties: {
190
+ first: { type: :string, description: tt(:first_page_link) },
191
+ next: { type: :string, description: tt(:next_page_link) },
192
+ last: { type: :string, description: tt(:last_page_link) },
193
+ },
194
+ description: tt(:page_links) },
195
+ },
196
+ required: [:data]
197
+ }
198
+ }
199
+ }
200
+ end
201
+
202
+ def show_resource_responses
203
+ {
204
+ '200' => {
205
+ description: tt(:get_detail),
206
+ schema: show_resource_schema
207
+ }
208
+ }
209
+ end
210
+
211
+ def create_resource_responses
212
+ {
213
+ '201' => {
214
+ description: tt(:create),
215
+ schema: show_resource_schema
216
+ }
217
+ }
218
+ end
219
+
220
+ def delete_resource_responses
221
+ {
222
+ '204' => { description: tt(:delete) }
223
+ }
224
+ end
225
+
226
+ def show_resource_schema
227
+ {
228
+ type: :object,
229
+ properties: {
230
+ data: {
231
+ type: :object,
232
+ properties: {
233
+ id: { type: :string, description: 'ID'},
234
+ type: { type: :string, description: 'Type'},
235
+ links: {
236
+ type: :object,
237
+ properties: {
238
+ self: { type: :string, description: tt(:detail_link) },
239
+ },
240
+ description: tt(:detail_link)
241
+ },
242
+ attributes: {
243
+ type: :object,
244
+ properties: properties(attrs: attributes.each_key),
245
+ description: tt(:attributes)
246
+ },
247
+ relationships: {
248
+ type: :object,
249
+ properties: relationships_properties,
250
+ description: tt(:associate_data)
251
+ }
252
+ },
253
+ description: tt(:data)
254
+ }
255
+ },
256
+ required: [:data]
257
+ }
258
+ end
259
+
260
+ doc['paths']["/#{route_resouces}"] = {
261
+ get: {
262
+ summary: "#{route_resouces} #{tt(:list)}",
263
+ tags: [route_resouces],
264
+ produces: ['application/vnd.api+json'],
265
+ parameters: list_resource_parameters,
266
+ responses: list_resource_responses
267
+ }
268
+ }
269
+
270
+ doc['paths']["/#{route_resouces}/{id}"] = {
271
+ get: {
272
+ summary: "#{route_resouces} #{tt(:detail)}",
273
+ tags: [route_resouces],
274
+ produces: ['application/vnd.api+json'],
275
+ parameters: show_resource_parameters,
276
+ responses: show_resource_responses
277
+ }
278
+ }
279
+
280
+ if resource_klass.mutable?
281
+ doc['paths']["/#{route_resouces}"].merge!({
282
+ post: {
283
+ summary: "#{route_resouces} #{tt(:create)}",
284
+ tags: [route_resouces],
285
+ consumes: ['application/vnd.api+json'],
286
+ produces: ['application/vnd.api+json'],
287
+ parameters: [create_resource_parameters],
288
+ responses: create_resource_responses
289
+ }
290
+ })
291
+
292
+ doc['paths']["/#{route_resouces}/{id}"].merge!({
293
+ patch: {
294
+ summary: "#{route_resouces} #{tt(:patch)}",
295
+ tags: [route_resouces],
296
+ consumes: ['application/vnd.api+json'],
297
+ produces: ['application/vnd.api+json'],
298
+ parameters: patch_resource_parameters,
299
+ responses: show_resource_responses
300
+ }
301
+ })
302
+
303
+ doc['paths']["/#{route_resouces}/{id}"].merge!({
304
+ delete: {
305
+ summary: "#{route_resouces} #{tt(:delete)}",
306
+ tags: [route_resouces],
307
+ produces: ['application/vnd.api+json'],
308
+ parameters: delete_resource_parameters,
309
+ responses: delete_resource_responses
310
+ }
311
+ })
312
+ else
313
+ doc['paths']["/#{route_resouces}"].delete(:post)
314
+ doc['paths']["/#{route_resouces}/{id}"].delete(:patch)
315
+ doc['paths']["/#{route_resouces}/{id}"].delete(:delete)
316
+ end
317
+ -%>
318
+ "paths": <%= JSON.pretty_generate(doc['paths'] ) %>
319
+ }
@@ -3,7 +3,11 @@ RSpec.describe '<%= resouces_name %>', type: :request do
3
3
  let(:include) {''} #see https://github.com/domaindrivendev/rswag/issues/188
4
4
 
5
5
  before(:each) do
6
+ <% if defined?(FactoryBot) -%>
6
7
  @<%= model_name %> = create :<%= model_name %>
8
+ <% else -%>
9
+ @<%= model_name %> = <%= model_class_name %>.create
10
+ <% end -%>
7
11
  end
8
12
 
9
13
  path '/<%= route_resouces %>' do
data/lib/i18n/en.yml CHANGED
@@ -1,6 +1,7 @@
1
1
  en:
2
2
  jsonapi_swagger:
3
3
  page_num: 'Page Number'
4
+ page_size: 'Page Size'
4
5
  include_related_data: 'Include Related Data'
5
6
  sortable_fields: 'Sortable Fields'
6
7
  display_field: 'Display Field'
data/lib/i18n/zh-CN.yml CHANGED
@@ -1,6 +1,7 @@
1
1
  zh-CN:
2
2
  jsonapi_swagger:
3
3
  page_num: '页码'
4
+ page_size: '每页条数'
4
5
  include_related_data: '包含关联数据'
5
6
  sortable_fields: '排序字段'
6
7
  display_field: '显示字段'
@@ -2,9 +2,38 @@
2
2
 
3
3
  require 'jsonapi/swagger/version'
4
4
  require 'jsonapi/swagger/railtie' if defined?(Rails)
5
+ require 'jsonapi/swagger/json'
5
6
 
6
7
  module Jsonapi
7
8
  module Swagger
8
9
  class Error < StandardError; end
10
+
11
+ class << self
12
+ attr_accessor :version, :info, :file_path, :base_path, :use_rswag
13
+
14
+ def config
15
+ yield self
16
+ end
17
+
18
+ def version
19
+ @version ||= '2.0'
20
+ end
21
+
22
+ def info
23
+ @info ||= { title: 'API V1', version: 'V1' }
24
+ end
25
+
26
+ def file_path
27
+ @file_path ||= 'v1/swagger.json'
28
+ end
29
+
30
+ def base_path
31
+ @base_path
32
+ end
33
+
34
+ def use_rswag
35
+ @use_rswag ||= false
36
+ end
37
+ end
9
38
  end
10
39
  end
@@ -0,0 +1,31 @@
1
+ module Jsonapi
2
+ module Swagger
3
+ class Json
4
+
5
+ attr_accessor :path
6
+
7
+ def initialize(path = 'swagger/v1/swagger.json')
8
+ @path = path
9
+ end
10
+
11
+ def parse_doc
12
+ @doc ||= JSON.parse(load) rescue Hash.new{ |h, k| h[k]= {} }
13
+ end
14
+
15
+ def base_path
16
+ Jsonapi::Swagger.base_path
17
+ end
18
+
19
+ def load
20
+ @data ||= if File.exist?(path)
21
+ IO.read(path)
22
+ else
23
+ puts "create swagger.json in #{path}"
24
+ '{}'
25
+ end
26
+ end
27
+
28
+
29
+ end
30
+ end
31
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Jsonapi
4
4
  module Swagger
5
- VERSION = '0.5.0'
5
+ VERSION = '0.6.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jsonapi-swagger
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - YingRui Lu
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-05-08 00:00:00.000000000 Z
11
+ date: 2019-06-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -59,7 +59,7 @@ dependencies:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
61
  version: '2.0'
62
- type: :runtime
62
+ type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
@@ -76,17 +76,18 @@ files:
76
76
  - README.md
77
77
  - lib/generators/jsonapi/swagger/USAGE
78
78
  - lib/generators/jsonapi/swagger/swagger_generator.rb
79
+ - lib/generators/jsonapi/swagger/templates/swagger.json.erb
79
80
  - lib/generators/jsonapi/swagger/templates/swagger.rb.erb
80
81
  - lib/i18n/en.yml
81
82
  - lib/i18n/zh-CN.yml
82
83
  - lib/jsonapi/swagger.rb
84
+ - lib/jsonapi/swagger/json.rb
83
85
  - lib/jsonapi/swagger/railtie.rb
84
86
  - lib/jsonapi/swagger/version.rb
85
87
  homepage: https://github.com/superiorlu/jsonapi-swagger
86
88
  licenses:
87
89
  - MIT
88
- metadata:
89
- allowed_push_host: https://rubygems.org
90
+ metadata: {}
90
91
  post_install_message:
91
92
  rdoc_options: []
92
93
  require_paths: