jsonapi-swagger 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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: