meta-api 0.0.8 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/lib/meta/application/execution.rb +19 -49
  4. data/lib/meta/application/metadata.rb +65 -5
  5. data/lib/meta/application/route.rb +4 -26
  6. data/lib/meta/config.rb +2 -2
  7. data/lib/meta/json_schema/builders/schema_builder_tool.rb +2 -1
  8. data/lib/meta/json_schema/schemas/array_schema.rb +13 -14
  9. data/lib/meta/json_schema/schemas/base_schema.rb +101 -36
  10. data/lib/meta/json_schema/schemas/object_schema.rb +22 -9
  11. data/lib/meta/json_schema/schemas/properties.rb +20 -74
  12. data/lib/meta/json_schema/schemas/scoping_schema.rb +41 -0
  13. data/lib/meta/json_schema/schemas/staging_schema.rb +58 -0
  14. data/lib/meta/json_schema/schemas/unsupported_schema.rb +22 -0
  15. data/lib/meta/json_schema/support/errors.rb +3 -0
  16. data/lib/meta/json_schema/support/schema_options.rb +0 -9
  17. data/lib/meta/route_dsl/meta_builder.rb +2 -2
  18. data/lib/meta/swagger_doc.rb +1 -1
  19. data/lib/meta/utils/kwargs/builder.rb +2 -0
  20. data/lib/meta/utils/route_dsl_builders.rb +1 -0
  21. data/meta-api.gemspec +6 -5
  22. metadata +71 -111
  23. data/.autoenv.zsh +0 -1
  24. data/.gitignore +0 -7
  25. data/.rubocop.yml +0 -28
  26. data/Gemfile +0 -19
  27. data/Gemfile.lock +0 -68
  28. data/README.md +0 -166
  29. data/Rakefile +0 -3
  30. data/docs/Rails.md +0 -61
  31. data/docs//345/220/215/347/247/260/347/224/261/346/235/245.md +0 -7
  32. data/docs//345/246/202/344/275/225/350/264/241/347/214/256.md +0 -10
  33. data/docs//346/225/231/347/250/213.md +0 -1708
  34. data/docs//347/264/242/345/274/225.md +0 -183
  35. data/examples/lobster.rb +0 -71
  36. data/examples/rack_app/README.md +0 -3
  37. data/examples/rack_app/config.ru +0 -6
  38. data/examples/rack_app/hello.rb +0 -6
  39. data/examples/rack_app/timing.rb +0 -15
  40. data/examples/rails_app/.gitattributes +0 -5
  41. data/examples/rails_app/.gitignore +0 -23
  42. data/examples/rails_app/.rspec +0 -1
  43. data/examples/rails_app/.ruby-version +0 -1
  44. data/examples/rails_app/Gemfile +0 -29
  45. data/examples/rails_app/Gemfile.lock +0 -190
  46. data/examples/rails_app/README.md +0 -11
  47. data/examples/rails_app/Rakefile +0 -6
  48. data/examples/rails_app/app/controllers/application_controller.rb +0 -7
  49. data/examples/rails_app/app/controllers/concerns/.keep +0 -0
  50. data/examples/rails_app/app/controllers/data_controller.rb +0 -63
  51. data/examples/rails_app/app/controllers/swagger_controller.rb +0 -13
  52. data/examples/rails_app/app/models/concerns/.keep +0 -0
  53. data/examples/rails_app/bin/rails +0 -4
  54. data/examples/rails_app/bin/rake +0 -4
  55. data/examples/rails_app/bin/setup +0 -25
  56. data/examples/rails_app/config/application.rb +0 -39
  57. data/examples/rails_app/config/boot.rb +0 -3
  58. data/examples/rails_app/config/credentials.yml.enc +0 -1
  59. data/examples/rails_app/config/environment.rb +0 -5
  60. data/examples/rails_app/config/environments/development.rb +0 -51
  61. data/examples/rails_app/config/environments/production.rb +0 -65
  62. data/examples/rails_app/config/environments/test.rb +0 -50
  63. data/examples/rails_app/config/initializers/cors.rb +0 -16
  64. data/examples/rails_app/config/initializers/filter_parameter_logging.rb +0 -8
  65. data/examples/rails_app/config/initializers/inflections.rb +0 -16
  66. data/examples/rails_app/config/initializers/meta_rails_plugin.rb +0 -3
  67. data/examples/rails_app/config/locales/en.yml +0 -33
  68. data/examples/rails_app/config/puma.rb +0 -43
  69. data/examples/rails_app/config/routes.rb +0 -13
  70. data/examples/rails_app/config.ru +0 -6
  71. data/examples/rails_app/lib/tasks/.keep +0 -0
  72. data/examples/rails_app/log/.keep +0 -0
  73. data/examples/rails_app/public/robots.txt +0 -1
  74. data/examples/rails_app/spec/data_controller_spec.rb +0 -60
  75. data/examples/rails_app/spec/rails_helper.rb +0 -55
  76. data/examples/rails_app/spec/spec_helper.rb +0 -94
  77. data/examples/rails_app/spec/swagger_controller_spec.rb +0 -13
  78. data/examples/rails_app/tmp/.keep +0 -0
  79. data/examples/rails_app/tmp/pids/.keep +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b35c78915baab28213ceb9f2b6184c8ae177742ce0524529c681132fa6e0390
4
- data.tar.gz: bc4ca96b46e0284f10fa41360ef4308aa7378121ead9f0cd1a536ac027cd590e
3
+ metadata.gz: 3394a5e2d3b6dfd45f1478d8477426a4a947253d3252a050dc9c1d7cafd3c3d1
4
+ data.tar.gz: 98d12919b2adf824bec651ba5625076ddb7571962cdc66cee188b3b250fd1b24
5
5
  SHA512:
6
- metadata.gz: c5cf6b6c1ee9ba5af2826b6682e57a357bbd74caf7807a5f3f9cbd124b99bdce4bcc7cba7040b45b2c1d62b9abfda1e4ee6c47853c2ebcb57bfd2d8a224db622
7
- data.tar.gz: 5043cc4c8516d7c6bc827d32889e26a2164299ba0a71b1439bd1594aabdd1975bbd1994139fed2ffca93409e9c1f2118157301b3cef502767750c52b061c7367
6
+ metadata.gz: c1e80fecb3cf0bce148345660f19a8f44136b1e1647471e89aa9fb4d1c4f55a0ddf7ccb11fdc26ca7880c318173ed4f3254e0835d5bfd96e77048a301804d729
7
+ data.tar.gz: 16d7fdf0f82b89155eb0fc655e424d54ba5bf880c984dd322266afdb2952c4c34ff1ce2c83f8d174adcb086057d6c63aa7534b75defd4c3f7ff84f8a7f948ec0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # 更新日志
2
2
 
3
+ ## 0.0.9(2023 年 7 月 22 日)
4
+
5
+ 1. JsonSchema 添加 before:、after: 选项,用于在过滤前后执行一些操作。
6
+ 2. 新添加一个 if: 选项用在属性上,它能够作为一个条件,当条件为 false 时,该属性不会被包含在结果内。
7
+ 3. 属性的 scope: 选项改名为 on: 选项。
8
+ 4. MetaBuilder 添加一个 scope 宏。
9
+ 5. Meta.config 的 JsonSchema 相关配置项改名。
10
+ 6. 优化异常的捕获。
11
+
12
+ ## 0.0.8(2023 年 7 月 17 日)
13
+
14
+ 1. `Meta.config` 添加 `initialize_configuration` 方法,用于接受若干个 Hash 初始化配置。
15
+ 2. 修复 GET 请求下会将 `header` 参数覆盖为 `query` 参数的 bug.
16
+
3
17
  ## 0.0.7(2023 年 7 月 14 日)
4
18
 
5
19
  1. 定义 parameters 宏时能够自动识别 `path` 参数。
@@ -4,8 +4,8 @@ require 'rack'
4
4
 
5
5
  module Meta
6
6
  class Execution
7
- attr_reader :request, :response, :params_schema, :request_body_schema
8
- attr_accessor :parameters
7
+ attr_reader :request, :response
8
+ attr_accessor :route_meta
9
9
 
10
10
  def initialize(request)
11
11
  @request = request
@@ -13,6 +13,10 @@ module Meta
13
13
  @parameters = {}
14
14
  end
15
15
 
16
+ def parameters
17
+ @_parameters || @_parameters = route_meta.parse_parameters(self)
18
+ end
19
+
16
20
  # 调用方式:
17
21
  #
18
22
  # - `request_body`:等价于 request_body(:keep_missing)
@@ -23,9 +27,9 @@ module Meta
23
27
 
24
28
  case mode
25
29
  when :keep_missing
26
- @_request_body[:keep_missing] || @_request_body[:keep_missing] = parse_request_body_for_replacing.freeze
30
+ @_request_body[:keep_missing] || @_request_body[:keep_missing] = route_meta.parse_request_body(self).freeze
27
31
  when :discard_missing
28
- @_request_body[:discard_missing] || @_request_body[:discard_missing] = parse_request_body_for_updating.freeze
32
+ @_request_body[:discard_missing] || @_request_body[:discard_missing] = route_meta.parse_request_body(self, discard_missing: true).freeze
29
33
  else
30
34
  raise NameError, "未知的 mode 参数:#{mode}"
31
35
  end
@@ -45,7 +49,7 @@ module Meta
45
49
  @_params[:raw] = parse_raw_params.freeze
46
50
  else
47
51
  params = parameters
48
- params = params.merge(request_body(mode) || {}) if @request_body_schema
52
+ params = params.merge(request_body(mode) || {}) if route_meta.request_body
49
53
  @_params[mode] = params
50
54
  end
51
55
 
@@ -67,16 +71,14 @@ module Meta
67
71
  end
68
72
 
69
73
  @renders ||= {}
70
- @renders[key] = { value: value, options: options || {} }
71
- end
72
-
73
- # 运行过程中首先会解析参数
74
- def parse_parameters(parameters_meta)
75
- self.parameters = parameters_meta.filter(request).freeze
76
- end
74
+ raise '同一种 render 方式只能调用一次,请检查是否重复使用相同的 key 渲染,或者重复调用 render(value) 的方式' if @renders.key?(key)
75
+ if key == :__root__ && @renders.keys.any? { |key| key != :__root__ }
76
+ raise '已使用 render(:key, value) 的方式调用过,不能再使用 render(value) 的方式'
77
+ elsif key != :__root__ && @renders.key?(:__root__)
78
+ raise '已使用 render(value) 的方式调用过,不能再使用 render(:key, value) 的方式'
79
+ end
77
80
 
78
- def parse_request_body(schema)
79
- @request_body_schema = schema
81
+ @renders[key] = { value: value, options: options || {} }
80
82
  end
81
83
 
82
84
  def render_entity(entity_schema)
@@ -94,12 +96,7 @@ module Meta
94
96
  options = {}
95
97
  end
96
98
 
97
- new_hash = entity_schema.filter(hash,
98
- **Meta.config.json_schema_user_options,
99
- **Meta.config.json_schema_render_stage_options,
100
- **options,
101
- execution: self, stage: :render
102
- )
99
+ new_hash = route_meta.render_entity(self, entity_schema, hash, options)
103
100
  response.content_type = 'application/json' if response.content_type.nil?
104
101
  response.body = [JSON.generate(new_hash)]
105
102
  else
@@ -108,13 +105,8 @@ module Meta
108
105
  final_value = {}
109
106
  renders.each do |key, render_content|
110
107
  raise Errors::RenderingError, "渲染的键名 `#{key}` 不存在,请检查实体定义以确认是否有拼写错误" unless entity_schema.properties.key?(key)
111
- schema = entity_schema.properties[key].schema(:render)
112
- final_value[key] = schema.filter(render_content[:value],
113
- **Meta.config.json_schema_user_options,
114
- **Meta.config.json_schema_render_stage_options,
115
- **render_content[:options],
116
- execution: self, stage: :render
117
- )
108
+ schema = entity_schema.properties[key].staged(:render)
109
+ final_value[key] = route_meta.render_entity(self, schema, render_content[:value], render_content[:options])
118
110
  rescue JsonSchema::ValidationErrors => e
119
111
  # 错误信息再度绑定 key
120
112
  errors.merge! e.errors.transform_keys! { |k| k.empty? ? key : "#{key}.#{k}" }
@@ -152,28 +144,6 @@ module Meta
152
144
  json
153
145
  end
154
146
 
155
- def parse_request_body_for_replacing
156
- request_body_schema.filter(
157
- params(:raw),
158
- **Meta.config.json_schema_user_options,
159
- **Meta.config.json_schema_param_stage_options,
160
- execution: self, stage: :param
161
- )
162
- rescue JsonSchema::ValidationErrors => e
163
- raise Errors::ParameterInvalid.new(e.errors)
164
- end
165
-
166
- def parse_request_body_for_updating
167
- request_body_schema.filter(
168
- params(:raw),
169
- **Meta.config.json_schema_user_options,
170
- **Meta.config.json_schema_param_stage_options,
171
- execution: self, stage: :param, discard_missing: true
172
- )
173
- rescue JsonSchema::ValidationErrors => e
174
- raise Errors::ParameterInvalid.new(e.errors)
175
- end
176
-
177
147
  class Abort < Exception
178
148
  end
179
149
 
@@ -1,24 +1,84 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative 'parameters'
3
4
 
4
5
  module Meta
5
6
  class Metadata
6
- attr_reader :title, :description, :tags, :parameters, :request_body, :responses
7
+ module ExecutionMethods
8
+ def parse_parameters(execution)
9
+ parameters.filter(execution.request)
10
+ # TODO: 未捕获 JsonSchema::ValidationErrors 异常
11
+ end
12
+
13
+ def parse_request_body(execution, discard_missing: false)
14
+ method = execution.request.request_method.downcase.to_sym
15
+ request_body.filter(
16
+ execution.params(:raw),
17
+ **Meta.config.json_schema_user_options,
18
+ **Meta.config.json_schema_param_stage_user_options,
19
+ **{ execution: execution, stage: :param, scope: @scope.concat([method]), discard_missing: discard_missing }.compact
20
+ )
21
+ rescue JsonSchema::ValidationErrors => e
22
+ raise Errors::ParameterInvalid.new(e.errors)
23
+ end
24
+
25
+ def render_entity(execution, entity_schema, value, user_options)
26
+ method = execution.request.request_method.downcase.to_sym
27
+ entity_schema.filter(value,
28
+ **Meta.config.json_schema_user_options,
29
+ **Meta.config.json_schema_render_stage_user_options,
30
+ **{ execution: execution, stage: :render, scope: @scope.concat([method]) }.compact,
31
+ **user_options,
32
+ )
33
+ end
34
+
35
+ def set_response(execution)
36
+ set_status(execution)
37
+ render_response_body(execution) if self.responses
38
+ end
39
+
40
+ private
41
+
42
+ def set_status(execution)
43
+ response_definitions = self.responses
44
+ response = execution.response
45
+ if response.status == 0
46
+ response.status = (response_definitions&.length > 0) ? response_definitions.keys[0] : 200
47
+ end
48
+ end
49
+
50
+ def render_response_body(execution)
51
+ response_definitions = self[:responses]
52
+ return if response_definitions.empty? # 未设定 status 宏时不需要执行 render_entity 方法
53
+
54
+ # 查找 entity schema
55
+ entity_schema = response_definitions[execution.response.status]
56
+ return if entity_schema.nil?
57
+
58
+ # 执行面向 schema 的渲染
59
+ execution.render_entity(entity_schema)
60
+ end
61
+ end
62
+
63
+ include ExecutionMethods
64
+
65
+ attr_reader :title, :description, :tags, :parameters, :request_body, :responses, :scope
7
66
 
8
- def initialize(title: nil, description: nil, tags: [], parameters: {}, request_body: nil, responses: nil)
67
+ def initialize(title: nil, description: nil, tags: [], parameters: {}, request_body: nil, responses: nil, scope: nil)
9
68
  @title = title
10
69
  @description = description
11
70
  @tags = tags
12
71
  @parameters = parameters.is_a?(Parameters) ? parameters : Parameters.new(parameters)
13
72
  @request_body = request_body
14
73
  @responses = responses || {} # || { 204 => nil }
74
+ @scope = scope
15
75
  end
16
76
 
17
77
  def [](key)
18
78
  send(key)
19
79
  end
20
80
 
21
- def generate_operation_doc(schemas)
81
+ def generate_operation_doc(schemas, scope: [])
22
82
  operation_object = {}
23
83
 
24
84
  operation_object[:summary] = title if title
@@ -28,7 +88,7 @@ module Meta
28
88
  operation_object[:parameters] = parameters.to_swagger_doc
29
89
 
30
90
  if request_body
31
- schema = request_body.to_schema_doc(stage: :param, schemas: schemas)
91
+ schema = request_body.to_schema_doc(stage: :param, scope: self.scope + scope, schemas: schemas)
32
92
  if schema || true
33
93
  operation_object[:requestBody] = {
34
94
  content: {
@@ -45,7 +105,7 @@ module Meta
45
105
  description: '', # description 属性必须存在
46
106
  content: schema ? {
47
107
  'application/json' => {
48
- schema: schema.to_schema_doc(stage: :render, schemas: schemas)
108
+ schema: schema.to_schema_doc(stage: :render, scope: self.scope + scope, schemas: schemas)
49
109
  }
50
110
  } : nil
51
111
  }.compact
@@ -21,13 +21,9 @@ module Meta
21
21
  def execute(execution, remaining_path)
22
22
  path_matching.merge_path_params(remaining_path, execution.request)
23
23
 
24
- execution.parse_parameters(@meta[:parameters]) if @meta[:parameters]
25
- execution.parse_request_body(@meta[:request_body]) if @meta[:request_body]
26
-
24
+ execution.route_meta = @meta # 解析参数的准备
27
25
  action.execute(execution) if action
28
-
29
- set_status(execution)
30
- render_entity(execution) if @meta[:responses]
26
+ @meta.set_response(execution) if @meta.responses
31
27
  rescue Execution::Abort
32
28
  execution.response.status = 200 if execution.response.status == 0
33
29
  end
@@ -42,26 +38,8 @@ module Meta
42
38
  return true
43
39
  end
44
40
 
45
- private
46
-
47
- def set_status(execution)
48
- response_definitions = @meta[:responses]
49
- response = execution.response
50
- if response.status == 0
51
- response.status = (response_definitions&.length > 0) ? response_definitions.keys[0] : 200
52
- end
53
- end
54
-
55
- def render_entity(execution)
56
- response_definitions = @meta[:responses]
57
- return if response_definitions.empty? # 未设定 status 宏时不需要执行 render_entity 方法
58
-
59
- # 查找 entity schema
60
- entity_schema = response_definitions[execution.response.status]
61
- return if entity_schema.nil?
62
-
63
- # 执行面向 schema 的渲染
64
- execution.render_entity(entity_schema)
41
+ def generate_operation_doc(schemas)
42
+ meta.generate_operation_doc(schemas, scope: [method])
65
43
  end
66
44
  end
67
45
  end
data/lib/meta/config.rb CHANGED
@@ -6,8 +6,8 @@ module Meta
6
6
  DEFAULT_OPTIONS = {
7
7
  default_locked_scope: nil,
8
8
  json_schema_user_options: {},
9
- json_schema_param_stage_options: {},
10
- json_schema_render_stage_options: {}
9
+ json_schema_param_stage_user_options: {},
10
+ json_schema_render_stage_user_options: {}
11
11
  }
12
12
 
13
13
  class << self
@@ -4,6 +4,7 @@ require_relative 'ref_schema_builder'
4
4
  require_relative 'dynamic_schema_builder'
5
5
  require_relative 'array_schema_builder'
6
6
  require_relative 'object_schema_builder'
7
+ require_relative '../schemas/staging_schema'
7
8
 
8
9
  module Meta
9
10
  module JsonSchema
@@ -33,7 +34,7 @@ module Meta
33
34
  elsif apply_object_schema?(options, block)
34
35
  ObjectSchemaBuilder.new(options, &block).to_schema
35
36
  else
36
- BaseSchema.new(options)
37
+ StagingSchema.build_from_options(options)
37
38
  end
38
39
  end
39
40
 
@@ -11,20 +11,6 @@ module Meta
11
11
  @items = items
12
12
  end
13
13
 
14
- def filter(array_value, options = {})
15
- array_value = super(array_value, options)
16
- return nil if array_value.nil?
17
- raise ValidationError.new('参数应该传递一个数组') unless array_value.respond_to?(:each_with_index)
18
-
19
- array_value.each_with_index.map do |item, index|
20
- begin
21
- @items.filter(item, **options)
22
- rescue ValidationErrors => e
23
- raise e.prepend_root("[#{index}]")
24
- end
25
- end
26
- end
27
-
28
14
  def to_schema_doc(**user_options)
29
15
  stage_options = options
30
16
 
@@ -35,6 +21,19 @@ module Meta
35
21
  schema[:description] = stage_options[:description] if stage_options[:description]
36
22
  schema
37
23
  end
24
+
25
+ private
26
+
27
+ def filter_internal(array_value, user_options)
28
+ raise ValidationError.new('参数应该传递一个数组') unless array_value.respond_to?(:each_with_index)
29
+ array_value.each_with_index.map do |item, index|
30
+ begin
31
+ @items.filter(item, user_options)
32
+ rescue ValidationErrors => e
33
+ raise e.prepend_root("[#{index}]")
34
+ end
35
+ end
36
+ end
38
37
  end
39
38
  end
40
39
  end
@@ -5,11 +5,35 @@ require_relative '../support/schema_options'
5
5
 
6
6
  module Meta
7
7
  module JsonSchema
8
+ # 表示一个基本类型的 Schema,或继承自该类表示一个拥有其他扩展能力的 Schema.
9
+ #
10
+ # 该类包含了通用 JsonSchema 思维的基本逻辑,比如 stage 和 scope. 其他 Schema 类应当主动地继
11
+ # 承自该类,这样就会自动获得 stage 和 scope 的能力。
12
+ #
13
+ # 该类剩余的逻辑是提供一个 `options` 属性,用于描述该 Schema. 因此,直接实例化该类可以用于表示
14
+ # 基本类型,而继承该类可以用于表示还有内部递归处理的对象和数组类型。这时,应当在子类的构造函数中调
15
+ # 用父类的构造方法,以便初始化 `options` 属性。并且在子类中重写 `filter_internal` 方法,实现
16
+ # 内部递归处理的逻辑。这种模式的案例主要是 `ObjectSchema` 和 `ArraySchema`.
17
+ #
18
+ # 如果是组合模式,也应当继承自该类,以便获得 stage 和 scope 的能力。但是,组合模式的 `options`
19
+ # 调用是非法的,因此不应当在构造函数中调用父类的构造方法。此时 options 为 nil,这样用到 options
20
+ # 的地方都会抛出异常(NoMethodError: undefined method `[]' for nil:NilClass)。这种模式
21
+ # 的案例很多,包括 StagingSchema、RefSchema 等。
8
22
  class BaseSchema
9
23
  OPTIONS_CHECKER = Utils::KeywordArgs::Builder.build do
10
24
  key :type, :items, :description, :presenter, :value, :default, :properties, :convert
11
25
  key :validate, :required, :format, :allowable
12
- key :param, :render
26
+ key :before, :after
27
+ key :if
28
+ end
29
+
30
+ USER_OPTIONS_CHECKER = Utils::KeywordArgs::Builder.build do
31
+ key :execution, :object_value, :type_conversion, :validation, :user_data
32
+ key :stage, validator: ->(value) { raise ArgumentError, "stage 只能取值为 :param 或 :render" unless [:param, :render].include?(value) }
33
+
34
+ # 以下是 ObjectSchema 需要的选项
35
+ # extra_properties 只能取值为 :ignore、:raise_error
36
+ key :discard_missing, :extra_properties, :exclude, :scope
13
37
  end
14
38
 
15
39
  # `options` 包含了转换器、验证器、文档、选项。
@@ -22,25 +46,22 @@ module Meta
22
46
  attr_reader :options
23
47
 
24
48
  def initialize(options = {})
49
+ raise ArgumentError, 'options 必须是 Hash 类型' unless options.is_a?(Hash)
25
50
  options = OPTIONS_CHECKER.check(options)
26
51
  raise '不允许 BaseSchema 直接接受 array 类型,必须通过继承使用 ArraySchema' if options[:type] == 'array' && self.class == BaseSchema
27
52
 
28
53
  @options = SchemaOptions.normalize(options).freeze
29
54
  end
30
55
 
31
- USER_OPTIONS_CHECKER = Utils::KeywordArgs::Builder.build do
32
- key :execution, :object_value, :type_conversion, :validation, :user_data
33
- key :stage, validator: ->(value) { raise ArgumentError, "stage 只能取值为 :param 或 :render" unless [:param, :render].include?(value) }
34
-
35
- # 以下是 ObjectSchema 需要的选项
36
- # extra_properties 只能取值为 :ignore、:raise_error
37
- key :discard_missing, :extra_properties, :exclude, :scope
56
+ def filter?
57
+ true
38
58
  end
39
59
 
40
60
  def filter(value, user_options = {})
41
61
  user_options = USER_OPTIONS_CHECKER.check(user_options)
42
62
 
43
- value = resolve_value(user_options) if options[:value]
63
+ value = value_callback(user_options) if options[:value]
64
+ value = before_callback(value, user_options) if options[:before]
44
65
  value = JsonSchema::Presenters.present(options[:presenter], value) if options[:presenter]
45
66
  value = resolve_default_value(options[:default]) if value.nil? && options.key?(:default)
46
67
  value = options[:convert].call(value) if options[:convert]
@@ -59,30 +80,45 @@ module Meta
59
80
  # 第二步,做校验。
60
81
  validate!(value, options) unless user_options[:validation] == false
61
82
 
83
+ # 第三步,如果存在内部属性,递归调用。
84
+ value = filter_internal(value, user_options) unless value.nil?
85
+
86
+ # 最后,返回 value
87
+ value = after_callback(value, user_options) if options[:after]
62
88
  value
63
89
  end
64
90
 
65
- def value?
66
- options[:value] != nil
91
+ # 返回能够处理 scope 和 stage 的 schema(可以是 self),否则应返回 UnsupportedStageSchema 或 nil.
92
+ def find_schema(scope:, stage:)
93
+ staged(stage)&.scoped(scope)
67
94
  end
68
95
 
69
- def resolve_value(user_options)
70
- value_proc = options[:value]
71
- if value_proc.lambda?
72
- value_proc_params = []
73
- value_proc_params << user_options[:object_value] if value_proc.arity >= 1
74
- value_proc_params << user_options[:user_data] if value_proc.arity >= 2
75
- else
76
- value_proc_params = [user_options[:object_value], user_options[:user_data]]
77
- end
96
+ # 返回能够处理 stage 的 schema(可以是 self),否则返回 UnsupportedStageSchema.
97
+ def staged(stage)
98
+ self
99
+ end
78
100
 
79
- if user_options[:execution]
80
- user_options[:execution].instance_exec(*value_proc_params, &value_proc)
101
+ # 返回能够处理 scope 的 schema(可以是 self),否则返回 nil.
102
+ def scoped(scope)
103
+ self
104
+ end
105
+
106
+ # 执行 if: 选项,返回 true 或 false
107
+ def if?(user_options)
108
+ return true if options[:if].nil?
109
+
110
+ execution = user_options[:execution]
111
+ if execution
112
+ execution.instance_exec(&options[:if])
81
113
  else
82
- value_proc.call(*value_proc_params)
114
+ options[:if]&.call
83
115
  end
84
116
  end
85
117
 
118
+ def value?
119
+ options[:value] != nil
120
+ end
121
+
86
122
  # 生成 Swagger 文档的 schema 格式。
87
123
  #
88
124
  # 选项:
@@ -106,22 +142,51 @@ module Meta
106
142
 
107
143
  private
108
144
 
109
- def validate!(value, stage_options)
110
- stage_options.each do |key, option|
111
- validator = JsonSchema::Validators[key]
112
- validator&.call(value, option, stage_options)
113
- end
145
+ def validate!(value, stage_options)
146
+ stage_options.each do |key, option|
147
+ validator = JsonSchema::Validators[key]
148
+ validator&.call(value, option, stage_options)
114
149
  end
150
+ end
115
151
 
116
- def resolve_default_value(default_resolver)
117
- if default_resolver.respond_to?(:call)
118
- default_resolver.call
119
- elsif default_resolver.respond_to?(:dup)
120
- default_resolver.dup
121
- else
122
- default_resolver
123
- end
152
+ def resolve_value(value, user_options, value_proc, arguments)
153
+ if value_proc.lambda?
154
+ value_proc_params = arguments[0...value_proc.arity]
155
+ else
156
+ value_proc_params = arguments
157
+ end
158
+ if user_options[:execution]
159
+ user_options[:execution].instance_exec(*value_proc_params, &value_proc)
160
+ else
161
+ value_proc.call(*value_proc_params)
124
162
  end
163
+ end
164
+
165
+ def resolve_default_value(default_resolver)
166
+ if default_resolver.respond_to?(:call)
167
+ default_resolver.call
168
+ elsif default_resolver.respond_to?(:dup)
169
+ default_resolver.dup
170
+ else
171
+ default_resolver
172
+ end
173
+ end
174
+
175
+ def filter_internal(value, user_options)
176
+ value
177
+ end
178
+
179
+ def value_callback(user_options)
180
+ resolve_value(nil, user_options, options[:value], [user_options[:object_value], user_options[:user_data]])
181
+ end
182
+
183
+ def before_callback(value, user_options)
184
+ resolve_value(value, user_options, options[:before], [value, user_options[:object_value], user_options[:user_data]])
185
+ end
186
+
187
+ def after_callback(value, user_options)
188
+ resolve_value(value, user_options, options[:after], [value, user_options[:object_value], user_options[:user_data]])
189
+ end
125
190
  end
126
191
  end
127
192
  end
@@ -38,13 +38,9 @@ module Meta
38
38
 
39
39
  def filter(object_value, user_options = {})
40
40
  # 合并 user_options
41
- user_options = user_options.merge(locked_options) if locked_options
42
41
  user_options = USER_OPTIONS_CHECKER.check(user_options)
43
-
44
- object_value = super(object_value, user_options)
45
-
46
- return nil if object_value.nil?
47
- @properties.filter(object_value, user_options)
42
+ user_options = merge_user_options(user_options, locked_options) if locked_options
43
+ super
48
44
  end
49
45
 
50
46
  # 合并其他的属性,并返回一个新的 ObjectSchema
@@ -57,13 +53,14 @@ module Meta
57
53
  @schema_name_resolver.call(stage, locked_scopes)
58
54
  end
59
55
 
60
- def to_schema_doc(stage: nil, **user_options)
61
- locked_scopes = (locked_options || {})[:scope] || []
56
+ def to_schema_doc(user_options = {})
57
+ user_options = USER_OPTIONS_CHECKER.check(user_options)
58
+ user_options = merge_user_options(user_options, locked_options) if locked_options
62
59
 
63
60
  schema = { type: 'object' }
64
61
  schema[:description] = options[:description] if options[:description]
65
62
 
66
- properties, required_keys = @properties.to_swagger_doc(stage: stage, locked_scopes: locked_scopes, **user_options)
63
+ properties, required_keys = @properties.to_swagger_doc(**user_options)
67
64
  schema[:properties] = properties unless properties.empty?
68
65
  schema[:required] = required_keys unless required_keys.empty?
69
66
  schema
@@ -76,6 +73,22 @@ module Meta
76
73
  def locked_exclude
77
74
  locked_options && locked_options[:exclude]
78
75
  end
76
+
77
+ private
78
+
79
+ def filter_internal(object_value, user_options)
80
+ @properties.filter(object_value, user_options)
81
+ end
82
+
83
+ def merge_user_options(user_options, locked_options)
84
+ user_options.merge(locked_options) do |key, user_value, locked_value|
85
+ if key == :scope
86
+ user_value + locked_value
87
+ else
88
+ locked_value
89
+ end
90
+ end
91
+ end
79
92
  end
80
93
  end
81
94
  end