meta-api 0.0.2 → 0.0.3

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.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -4
  3. data/CHANGELOG.md +6 -1
  4. data/Gemfile.lock +1 -1
  5. data/README.md +9 -7
  6. data/docs/Rails.md +61 -0
  7. data/docs//345/246/202/344/275/225/350/264/241/347/214/256.md +10 -0
  8. data/docs//346/225/231/347/250/213.md +21 -21
  9. data/examples/rails_app/.gitattributes +5 -0
  10. data/examples/rails_app/.gitignore +23 -0
  11. data/examples/rails_app/.rspec +1 -0
  12. data/examples/rails_app/.ruby-version +1 -0
  13. data/examples/rails_app/Gemfile +29 -0
  14. data/examples/rails_app/Gemfile.lock +190 -0
  15. data/examples/rails_app/README.md +11 -0
  16. data/examples/rails_app/Rakefile +6 -0
  17. data/examples/rails_app/app/controllers/application_controller.rb +7 -0
  18. data/examples/rails_app/app/controllers/concerns/.keep +0 -0
  19. data/examples/rails_app/app/controllers/data_controller.rb +63 -0
  20. data/examples/rails_app/app/controllers/swagger_controller.rb +13 -0
  21. data/examples/rails_app/app/models/concerns/.keep +0 -0
  22. data/examples/rails_app/bin/rails +4 -0
  23. data/examples/rails_app/bin/rake +4 -0
  24. data/examples/rails_app/bin/setup +25 -0
  25. data/examples/rails_app/config/application.rb +39 -0
  26. data/examples/rails_app/config/boot.rb +3 -0
  27. data/examples/rails_app/config/credentials.yml.enc +1 -0
  28. data/examples/rails_app/config/environment.rb +5 -0
  29. data/examples/rails_app/config/environments/development.rb +51 -0
  30. data/examples/rails_app/config/environments/production.rb +65 -0
  31. data/examples/rails_app/config/environments/test.rb +50 -0
  32. data/examples/rails_app/config/initializers/cors.rb +16 -0
  33. data/examples/rails_app/config/initializers/filter_parameter_logging.rb +8 -0
  34. data/examples/rails_app/config/initializers/inflections.rb +16 -0
  35. data/examples/rails_app/config/initializers/meta_rails_plugin.rb +3 -0
  36. data/examples/rails_app/config/locales/en.yml +33 -0
  37. data/examples/rails_app/config/puma.rb +43 -0
  38. data/examples/rails_app/config/routes.rb +13 -0
  39. data/examples/rails_app/config.ru +6 -0
  40. data/examples/rails_app/lib/tasks/.keep +0 -0
  41. data/examples/rails_app/log/.keep +0 -0
  42. data/examples/rails_app/public/robots.txt +1 -0
  43. data/examples/rails_app/spec/data_controller_spec.rb +60 -0
  44. data/examples/rails_app/spec/rails_helper.rb +55 -0
  45. data/examples/rails_app/spec/spec_helper.rb +94 -0
  46. data/examples/rails_app/spec/swagger_controller_spec.rb +13 -0
  47. data/examples/rails_app/tmp/.keep +0 -0
  48. data/examples/rails_app/tmp/pids/.keep +0 -0
  49. data/lib/meta/application/execution.rb +3 -11
  50. data/lib/meta/application/{meta.rb → metadata.rb} +5 -15
  51. data/lib/meta/application/parameters.rb +47 -0
  52. data/lib/meta/application/route.rb +2 -2
  53. data/lib/meta/json_schema/builders/array_schema_builder.rb +11 -10
  54. data/lib/meta/json_schema/builders/dynamic_schema_builder.rb +23 -0
  55. data/lib/meta/json_schema/builders/object_schema_builder.rb +4 -18
  56. data/lib/meta/json_schema/builders/ref_schema_builder.rb +19 -0
  57. data/lib/meta/json_schema/builders/schema_builder_tool.rb +26 -1
  58. data/lib/meta/json_schema/schemas/array_schema.rb +3 -3
  59. data/lib/meta/json_schema/schemas/base_schema.rb +29 -69
  60. data/lib/meta/json_schema/schemas/dynamic_schema.rb +29 -0
  61. data/lib/meta/json_schema/schemas/object_schema.rb +27 -113
  62. data/lib/meta/json_schema/schemas/properties.rb +157 -0
  63. data/lib/meta/json_schema/schemas/ref_schema.rb +35 -0
  64. data/lib/meta/json_schema/schemas.rb +0 -2
  65. data/lib/meta/json_schema/support/schema_options.rb +21 -12
  66. data/lib/meta/json_schema.rb +2 -0
  67. data/lib/meta/rails.rb +98 -0
  68. data/lib/meta/route_dsl/parameters_builder.rb +2 -1
  69. data/lib/meta/swagger_doc.rb +5 -2
  70. data/lib/meta/utils/kwargs/builder.rb +115 -0
  71. data/lib/meta/utils/{kwargs.rb → kwargs/check.rb} +22 -22
  72. data/meta-api.gemspec +1 -1
  73. metadata +55 -4
@@ -1,18 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../../utils/kwargs'
3
+ require_relative '../../utils/kwargs/check'
4
4
 
5
5
  module Meta
6
6
  module JsonSchema
7
7
  class ObjectSchema < BaseSchema
8
- attr_reader :properties, :object_validations, :locked_options
8
+ attr_reader :properties, :locked_options
9
9
 
10
- def initialize(properties: {}, object_validations: {}, options: {}, locked_options: {}, schema_name_resolver: proc { nil })
10
+ USER_OPTIONS_CHECKER = Utils::KeywordArgs::Builder.build do
11
+ permit_extras true
12
+
13
+ key :scope, normalizer: ->(value) {
14
+ raise ArgumentError, 'scope 选项不可传递 nil' if value.nil?
15
+ value = [value] unless value.is_a?(Array)
16
+ value
17
+ }
18
+ end
19
+
20
+ def initialize(properties: nil, options: {}, locked_options: {}, schema_name_resolver: proc { nil })
11
21
  super(options)
12
22
 
13
- @properties = properties || {}
14
- @object_validations = object_validations || {}
15
- @locked_options = locked_options || {}
23
+ @properties = properties || Properties.new({}) # property 包含 stage,stage 包含 scope、schema
24
+ @properties = Properties.new(@properties) if @properties.is_a?(Hash)
25
+ @locked_options = USER_OPTIONS_CHECKER.check(locked_options || {})
16
26
  @schema_name_resolver = schema_name_resolver || proc { nil }
17
27
  end
18
28
 
@@ -20,7 +30,6 @@ module Meta
20
30
  def dup(options)
21
31
  self.class.new(
22
32
  properties: properties,
23
- object_validations: object_validations,
24
33
  options: options,
25
34
  locked_options: locked_options,
26
35
  schema_name_resolver: @schema_name_resolver
@@ -30,52 +39,12 @@ module Meta
30
39
  def filter(object_value, user_options = {})
31
40
  # 合并 user_options
32
41
  user_options = user_options.merge(locked_options) if locked_options
42
+ user_options = USER_OPTIONS_CHECKER.check(user_options)
33
43
 
34
44
  object_value = super(object_value, user_options)
35
- return nil if object_value.nil?
36
45
 
37
- # 第一步,根据 user_options[:scope] 需要过滤一些字段
38
- # user_options[:scope] 应是一个数组
39
- user_scope = user_options[:scope] || []
40
- user_scope = [user_scope] unless user_scope.is_a?(Array)
41
- stage = user_options[:stage]
42
- exclude = user_options.delete(:exclude) # 这里删除 exclude 选项
43
- filtered_properties = @properties.filter do |name, property_schema|
44
- # 通过 discard_missing 过滤
45
- next false if user_options[:discard_missing] && !object_value.key?(name.to_s)
46
-
47
- # 通过 stage 过滤。
48
- property_schema_options = property_schema.options(stage)
49
- next false unless property_schema_options
50
-
51
- # 通过 locked_exclude 选项过滤
52
- next false if exclude && exclude.include?(name)
53
-
54
- # 通过 scope 过滤
55
- scope_option = property_schema_options[:scope]
56
- next true if scope_option.empty?
57
- next false if user_scope.empty?
58
- (user_scope - scope_option).empty? # user_scope 应被消耗殆尽
59
- end
60
-
61
- # 第二步,递归过滤每一个属性
62
- object = {}
63
- errors = {}
64
- filtered_properties.each do |name, property_schema|
65
- value = resolve_property_value(object_value, name, property_schema, stage)
66
-
67
- begin
68
- object[name] = property_schema.filter(value, **user_options, object_value: object_value)
69
- rescue JsonSchema::ValidationErrors => e
70
- errors.merge! e.prepend_root(name).errors
71
- end
72
- end.to_h
73
-
74
- if errors.empty?
75
- object
76
- else
77
- raise JsonSchema::ValidationErrors.new(errors)
78
- end
46
+ return nil if object_value.nil?
47
+ @properties.filter(object_value, user_options)
79
48
  end
80
49
 
81
50
  # 合并其他的属性,并返回一个新的 ObjectSchema
@@ -83,61 +52,18 @@ module Meta
83
52
  ObjectSchema.new(properties: self.properties.merge(properties))
84
53
  end
85
54
 
86
- # 生成 Swagger 文档的 schema 格式。
87
- #
88
- # 选项:
89
- # - stage: 传递 :param 或 :render
90
- # - schemas: 用于保存已经生成的 Schema
91
- # - to_ref: 是否生成 $ref 格式,默认为“是”
92
- #
93
- # 提示:
94
- # > 每个 ObjectSchema 拥有一个 @schema_name_resolver 实例变量,如果由它解析出来的名称不为 nil,
95
- # > 则该 Schema 生成文档时会使用 $ref 格式。除非 to_ref 选项设置为 false.
96
- #
97
- def to_schema_doc(user_options)
98
- Utils::KeywordArgs.check(
99
- args: user_options,
100
- schema: { stage: nil, scope: nil, to_ref: false, schemas: nil }
101
- )
55
+ def resolve_name(stage)
56
+ locked_scopes = (locked_options || {})[:scope] || []
57
+ @schema_name_resolver.call(stage, locked_scopes)
58
+ end
102
59
 
103
- stage = user_options[:stage]
60
+ def to_schema_doc(stage: nil, **user_options)
104
61
  locked_scopes = (locked_options || {})[:scope] || []
105
- locked_scopes = [locked_scopes] unless locked_scope.nil? && locked_scopes.is_a?(Array)
106
- schema_name = @schema_name_resolver.call(stage, locked_scopes)
107
- if schema_name && user_options[:to_ref] != false
108
- # 首先将 Schema 写进 schemas 选项中去
109
- schemas = user_options[:schemas]
110
- unless schemas.key?(schema_name)
111
- schemas[schema_name] = nil # 首先设置 schemas 防止出现无限循环
112
- schemas[schema_name] = to_schema_doc(**user_options, to_ref: false) # 原地修改 schemas,无妨
113
- end
114
-
115
- return { '$ref': "#/components/schemas/#{schema_name}" }
116
- end
117
-
118
- stage_options = options(stage)
119
- properties = @properties.filter do |name, property_schema|
120
- # 根据 stage 过滤
121
- next false if stage.nil?
122
- next false if stage == :param && !property_schema.options(:param)
123
- next false if stage == :render && !property_schema.options(:render)
124
-
125
- # 根据 locked_scope 过滤
126
- next true if locked_scopes.empty? # locked_scope 未提供时不过滤
127
- property_scope = property_schema.options(stage, :scope)
128
- property_scope = [property_scope] unless property_scope.is_a?(Array)
129
- next true if property_scope.empty?
130
- (locked_scopes - property_scope).empty? # user_scope 应被消耗殆尽
131
- end
132
- required_keys = properties.filter do |key, property_schema|
133
- property_schema.options(stage, :required)
134
- end.keys
135
- properties = properties.transform_values do |property_schema|
136
- property_schema.to_schema_doc(**user_options, to_ref: true)
137
- end
138
62
 
139
63
  schema = { type: 'object' }
140
- schema[:description] = stage_options[:description] if stage_options[:description]
64
+ schema[:description] = options[:description] if options[:description]
65
+
66
+ properties, required_keys = @properties.to_swagger_doc(stage: stage, locked_scopes: locked_scopes, **user_options)
141
67
  schema[:properties] = properties unless properties.empty?
142
68
  schema[:required] = required_keys unless required_keys.empty?
143
69
  schema
@@ -150,18 +76,6 @@ module Meta
150
76
  def locked_exclude
151
77
  locked_options && locked_options[:exclude]
152
78
  end
153
-
154
- private
155
-
156
- def resolve_property_value(object_value, name, property_schema, stage)
157
- if property_schema.value?(stage)
158
- nil
159
- elsif object_value.is_a?(Hash) || object_value.is_a?(ObjectWrapper)
160
- object_value.key?(name.to_s) ? object_value[name.to_s] : object_value[name.to_sym]
161
- else
162
- raise "不应该还有其他类型了,已经在类型转换中将其转换为 Meta::JsonSchema::ObjectWrapper 了"
163
- end
164
- end
165
79
  end
166
80
  end
167
81
  end
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module Meta
6
+ module JsonSchema
7
+ class Properties
8
+ class StagingProperty
9
+ def initialize(param:, render:, none:)
10
+ @param_stage = param
11
+ @render_stage = render
12
+ @none_stage = none
13
+ end
14
+
15
+ def stage(stage = nil)
16
+ case stage
17
+ when :param
18
+ @param_stage
19
+ when :render
20
+ @render_stage
21
+ else
22
+ @none_stage
23
+ end
24
+ end
25
+
26
+ def stage?(stage)
27
+ stage(stage) != nil
28
+ end
29
+
30
+ def schema(stage = nil)
31
+ stage(stage).schema
32
+ end
33
+
34
+ def self.build(options, build_schema)
35
+ param_opts, render_opts, common_opts = SchemaOptions.divide_to_param_and_render(options)
36
+
37
+ StagingProperty.new(
38
+ param: options[:param] === false ? nil : ScopingProperty.build(param_opts, build_schema),
39
+ render: options[:render] === false ? nil : ScopingProperty.build(render_opts, build_schema),
40
+ none: ScopingProperty.build(common_opts, build_schema)
41
+ )
42
+ end
43
+ end
44
+
45
+ class ScopingProperty
46
+ attr_reader :scope, :schema
47
+
48
+ def initialize(scope: :all, schema:)
49
+ scope = :all if scope.nil?
50
+ scope = [scope] unless scope.is_a?(Array) || scope == :all
51
+ if scope.is_a?(Array) && scope.any? { |s| s.is_a?(Integer) }
52
+ raise ArgumentError, 'scope 选项内不可传递数字'
53
+ end
54
+ @scope = scope
55
+
56
+ @schema = schema
57
+ end
58
+
59
+ def self.build(options, build_schema)
60
+ options = options.dup
61
+ scope = options.delete(:scope)
62
+ schema = build_schema.call(options)
63
+ ScopingProperty.new(scope: scope, schema: schema)
64
+ end
65
+ end
66
+
67
+ extend Forwardable
68
+
69
+ def initialize(properties)
70
+ @properties = properties
71
+ end
72
+
73
+ def filter(object_value, user_options = {})
74
+ # 第一步,根据 user_options[:scope] 需要过滤一些字段
75
+ stage = user_options[:stage]
76
+ # 传递一个数字;因为 scope 不能包含数字,这里传递一个数字,使得凡是配置 scope 的属性都会被过滤
77
+ user_scope = user_options[:scope] || [0]
78
+ exclude = user_options.delete(:exclude) # 这里删除 exclude 选项,不要传递给下一层
79
+ properties = filter_by(stage: stage, user_scope: user_scope)
80
+ filtered_properties = properties.filter do |name, property|
81
+ # 通过 discard_missing 过滤
82
+ next false if user_options[:discard_missing] && !object_value.key?(name.to_s)
83
+
84
+ # 通过 locked_exclude 选项过滤
85
+ next false if exclude && exclude.include?(name)
86
+
87
+ # 默认返回 true
88
+ next true
89
+ end
90
+
91
+ # 第二步,递归过滤每一个属性
92
+ object = {}
93
+ errors = {}
94
+ filtered_properties.each do |name, property_schema|
95
+ value = resolve_property_value(object_value, name, property_schema, stage)
96
+
97
+ begin
98
+ object[name] = property_schema.filter(value, **user_options, object_value: object_value)
99
+ rescue JsonSchema::ValidationErrors => e
100
+ errors.merge! e.prepend_root(name).errors
101
+ end
102
+ end.to_h
103
+
104
+ if errors.empty?
105
+ object
106
+ else
107
+ raise JsonSchema::ValidationErrors.new(errors)
108
+ end
109
+ end
110
+
111
+ def to_swagger_doc(locked_scopes:, stage:, **user_options)
112
+ properties = filter_by(stage: stage, user_scope: locked_scopes)
113
+ required_keys = properties.filter do |key, property_schema|
114
+ property_schema.options[:required]
115
+ end.keys
116
+ properties = properties.transform_values do |property_schema |
117
+ property_schema.to_schema_doc(stage: stage, **user_options)
118
+ end
119
+ [properties, required_keys]
120
+ end
121
+
122
+ # 程序中有些地方用到了这三个方法
123
+ def_delegators :@properties, :empty?, :key?, :[]
124
+
125
+ def self.build_property(*args)
126
+ StagingProperty.build(*args)
127
+ end
128
+
129
+ private
130
+
131
+ def filter_by(stage:, user_scope: false)
132
+ properties = @properties.filter do |name, property|
133
+ # 通过 stage 过滤。
134
+ next false unless property.stage?(stage)
135
+ property = property.stage(stage)
136
+
137
+ # 通过 user_scope 过滤
138
+ next true if property.scope == :all
139
+ (user_scope - property.scope).empty? # user_scope 应被消耗殆尽
140
+ end
141
+ properties.transform_values do |property|
142
+ property.stage(stage).schema
143
+ end
144
+ end
145
+
146
+ def resolve_property_value(object_value, name, property_schema, stage)
147
+ if property_schema.value?
148
+ nil
149
+ elsif object_value.is_a?(Hash) || object_value.is_a?(ObjectWrapper)
150
+ object_value.key?(name.to_s) ? object_value[name.to_s] : object_value[name.to_sym]
151
+ else
152
+ raise "不应该还有其他类型了,已经在类型转换中将其转换为 Meta::JsonSchema::ObjectWrapper 了"
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_schema'
4
+
5
+ module Meta
6
+ module JsonSchema
7
+ class RefSchema < BaseSchema
8
+ attr_reader :schema
9
+
10
+ def initialize(schema, options = {})
11
+ super(options)
12
+ @schema = schema
13
+ end
14
+
15
+ def filter(value, user_options = {})
16
+ value = super
17
+ schema.filter(value, user_options)
18
+ end
19
+
20
+ def to_schema_doc(user_options)
21
+ schema_name = schema.resolve_name(user_options[:stage])
22
+
23
+ # 首先将 Schema 写进 schemas 选项中去
24
+ schema_components = user_options[:schemas]
25
+ unless schema_components.key?(schema_name)
26
+ schema_components[schema_name] = nil # 首先设置 schemas 防止出现无限循环
27
+ schema_components[schema_name] = schema.to_schema_doc(**user_options) # 原地修改 schemas,无妨
28
+ end
29
+
30
+ # 返回的是 $ref 结构
31
+ { '$ref': "#/components/schemas/#{schema_name}" }
32
+ end
33
+ end
34
+ end
35
+ end
@@ -7,6 +7,4 @@ require_relative 'support/presenters'
7
7
  require_relative 'schemas/base_schema'
8
8
  require_relative 'schemas/object_schema'
9
9
  require_relative 'schemas/array_schema'
10
- require_relative 'builders/object_schema_builder'
11
- require_relative 'builders/array_schema_builder'
12
10
  require_relative 'builders/schema_builder_tool'
@@ -1,8 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../../utils/kwargs/builder'
4
+
3
5
  module Meta
4
6
  module JsonSchema
5
7
  module SchemaOptions
8
+ OPTIONS_CHECKER = Utils::KeywordArgs::Builder.build do
9
+ key :type, :items, :description, :presenter, :value, :format, :required, :default, :validate, :allowable, :properties, :convert
10
+ key :using, normalizer: ->(value) { value.is_a?(Proc) ? { resolve: value } : value }
11
+ key :param
12
+ key :render
13
+ end
14
+
6
15
  @default_options = {
7
16
  scope: [],
8
17
  required: false
@@ -14,29 +23,21 @@ module Meta
14
23
  ).uniq
15
24
 
16
25
  class << self
17
- def normalize_to_param_and_render(options)
26
+ def divide_to_param_and_render(options)
18
27
  common_opts = (options || {}).dup
19
28
  param_opts = common_opts.delete(:param)
20
29
  render_opts = common_opts.delete(:render)
21
30
 
22
31
  param_opts = merge_common_to_stage(common_opts, param_opts)
23
32
  render_opts = merge_common_to_stage(common_opts, render_opts)
24
- [param_opts, render_opts]
25
- end
26
-
27
- def merge_common_to_stage(common_opts, stage_opts)
28
- stage_opts = {} if stage_opts.nil? || stage_opts == true
29
- stage_opts = common_opts.merge(stage_opts) if stage_opts
30
- stage_opts = normalize(stage_opts) if stage_opts
31
- stage_opts
33
+ [param_opts, render_opts, common_opts]
32
34
  end
33
35
 
34
36
  def normalize(options)
37
+ options = OPTIONS_CHECKER.check(options)
38
+
35
39
  # 只要 options 中设置为 nil 的选项没有明确的意义,则下行代码是永远有效的
36
40
  options = (@default_options.compact).merge(options.compact)
37
- options[:scope] = [options[:scope]] unless options[:scope].is_a?(Array)
38
- # TODO: 更好的规范选项的方式,以及如何检查深层次嵌套下参数类型的错误
39
- options[:using] = { resolve: options[:using] } if options[:using].is_a?(Proc)
40
41
  if options[:using]
41
42
  if options[:type].nil?
42
43
  options[:type] = 'object'
@@ -51,6 +52,14 @@ module Meta
51
52
 
52
53
  options
53
54
  end
55
+
56
+ private
57
+
58
+ def merge_common_to_stage(common_opts, stage_opts)
59
+ stage_opts = {} if stage_opts.nil? || stage_opts == true
60
+ stage_opts = common_opts.merge(stage_opts) if stage_opts
61
+ stage_opts
62
+ end
54
63
  end
55
64
  end
56
65
  end
@@ -0,0 +1,2 @@
1
+ require_relative 'load_i18n'
2
+ require_relative 'json_schema/schemas'
data/lib/meta/rails.rb ADDED
@@ -0,0 +1,98 @@
1
+ # 作为 Rails 插件
2
+
3
+ require_relative 'errors'
4
+ require_relative 'json_schema/schemas'
5
+
6
+ module Meta
7
+ module Rails
8
+ def self.setup
9
+ # 第一步,为 ActionController 添加一个新的 Renderer
10
+ ActionController::Renderers.add :json_on_schema do |obj, options|
11
+ options = options.dup
12
+ status = options.delete(:status) || 200
13
+ scope = options.delete(:scope) || :all
14
+
15
+ route_definitions = self.class.route_definitions
16
+ route_definition = route_definitions[[self.class, params[:action].to_sym]]
17
+ raise '未绑定 Route 定义' unless route_definition
18
+
19
+ meta_definition = route_definition.meta
20
+ raise '未提供 status 宏定义' unless meta_definition[:responses] && meta_definition[:responses][status]
21
+
22
+ render_schema = meta_definition[:responses][status]
23
+ str = render_schema.filter(obj, execution: self, stage: :render, scope: scope)
24
+ render json: str, **options
25
+ rescue JsonSchema::ValidationErrors => e
26
+ raise Errors::RenderingInvalid.new(e.errors)
27
+ end
28
+ end
29
+
30
+ module Plugin
31
+ def self.generate_swagger_doc(klass, **kwargs)
32
+ paths_and_routes = klass.route_definitions.values.map do |route_definition|
33
+ [route_definition.path, route_definition]
34
+ end
35
+ SwaggerDocUtil.generate_from_paths_and_routes(paths_and_routes, **kwargs)
36
+ end
37
+
38
+ def self.included(base)
39
+ # 已经被父类引入过,不再重复引入
40
+ return if self.respond_to?(:route_definitions)
41
+
42
+ # 为 ActionController 引入宏命令,宏命令在子类中生效
43
+ base.extend ClassMethods
44
+
45
+ # 为 ActionController 定义一个 Route Definitions,子类实例可以通过 self.class.route_definitions 访问
46
+ route_definitions = {}
47
+ base.define_singleton_method(:route_definitions) { route_definitions }
48
+
49
+ # 定义一个方法,子类在定义方法后,将当前的路由定义应用到该方法上
50
+ base.define_singleton_method(:apply_route_definition) do |klass, method_name|
51
+ if @current_route_builder
52
+ self.route_definitions[[klass, method_name]] = @current_route_builder.build
53
+ @current_route_builder = nil
54
+ end
55
+ end
56
+ # 触发 apply_route_definition 方法
57
+ base.define_singleton_method(:method_added) do |name|
58
+ apply_route_definition(self, name)
59
+ end
60
+
61
+ # 为 ActionController 定义一个方法,用于获取过滤后的参数
62
+ attr_accessor :params_on_schema
63
+
64
+ # 为 ActionController 定义一个 before_action,用于过滤参数
65
+ base.before_action do
66
+ route_definitions = self.class.route_definitions
67
+ route_definition = route_definitions[[self.class, params[:action].to_sym]]
68
+ next unless route_definition
69
+
70
+ meta_definition = route_definition.meta
71
+ next if meta_definition[:parameters].empty? && meta_definition[:request_body].nil?
72
+
73
+ raw_params = self.params.to_unsafe_h
74
+ final_params = {}
75
+
76
+ if meta_definition[:parameters]
77
+ parameters_meta = meta_definition[:parameters]
78
+ final_params = parameters_meta.filter(request)
79
+ end
80
+ if meta_definition[:request_body]
81
+ params_schema = meta_definition[:request_body]
82
+ final_params.merge! params_schema.filter(raw_params, stage: :param)
83
+ end
84
+
85
+ self.params_on_schema = final_params
86
+ rescue JsonSchema::ValidationErrors => e
87
+ raise Errors::ParameterInvalid.new(e.errors)
88
+ end
89
+ end
90
+
91
+ module ClassMethods
92
+ def route(path = '', method = :all, &block)
93
+ @current_route_builder = RouteDSL::RouteBuilder.new(path, method, &block)
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require_relative '../application/parameters'
2
3
 
3
4
  module Meta
4
5
  module RouteDSL
@@ -17,7 +18,7 @@ module Meta
17
18
  end
18
19
 
19
20
  def build
20
- @parameters
21
+ Parameters.new(@parameters)
21
22
  end
22
23
  end
23
24
  end
@@ -4,10 +4,13 @@ module Meta
4
4
  module SwaggerDocUtil
5
5
  class << self
6
6
  def generate(application, info: {}, servers: [])
7
- routes = get_paths_and_routes!(application)
7
+ paths_and_routes = get_paths_and_routes!(application)
8
+ return generate_from_paths_and_routes(paths_and_routes, info: info, servers: servers)
9
+ end
8
10
 
11
+ def generate_from_paths_and_routes(paths_and_routes, info: {}, servers: [])
9
12
  schemas = {}
10
- paths = routes.group_by { |path, route| path }.map { |path, routes| [path, routes.map { |item| item[1] }]}.map do |path, routes|
13
+ paths = paths_and_routes.group_by { |path, route| path }.map { |path, routes| [path, routes.map { |item| item[1] }]}.map do |path, routes|
11
14
  operations = routes.map do |route|
12
15
  [route.method.downcase.to_sym, generate_operation_object(route, schemas)]
13
16
  end.to_h