zero-rails_openapi 1.3.2 → 1.3.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -23,9 +23,9 @@ class Api::V1::ExamplesController < Api::V1::BaseController
23
23
  desc 'Optional multiline or single-line Markdown-formatted description',
24
24
  id: 'user id',
25
25
  email_addr: 'email_addr\'s desc'
26
- email = 'git@github.com'
26
+ email = 'zero@rails.org'
27
27
 
28
- query! :id, Integer, enum: 0..5, length: [1, 2], pattern: /^[0-9]$/, range: {gt:0, le:5}
28
+ query! :id, Integer, enum: 0..5, length: [1, 2], pattern: /^[0-9]$/, range: { gt:0, le:5 }
29
29
  query! :done, Boolean, must_be: false, default: true, desc: 'must be false'
30
30
  query :email_addr, String, lth: :ge_3, default: email # is_a: :email
31
31
 
@@ -1,12 +1,13 @@
1
1
  class V2::GoodsDoc < BaseDoc
2
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
3
+ open_api :index, 'GET list of Goods.', builder: :index, # jbuilder templates is set in initializers/open_api.rb
4
+ use: [ 'Token', :page, :rows ] do # use parameters write in AutoGenDoc#api_dry
5
+ # skip: [ 'Token' ] do # you can also skip parameters
6
6
  desc 'listing Goods',
7
7
  view!: 'search view, allows::<br/>',
8
8
  search_type!: 'search field, allows:<br/>'
9
9
 
10
+ # Single `query`
10
11
  query :view, String, enum: {
11
12
  'all goods (default)': :all,
12
13
  'only online': :online,
@@ -14,22 +15,23 @@ class V2::GoodsDoc < BaseDoc
14
15
  'expensive goods': :expensive,
15
16
  'cheap goods': :cheap,
16
17
  }
17
- # query :search_type, String, enum: %w[name creator category price]
18
+ # Batch `query`
18
19
  do_query by: {
19
20
  :search_type => { type: String, enum: %w[ name creator category price ] },
20
- :export => { type: Boolean, desc: 'export as Excel format', examples: {
21
- :right_input => true,
22
- :wrong_input => 'wrong input'
23
- }}
21
+ :value => String,
22
+ :export => { type: Boolean, desc: 'export as Excel format', examples: {
23
+ :right_input => true,
24
+ :wrong_input => 'wrong input'
25
+ }}
24
26
  }
25
27
  end
26
28
 
27
29
 
28
- open_api :create, 'Create a Good', builder: :success_or_not, use: 'Token' do
30
+ open_api :create, 'POST create a Good', builder: :success_or_not, use: 'Token' do
29
31
  form! 'for creating a good', data: {
30
32
  :name! => { type: String, desc: 'good\'s name' },
31
33
  :category_id! => { type: Integer, desc: 'sub_category\'s id', npmt: true, range: { ge: 1 }, as: :cate },
32
- :price! => { type: Float, desc: 'good\'s price', range: { ge: 0} },
34
+ :price! => { type: Float, desc: 'good\'s price', range: { ge: 0 } },
33
35
  # -- optional
34
36
  :is_online => { type: Boolean, desc: 'it\'s online?' },
35
37
  :remarks => { type: String, desc: 'remarks' },
@@ -43,8 +45,8 @@ class V2::GoodsDoc < BaseDoc
43
45
  end
44
46
 
45
47
 
46
- open_api :show, 'Show a Good.', builder: :show, use: [ 'Token', :id ]
48
+ open_api :show, 'GET a Good.', builder: :show, use: [ 'Token', :id ]
47
49
 
48
50
 
49
- open_api :destroy, 'Delete a Good.', builder: :success_or_not, use: [ 'Token', :id ]
51
+ open_api :destroy, 'DELETE a Good.', builder: :success_or_not, use: [ 'Token', :id ]
50
52
  end
@@ -1,6 +1,16 @@
1
1
  require 'open_api'
2
2
 
3
3
  OpenApi::Config.tap do |c|
4
+ # Config DSL
5
+ c.instance_eval do
6
+ api :zero_rails_api, root_controller: ApiDoc
7
+ info version: '0.0.1', title: 'Zero Rails APIs', description: 'API documentation of Zero-Rails Application.'
8
+ server 'http://localhost:3000', desc: 'Main (production) server'
9
+ server 'http://localhost:3000', desc: 'Internal staging server for testing'
10
+ security ApiKeyAuth: [ ]
11
+ security_scheme :ApiKeyAuth, type: 'apiKey', name: 'server_token', in: 'query'
12
+ end
13
+
4
14
  # [REQUIRED] The location where .json doc file will be output.
5
15
  c.file_output_path = 'public/open_api'
6
16
 
@@ -88,7 +98,7 @@ OpenApi::Config.tap do |c|
88
98
  c.jbuilder_templates = {
89
99
  index: (
90
100
  <<~FILE
91
- # *** Generated by ZRO ***
101
+ # *** Generated by ZRO [ please make sure that you have checked this file ] ***
92
102
  json.partial! 'api/base', total: @data.size
93
103
 
94
104
  json.data do
@@ -103,7 +113,7 @@ OpenApi::Config.tap do |c|
103
113
 
104
114
  show: (
105
115
  <<~FILE
106
- # *** Generated by ZRO ***
116
+ # *** Generated by ZRO [ please make sure that you have checked this file ] ***
107
117
  json.partial! 'api/base', total: 1
108
118
 
109
119
  json.data do
@@ -116,14 +126,14 @@ OpenApi::Config.tap do |c|
116
126
 
117
127
  success: (
118
128
  <<~FILE
119
- # *** Generated by ZRO ***
129
+ # *** Generated by ZRO [ please make sure that you have checked this file ] ***
120
130
  json.partial! 'api/success'
121
131
  FILE
122
132
  ),
123
133
 
124
134
  success_or_not: (
125
135
  <<~FILE
126
- # *** Generated by ZRO ***
136
+ # *** Generated by ZRO [ please make sure that you have checked this file ] ***
127
137
  unless @status
128
138
  # @_code, @_msg = @error_info.present? ? @error_info : ApiError.action_failed.info
129
139
  end
@@ -1,21 +1,26 @@
1
- ### More Explanation of Param()
1
+ ### More Explanation for `param` and `schema_hash`
2
2
 
3
- #### param_type
3
+ #### param_type (param_location)
4
4
  OpenAPI 3.0 distinguishes between the following parameter types based on the parameter location:
5
5
  **header, path, query, cookie**. [more](https://swagger.io/docs/specification/describing-parameters/)
6
6
 
7
- #### name
8
- parameter name. If param_type is :path, it must correspond to the associated path segment form
9
- the routing path, for example: the path is `/good/:id`, then you have to declare a path parameter with name `id`.
7
+ #### name (param_name)
8
+ The name of parameter. It can be Symbol or String.
9
+
10
+ If param_type is :path, it must correspond to the associated path segment form
11
+ the routing path, for example: if the API path is `/good/:id`, you have to declare a path parameter with name `id` to it.
12
+
13
+ #### type (schema_type)
14
+ Parameter's (schema) type. We call it `schema_type` because it is inside SchemaObj.
15
+
16
+ Support all [data types](https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md#dataTypes) defined in OAS.
10
17
 
11
- #### type
12
- parameter (schema) type. Support all [data types](https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md#dataTypes) defined in OAS.
13
18
  In addition, you can use `format` in schema_hash to define in fine detail the data type being used, like:
14
19
  int32, float, date ...
15
- All the types you can use are:
20
+ All the types you can use as following:
16
21
  - **String, 'binary', 'base64'**
17
22
  - **Integer, Long, 'int32', 'int64', Float, Double**
18
- - **File** (it will be converted as `{ type: 'string', format: Config.dft_file_format }`)
23
+ - **File** (it will be converted to `{ type: 'string', format: Config.dft_file_format }`)
19
24
  - **Date, DateTime**
20
25
  - **Boolean**
21
26
  - **Array**: `Array[String]` or `[String]`
@@ -24,8 +29,8 @@ All the types you can use are:
24
29
  (`!` bang key means it is required).
25
30
  - Nested Object: `{ id!: Integer, name: { first: String, last: String } }`
26
31
  - Nested Array and Object: `[[{ id!: Integer, name: { first: String, last: String } }]]`
27
- - **:ComponentKey**: the Symbol value pass to type will generate a Schema Reference Object link
28
- to the component correspond to ComponentKey.
32
+ - **:ComponentKey**: pass **Symbol** value to type will generate a Schema Reference Object link
33
+ to the component correspond to ComponentKey, like: :IdPath, :NameQuery
29
34
 
30
35
  You can use `Object.const_set()` to define a constant that does not exist, but note that
31
36
  the value you set could not be a Symbol (it will be explained as a Ref Object), should be a String.
@@ -59,4 +64,4 @@ You can set the schema by following keys (all are optional), the words in parent
59
64
  5. If type is Object, for describing each property's schema, the only way is use ref type, like: `{ id: :Id, name: :Name }`
60
65
  - **pattern (regexp, pr, reg)**
61
66
  - **default (dft, default_value)**
62
- - **as** # TODO
67
+ - **as** # TODO
@@ -33,7 +33,7 @@ module OpenApi
33
33
  processed_is_and_format(param_name),
34
34
  {
35
35
  pattern: _pattern&.inspect&.delete('/'),
36
- default: '_default',
36
+ default: _default.nil? ? nil : '_default',
37
37
  examples: self[:examples].present? ? ExampleObj.new(self[:examples], self[:exp_by]).process : nil,
38
38
  },
39
39
  { as: _as, permit: _permit, not_permit: _npermit, req_if: _req_if, opt_if: _opt_if }
@@ -18,20 +18,20 @@ module OpenApi
18
18
  # Getting started: https://swagger.io/docs/specification/basic-structure/
19
19
  cattr_accessor :register_docs do
20
20
  {
21
- # [REQUIRED] At least one doc.
22
- zero_rails: {
23
- # [REQUIRED] ZRO will scan all the descendants of the root_controller, and then generate their docs.
24
- root_controller: ApplicationController,
25
-
26
- # [REQUIRED] Info Object: The info section contains API information
27
- info: {
28
- # [REQUIRED] The title of the application.
29
- title: 'Zero Rails Apis',
30
- # [REQUIRED] The version of the OpenAPI document
31
- # (which is distinct from the OpenAPI Specification version or the API implementation version).
32
- version: '0.0.1'
33
- }
34
- }
21
+ # # [REQUIRED] At least one doc.
22
+ # zero_rails_api: {
23
+ # # [REQUIRED] ZRO will scan all the descendants of the root_controller, and then generate their docs.
24
+ # root_controller: ApplicationController,
25
+ #
26
+ # # [REQUIRED] Info Object: The info section contains API information
27
+ # info: {
28
+ # # [REQUIRED] The title of the application.
29
+ # title: 'Zero Rails Apis',
30
+ # # [REQUIRED] The version of the OpenAPI document
31
+ # # (which is distinct from the OpenAPI Specification version or the API implementation version).
32
+ # version: '0.0.1'
33
+ # }
34
+ # }
35
35
  }
36
36
  end
37
37
 
@@ -55,7 +55,7 @@ module OpenApi
55
55
  {
56
56
  index: (
57
57
  <<~FILE
58
- # *** Generated by ZRO ***
58
+ # *** Generated by ZRO [ please make sure that you have checked this file ] ***
59
59
  json.partial! 'api/base', total: @data.size
60
60
 
61
61
  json.data do
@@ -70,7 +70,7 @@ module OpenApi
70
70
 
71
71
  show: (
72
72
  <<~FILE
73
- # *** Generated by ZRO ***
73
+ # *** Generated by ZRO [ please make sure that you have checked this file ] ***
74
74
  json.partial! 'api/base', total: 1
75
75
 
76
76
  json.data do
@@ -83,14 +83,14 @@ module OpenApi
83
83
 
84
84
  success: (
85
85
  <<~FILE
86
- # *** Generated by ZRO ***
86
+ # *** Generated by ZRO [ please make sure that you have checked this file ] ***
87
87
  json.partial! 'api/success'
88
88
  FILE
89
89
  ),
90
90
 
91
91
  success_or_not: (
92
92
  <<~FILE
93
- # *** Generated by ZRO ***
93
+ # *** Generated by ZRO [ please make sure that you have checked this file ] ***
94
94
  unless @status
95
95
  # @_code, @_msg = @error_info.present? ? @error_info : ApiError.action_failed.info
96
96
  end
@@ -4,8 +4,27 @@ module OpenApi
4
4
  base.class_eval do
5
5
  module_function
6
6
 
7
- def info
8
- 1
7
+ def api name, root_controller:
8
+ @api = name
9
+ register_docs[name] = { root_controller: root_controller }
10
+ end
11
+
12
+ def info version:, title:, **addition
13
+ register_docs[@api].merge! version: version, title: title, **addition
14
+ end
15
+
16
+ def server url, desc: ''
17
+ (register_docs[@api][:servers] ||= [ ]) << { url: url, description: desc }
18
+ end
19
+
20
+ def security requirement
21
+ (register_docs[@api][:global_security] ||= [ ]) << requirement
22
+ end
23
+
24
+ alias_method :security_require, :security
25
+
26
+ def security_scheme scheme_name, schema# = { }
27
+ (register_docs[@api][:global_security_schemes] ||= { }).merge! scheme_name => schema
9
28
  end
10
29
  end
11
30
  end
data/lib/open_api/dsl.rb CHANGED
@@ -26,28 +26,28 @@ module OpenApi
26
26
  def components &block
27
27
  apis_tag if @_ctrl_infos.nil?
28
28
  current_ctrl = @_ctrl_infos[:components] = CtrlInfoObj.new
29
- current_ctrl.instance_eval &block
29
+ current_ctrl.instance_eval(&block)
30
30
  current_ctrl._process_objs
31
31
  end
32
32
 
33
- def open_api method, summary = '', builder: nil, skip: [ ], use: [ ], &block
33
+ def open_api action, summary = '', builder: nil, skip: [ ], use: [ ], &block
34
34
  apis_tag if @_ctrl_infos.nil?
35
35
 
36
- # select the routing info (corresponding to the current method) from the routing list.
37
- action_path = "#{@_ctrl_path ||= controller_path}##{method}"
36
+ # select the routing info (corresponding to the current method) from routing list.
37
+ action_path = "#{@_ctrl_path ||= controller_path}##{action}"
38
38
  routes_info = ctrl_routes_list&.select { |api| api[:action_path].match? /^#{action_path}$/ }&.first
39
- pp "[ZRO Warning] Routing mapping failed: #{@_ctrl_path}##{method}" and return if routes_info.nil?
39
+ pp "[ZRO Warning] Routing mapping failed: #{@_ctrl_path}##{action}" and return if routes_info.nil?
40
40
  Generator.generate_builder_file(action_path, builder) if builder.present?
41
41
 
42
42
  # structural { #path: { #http_method:{ } } }, for pushing into Paths Object.
43
43
  path = (@_api_infos ||= { })[routes_info[:path]] ||= { }
44
44
  current_api = path[routes_info[:http_verb]] =
45
45
  ApiInfoObj.new(action_path, skip: Array(skip), use: Array(use))
46
- .merge! description: '', summary: summary, operationId: method, tags: [@_apis_tag],
46
+ .merge! description: '', summary: summary, operationId: action, tags: [@_apis_tag],
47
47
  parameters: [ ], requestBody: '', responses: { }, security: [ ], servers: [ ]
48
48
 
49
49
  current_api.tap do |api|
50
- [method, :all].each do |key| # blocks_store_key
50
+ [action, :all].each do |key| # blocks_store_key
51
51
  @_apis_blocks&.[](key)&.each { |blk| api.instance_eval(&blk) }
52
52
  end
53
53
  api.param_use = [ ] # skip 和 use 是对 dry 块而言的
@@ -58,18 +58,17 @@ module OpenApi
58
58
  end
59
59
 
60
60
  # method could be symbol array, like: %i[ .. ]
61
- def api_dry method = :all, desc = '', &block
61
+ def api_dry action = :all, desc = '', &block
62
62
  @_apis_blocks ||= { }
63
- if method.is_a? Array
64
- method.each { |m| (@_apis_blocks[m.to_sym] ||= [ ]) << block }
63
+ if action.is_a? Array
64
+ action.each { |m| (@_apis_blocks[m.to_sym] ||= [ ]) << block }
65
65
  else
66
- (@_apis_blocks[method.to_sym] ||= [ ]) << block
66
+ (@_apis_blocks[action.to_sym] ||= [ ]) << block
67
67
  end
68
68
  end
69
69
 
70
70
  def ctrl_routes_list
71
- @routes_list ||= Generator.generate_routes_list
72
- @routes_list[@_ctrl_path]
71
+ Generator.routes_list[@_ctrl_path]
73
72
  end
74
73
  end
75
74
  end
@@ -6,7 +6,7 @@ module OpenApi
6
6
  include DSL::CommonDSL
7
7
  include DSL::Helpers
8
8
 
9
- attr_accessor :action_path, :param_skip, :param_use, :param_descs
9
+ attr_accessor :action_path, :param_skip, :param_use, :param_descs, :param_order
10
10
 
11
11
  def initialize(action_path, skip: [ ], use: [ ])
12
12
  self.action_path = action_path
@@ -50,7 +50,7 @@ module OpenApi
50
50
  %i[header header! path path! query query! cookie cookie!].each do |param_type|
51
51
  define_method "do_#{param_type}" do |by:|
52
52
  by.each do |key, value|
53
- args = [ key.dup.to_s.delete('!'), value.delete(:type), value ]
53
+ args = [ key.dup.to_s.delete('!').to_sym, value.delete(:type), value ]
54
54
  key.to_s['!'] ? send("#{param_type}!", *args) : send(param_type, *args)
55
55
  end
56
56
  end unless param_type.to_s['!']
@@ -113,12 +113,37 @@ module OpenApi
113
113
  self[:servers] << { url: url, description: desc }
114
114
  end
115
115
 
116
+ def order *param_names
117
+ self.param_order = param_names
118
+ end
119
+
120
+ def param_examples exp_by = :all, examples_hash
121
+ _process_objs
122
+ exp_by = self[:parameters].map { |p| p[:name] } if exp_by == :all
123
+ # TODO: ref obj
124
+ # exp_in_params = self[:parameters].map { |p| p[:schema][:examples] }.compact
125
+ # examples_hash.map! do |key, value|
126
+ # if value == []
127
+ # if key.in?(exp_in_params.map { |e| e.keys }.flatten.uniq)
128
+ # # TODO
129
+ # end
130
+ # end
131
+ # end
132
+ self[:examples] = ExampleObj.new(examples_hash, exp_by).process
133
+ end
134
+ alias_method :examples, :param_examples
135
+
116
136
 
117
137
  def _process_objs
118
138
  self[:parameters]&.each_with_index do |p, index|
119
139
  self[:parameters][index] = p.process if p.is_a?(ParamObj)
120
140
  end
121
141
 
142
+ # Parameters sorting
143
+ self[:parameters].clone.each do |p|
144
+ self[:parameters][param_order.index(p[:name])] = p
145
+ end if param_order.present?
146
+
122
147
  self[:responses]&.each do |code, obj|
123
148
  self[:responses][code] = obj.process if obj.is_a?(ResponseObj)
124
149
  end
@@ -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 'oas_objs/example_obj'
6
7
  require 'open_api/dsl/helpers'
7
8
 
8
9
  module OpenApi
@@ -6,7 +6,7 @@ module OpenApi
6
6
  include DSL::CommonDSL
7
7
  include DSL::Helpers
8
8
 
9
- def schema component_key, type, schema_hash = { }
9
+ def schema component_key, type, schema_hash# = { }
10
10
  (self[:schemas] ||= { })[component_key] = SchemaObj.new(type, schema_hash).process
11
11
  end
12
12
  arrow_enable :schema
@@ -11,9 +11,9 @@ module OpenApi
11
11
  # (2) config in model: https://github.com/zhandao/zero-rails/tree/master/app/models/good.rb
12
12
  # (3) jbuilder file: https://github.com/zhandao/zero-rails/blob/mster/app/views/api/v1/goods/index.json.jbuilder
13
13
  # in a word, BuilderSupport let you control the `output fields and nested association infos` very easily.
14
- if model&.respond_to? :show_attrs
14
+ if model.respond_to? :show_attrs
15
15
  columns = model.columns.map(&:name).map(&:to_sym)
16
- model&.show_attrs&.map do |attr|
16
+ model.show_attrs.map do |attr|
17
17
  if columns.include? attr
18
18
  index = columns.index attr
19
19
  type = model.columns[index].sql_type_metadata.type.to_s.camelize
@@ -26,13 +26,13 @@ module OpenApi
26
26
  end rescue next
27
27
  end
28
28
  else
29
- model&.columns&.map do |column|
29
+ model.columns.map do |column|
30
30
  name = column.name.to_sym
31
31
  type = column.sql_type_metadata.type.to_s.camelize
32
32
  type = 'DateTime' if type == 'Datetime'
33
33
  { name => Object.const_get(type) }
34
34
  end
35
- end&.compact&.reduce({ }, :merge)
35
+ end.compact.reduce({ }, :merge) rescue ''
36
36
  end
37
37
 
38
38
  # Arrow Writing: