zero-rails_openapi 1.2.0 → 1.3.0

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