zero-rails_openapi 1.2.0 → 1.3.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.
@@ -16,7 +16,7 @@ class Api::V1::ExamplesController < Api::V1::BaseController
16
16
  response '567', 'query result export', :pdf, type: File
17
17
  end
18
18
 
19
- open_api :index, '(SUMMARY) this api blah blah ...' do
19
+ open_api :index, '(SUMMARY) this api blah blah ...', builder: :template1 do
20
20
  this_api_is_invalid! 'this api is expired!'
21
21
  desc 'Optional multiline or single-line Markdown-formatted description',
22
22
  id: 'user id',
@@ -0,0 +1,38 @@
1
+ class V2::GoodsDoc < BaseDoc
2
+
3
+ open_api :index, 'Get list of Goods.', builder: :index,
4
+ use: [ :Token ] do # use parameters write in AutoGenDoc#api_dry
5
+ # skip: %i[ Token ] do # you can also skip parameters
6
+ desc 'listing Goods',
7
+ view!: 'search view, allows::<br/>',
8
+ search_type!: 'search field, allows:<br/>'
9
+
10
+ query :view, String, enum: {
11
+ 'all goods (default)': :all,
12
+ 'only online': :online,
13
+ 'only offline': :offline,
14
+ 'expensive goods': :expensive,
15
+ 'cheap goods': :cheap,
16
+ }
17
+ query :search_type, String, enum: %w[name creator category price]
18
+ end
19
+
20
+
21
+ open_api :create, 'Create a Good', builder: :success_or_not, use: token do
22
+ form! 'for creating a good', data: {
23
+ :name! => { type: String, desc: 'good\'s name' },
24
+ :category_id! => { type: Integer, desc: 'sub_category\'s id', npmt: true, range: { ge: 1 }, as: :cate },
25
+ :price! => { type: Float, desc: 'good\'s price', range: { ge: 0} },
26
+ # -- optional
27
+ :is_online => { type: Boolean, desc: 'it\'s online?' },
28
+ :remarks => { type: String, desc: 'remarks' },
29
+ :pic_path => { type: String, desc: 'picture url', is: :url },
30
+ }
31
+ end
32
+
33
+
34
+ open_api :show, 'Show a Good.', builder: :show, use: [ :Token, :id ]
35
+
36
+
37
+ open_api :destroy, 'Delete a Good.', builder: :success_or_not, use: [ :Token, :id ]
38
+ end
@@ -1,6 +1,6 @@
1
1
  require 'open_api'
2
2
 
3
- OpenApi.configure do |c|
3
+ OpenApi::Config.tap do |c|
4
4
  # [REQUIRED] The location where .json doc file will be output.
5
5
  c.file_output_path = 'public/open_api'
6
6
 
@@ -82,6 +82,54 @@ OpenApi.configure do |c|
82
82
  global_security: [{ ApiKeyAuth: [] }],
83
83
  }
84
84
  }
85
+
86
+ c.generate_jbuilder_file = true
87
+ c.overwrite_jbuilder_file = false
88
+ c.jbuilder_templates = {
89
+ index: (
90
+ <<-FILE
91
+ json.partial! 'api/base', total: @data.count
92
+
93
+ json.data do
94
+ # @data = @data.page(@_page).per(@_rows) if @_page || @_rows
95
+ # json.array! @data do |datum|
96
+ json.array! @data.page(@_page).per(@_rows) do |datum|
97
+ json.(datum, *datum.show_attrs) if datum.present?
98
+ end
99
+ end
100
+ FILE
101
+ ),
102
+
103
+ show: (
104
+ <<-FILE
105
+ json.partial! 'api/base', total: 1
106
+
107
+ json.data do
108
+ json.array! [ @data ] do |datum|
109
+ json.(datum, *datum.show_attrs) if datum.present?
110
+ end
111
+ end
112
+ FILE
113
+ ),
114
+
115
+ success: (
116
+ <<-FILE
117
+ json.partial! 'api/success'
118
+ FILE
119
+ ),
120
+
121
+ success_or_not: (
122
+ <<-FILE
123
+ unless @status
124
+ # @_code, @_msg = @error_info.present? ? @error_info : ApiError.action_failed.info
125
+ end
126
+
127
+ json.partial! 'api/base', total: 0
128
+ json.data ''
129
+ FILE
130
+ ),
131
+ }
132
+
85
133
  end
86
134
 
87
135
  Object.const_set('Boolean', 'boolean') # Support `Boolean` writing in DSL
@@ -15,7 +15,7 @@ int32, float, date ...
15
15
  All the types you can use are:
16
16
  - **String, 'binary', 'base64'**
17
17
  - **Integer, Long, 'int32', 'int64', Float, Double**
18
- - **File** (it will be converted as `{ type: 'string', format: OpenApi.config.dft_file_format }`)
18
+ - **File** (it will be converted as `{ type: 'string', format: Config.dft_file_format }`)
19
19
  - **Date, DateTime**
20
20
  - **Boolean**
21
21
  - **Array**: `Array[String]` or `[String]`
@@ -58,4 +58,5 @@ You can set the schema by following keys (all are optional), the words in parent
58
58
  [email phone password uuid uri url time date], to overwrite it you can set it in initializer `c.is_options = %w[]`.
59
59
  5. If type is Object, for describing each property's schema, the only way is use ref type, like: `{ id: :Id, name: :Name }`
60
60
  - **pattern (regexp, pr, reg)**
61
- - **default (dft, default_value)**
61
+ - **default (dft, default_value)**
62
+ - **as** # TODO
@@ -10,11 +10,13 @@ module OpenApi
10
10
  proc { |_, v| truly_present? v }
11
11
  end
12
12
 
13
+ # assign.to
13
14
  def assign(value)
14
15
  @assign = value.is_a?(Symbol) ? send("_#{value}") : value
15
16
  self
16
17
  end
17
18
 
19
+ # reduceee.then_merge! => for Hash
18
20
  def reduceee(*values)
19
21
  @assign = values.compact.reduce({ }, :merge).keep_if &value_present
20
22
  self
@@ -7,7 +7,7 @@ module OpenApi
7
7
  attr_accessor :media_type, :schema
8
8
  def initialize(media_type, schema_hash)
9
9
  self.media_type = media_type_mapping media_type
10
- self.schema = SchemaObj.new(schema_hash[:type] || schema_hash[:data], schema_hash)
10
+ self.schema = SchemaObj.new(schema_hash.values_at(:type, :data, :t).compact.first, schema_hash)
11
11
  end
12
12
 
13
13
  def process
@@ -30,12 +30,15 @@ module OpenApi
30
30
  processed_range,
31
31
  processed_is_and_format(param_name),
32
32
  {
33
- pattern: _pattern&.inspect&.delete('/'),
34
- default: _default,
33
+ pattern: _pattern&.inspect&.delete('/'),
34
+ default: _default,
35
+ as: _as,
36
+ permit: _permit,
37
+ not_permit: _npermit,
35
38
  }
36
39
  then_merge!
37
40
 
38
- assign(processed_desc options).then_merge!
41
+ reduceee(processed_desc options).then_merge!
39
42
  end
40
43
  alias_method :process, :process_for
41
44
 
@@ -51,15 +54,17 @@ module OpenApi
51
54
  options[:desc_inside] ? { description: result } : nil
52
55
  end
53
56
 
57
+ # TODO: more info
58
+ # TODO: desc configure
54
59
  def process_desc
55
60
  if processed[:enum].present?
56
61
  if @enum_info.present?
57
62
  @enum_info.each_with_index do |(info, value), index|
58
- __desc.concat "#{index + 1}/ #{info}: #{value}<br/>"
63
+ __desc.concat "<br/>#{index + 1}/ #{info}: #{value}"
59
64
  end
60
65
  else
61
66
  processed[:enum].each_with_index do |value, index|
62
- __desc.concat "#{index + 1}/ #{value}<br/>"
67
+ __desc.concat "<br/>#{index + 1}/ #{value}"
63
68
  end
64
69
  end
65
70
  end
@@ -69,7 +74,7 @@ module OpenApi
69
74
  def processed_type(type = self.type)
70
75
  t = type.class.in?([Hash, Array, Symbol]) ? type : "#{type}".downcase
71
76
  if t.is_a? Hash
72
- # For support writing:
77
+ # For supporting writing:
73
78
  # form 'desc', data: {
74
79
  # id!: { type: Integer, enum: 0..5, desc: 'user id' }
75
80
  # }
@@ -87,7 +92,7 @@ module OpenApi
87
92
  elsif t.in? %w[binary base64]
88
93
  { type: 'string', format: t}
89
94
  elsif t.eql? 'file'
90
- { type: 'string', format: OpenApi.config.dft_file_format }
95
+ { type: 'string', format: Config.dft_file_format }
91
96
  elsif t.eql? 'datetime'
92
97
  { type: 'string', format: 'date-time' }
93
98
  else # other string
@@ -177,7 +182,7 @@ module OpenApi
177
182
  end
178
183
  def recognize_is_options_in(name)
179
184
  # identify whether `is` patterns matched the name, if so, generate `is`.
180
- OpenApi.config.is_options.each do |pattern|
185
+ Config.is_options.each do |pattern|
181
186
  self._is = pattern or break if name.match? /#{pattern}/
182
187
  end if _is.nil?
183
188
  self.delete :_is if _is.in?([:x, :we])
@@ -188,13 +193,16 @@ module OpenApi
188
193
  _enum: %i[ enum values allowable_values ],
189
194
  _value: %i[ must_be value allowable_value ],
190
195
  _range: %i[ range number_range ],
191
- _length: %i[ length lth ],
192
- _is: %i[ is_a is ], # NOT OAS Spec, just an addition
196
+ _length: %i[ length lth size ],
197
+ _is: %i[ is_a is ], # NOT OAS Spec, see documentation/parameter.md
193
198
  _format: %i[ format fmt ],
194
- _pattern: %i[ pattern regexp pr reg ],
199
+ _pattern: %i[ pattern regexp pt reg ],
195
200
  _default: %i[ default dft default_value ],
196
- _desc: %i[ desc description ],
197
- __desc: %i[ desc! description! ],
201
+ _desc: %i[ desc description d ],
202
+ __desc: %i[ desc! description! d! ],
203
+ _as: %i[ as to for map mapping ], # NOT OAS Spec, it's for zero-params_processor
204
+ _permit: %i[ permit pmt ], # NOT OAS Spec, it's for zero-params_processor
205
+ _npermit: %i[ npmt not_permit unpermit ], # NOT OAS Spec, it's for zero-params_processor
198
206
  }.each do |key, aliases|
199
207
  define_method key do
200
208
  aliases.each do |alias_name|
@@ -4,6 +4,5 @@ require "open_api/generator"
4
4
  require "open_api/dsl"
5
5
 
6
6
  module OpenApi
7
- include Config
8
7
  include Generator
9
8
  end
@@ -1,34 +1,100 @@
1
1
  module OpenApi
2
2
  module Config
3
- def self.included(base)
4
- base.extend ClassMethods
5
- end
6
-
7
- DEFAULT_CONFIG = {
8
- is_options: %w[email phone password uuid uri url time date],
9
- dft_file_format: 'binary'
10
- }.freeze
11
-
12
- module ClassMethods
13
- def config
14
- @config ||= ActiveSupport::InheritableOptions.new(DEFAULT_CONFIG)
15
- end
16
-
17
- def configure(&block)
18
- config.instance_eval &block
19
- end
20
-
21
- ### config options
22
- # register_docs = {
23
- # doc_name: {
24
- # :file_output_path, :root_controller
25
- # info: {}
26
- # }}
27
- # is_options = %w[]
28
-
29
- def apis
30
- @apis ||= @config.register_docs
31
- end
3
+ # [REQUIRED] The location where .json doc file will be output.
4
+ cattr_accessor :file_output_path do
5
+ 'public/open_api'
6
+ end
7
+
8
+ cattr_accessor :generate_doc do
9
+ true
10
+ end
11
+
12
+ # Everything about OAS3 is on https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md
13
+ # Getting started: https://swagger.io/docs/specification/basic-structure/
14
+ cattr_accessor :register_docs do
15
+ {
16
+ # [REQUIRED] At least one doc.
17
+ zero_rails: {
18
+ # [REQUIRED] ZRO will scan all the descendants of the root_controller, and then generate their docs.
19
+ root_controller: ApplicationController,
20
+
21
+ # [REQUIRED] Info Object: The info section contains API information
22
+ info: {
23
+ # [REQUIRED] The title of the application.
24
+ title: 'Zero Rails Apis',
25
+ # [REQUIRED] The version of the OpenAPI document
26
+ # (which is distinct from the OpenAPI Specification version or the API implementation version).
27
+ version: '0.0.1'
28
+ }
29
+ }
30
+ }
31
+ end
32
+
33
+ cattr_accessor :is_options do
34
+ %w[ email phone password uuid uri url time date ]
35
+ end
36
+
37
+ cattr_accessor :dft_file_format do
38
+ 'binary'
39
+ end
40
+
41
+ cattr_accessor :generate_jbuilder_file do
42
+ false
43
+ end
44
+
45
+ cattr_accessor :overwrite_jbuilder_file do
46
+ false
47
+ end
48
+
49
+ cattr_accessor :jbuilder_templates do
50
+ {
51
+ index: (
52
+ <<-FILE
53
+ json.partial! 'api/base', total: @data.count
54
+
55
+ json.data do
56
+ # @data = @data.page(@_page).per(@_rows) if @_page || @_rows
57
+ # json.array! @data do |datum|
58
+ json.array! @data.page(@_page).per(@_rows) do |datum|
59
+ json.(datum, *datum.show_attrs) if datum.present?
60
+ end
61
+ end
62
+ FILE
63
+ ),
64
+
65
+ show: (
66
+ <<-FILE
67
+ json.partial! 'api/base', total: 1
68
+
69
+ json.data do
70
+ json.array! [ @data ] do |datum|
71
+ json.(datum, *datum.show_attrs) if datum.present?
72
+ end
73
+ end
74
+ FILE
75
+ ),
76
+
77
+ success: (
78
+ <<-FILE
79
+ json.partial! 'api/success'
80
+ FILE
81
+ ),
82
+
83
+ success_or_not: (
84
+ <<-FILE
85
+ unless @status
86
+ # @_code, @_msg = @error_info.present? ? @error_info : ApiError.action_failed.info
87
+ end
88
+
89
+ json.partial! 'api/base', total: 0
90
+ json.data ''
91
+ FILE
92
+ ),
93
+ }
94
+ end
95
+
96
+ def self.docs
97
+ register_docs
32
98
  end
33
99
  end
34
100
  end
@@ -24,33 +24,40 @@ module OpenApi
24
24
  current_ctrl.instance_eval &block if block_given?
25
25
  end
26
26
 
27
- def open_api method, summary = '', &block
27
+ def open_api method, summary = '', options = { }, &block
28
28
  apis_set if @_ctrl_infos.nil?
29
29
 
30
30
  # select the routing info (corresponding to the current method) from the routing list.
31
31
  action_path = "#{@_ctrl_path ||= controller_path}##{method}"
32
32
  routes_info = ctrl_routes_list&.select { |api| api[:action_path].match? /^#{action_path}$/ }&.first
33
- puts "[zero-rails_openapi] Routing mapping failed: #{@_ctrl_path}##{method}" or return if routes_info.nil?
33
+ pp "[ZRO Warnning] Routing mapping failed: #{@_ctrl_path}##{method}" and return if routes_info.nil?
34
34
 
35
- # structural { path: { http_method:{ } } }, for Paths Object.
35
+ # structural { #path: { #http_method:{ } } }, for pushing into Paths Object.
36
36
  path = (@_api_infos ||= { })[routes_info[:path]] ||= { }
37
37
  current_api = path[routes_info[:http_verb]] =
38
- ApiInfoObj.new(action_path)
38
+ ApiInfoObj.new(action_path, options.slice(:skip, :use))
39
39
  .merge! description: '', summary: summary, operationId: method, tags: [@_apis_tag],
40
- parameters: [ ], requestBody: '', responses: { },
41
- security: [ ], servers: [ ]
40
+ parameters: [ ], requestBody: '', responses: { }, security: [ ], servers: [ ]
42
41
 
43
- current_api.tap do |it|
42
+ if (builder = options.values_at(:builder, :bd, :jbuilder).compact.first).present?
43
+ Generator
44
+ .generate_builder_file path: action_path.split('#').first,
45
+ action: action_path.split('#').last,
46
+ builder: builder
47
+ end
48
+
49
+ current_api.tap do |api|
44
50
  [method, :all].each do |key| # blocks_store_key
45
- @_apis_blocks&.[](key)&.each { |blk| it.instance_eval &blk }
51
+ @_apis_blocks&.[](key)&.each { |blk| api.instance_eval &blk }
46
52
  end
47
- it.instance_eval &block if block_given?
48
- it.instance_eval { process_params }
49
- it.delete_if { |_, v| v.blank? }
53
+ api.param_use = nil
54
+ api.instance_eval &block if block_given?
55
+ api.instance_eval { process_params }
56
+ api.delete_if { |_, v| v.blank? }
50
57
  end
51
58
  end
52
59
 
53
- # For DRY; method could be symbol array
60
+ # method could be symbol array, like: %i[ .. ]
54
61
  def api_dry method = :all, desc = '', &block
55
62
  @_apis_blocks ||= { }
56
63
  if method.is_a? Array
@@ -3,6 +3,7 @@ require 'oas_objs/param_obj'
3
3
  require 'oas_objs/response_obj'
4
4
  require 'oas_objs/request_body_obj'
5
5
  require 'oas_objs/ref_obj'
6
+ require 'open_api/helpers'
6
7
 
7
8
  module OpenApi
8
9
  module DSL
@@ -100,10 +101,13 @@ module OpenApi
100
101
 
101
102
  class ApiInfoObj < Hash
102
103
  include DSL::CommonDSL
104
+ include DSL::Helpers
103
105
 
104
- attr_accessor :action_path
105
- def initialize(action_path)
106
+ attr_accessor :action_path, :param_skip, :param_use
107
+ def initialize(action_path, options = { })
106
108
  self.action_path = action_path
109
+ self.param_skip = options[:skip] || [ ]
110
+ self.param_use = options[:use] || [ ]
107
111
  end
108
112
 
109
113
  def this_api_is_invalid! explain = ''
@@ -131,6 +135,9 @@ module OpenApi
131
135
  end
132
136
 
133
137
  def param param_type, name, type, required, schema_hash = { }
138
+ return if param_skip.include? name
139
+ return unless param_use.include? name if param_use.present?
140
+
134
141
  if @inputs_descs&.[](name).present?
135
142
  schema_hash[:desc] = @inputs_descs[name]
136
143
  elsif @inputs_descs&.[]("#{name}!".to_sym).present?
@@ -139,7 +146,9 @@ module OpenApi
139
146
 
140
147
  param_obj = ParamObj.new(name, param_type, type, required, schema_hash)
141
148
  # The definition of the same name parameter will be overwritten
142
- index = self[:parameters].map { |p_obj| p_obj.processed[:name] }.index name
149
+ index = self[:parameters].map do |p|
150
+ p.processed[:name] if p.is_a? ParamObj
151
+ end.index name
143
152
  if index.present?
144
153
  self[:parameters][index] = param_obj
145
154
  else
@@ -148,8 +157,8 @@ module OpenApi
148
157
  end
149
158
 
150
159
  def process_params
151
- self[:parameters].each_with_index do |param_obj, index|
152
- self[:parameters][index] = param_obj.process
160
+ self[:parameters].each_with_index do |p, index|
161
+ self[:parameters][index] = p.is_a?(ParamObj) ? p.process : p
153
162
  end
154
163
  end
155
164