meta-api 0.0.7 → 0.0.9

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 (80) 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 +14 -14
  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/route_dsl/uniformed_params_builder.rb +5 -5
  19. data/lib/meta/swagger_doc.rb +1 -1
  20. data/lib/meta/utils/kwargs/builder.rb +2 -0
  21. data/lib/meta/utils/route_dsl_builders.rb +1 -0
  22. data/meta-api.gemspec +6 -5
  23. metadata +71 -111
  24. data/.autoenv.zsh +0 -1
  25. data/.gitignore +0 -7
  26. data/.rubocop.yml +0 -28
  27. data/Gemfile +0 -18
  28. data/Gemfile.lock +0 -66
  29. data/README.md +0 -166
  30. data/Rakefile +0 -3
  31. data/docs/Rails.md +0 -61
  32. data/docs//345/220/215/347/247/260/347/224/261/346/235/245.md +0 -7
  33. data/docs//345/246/202/344/275/225/350/264/241/347/214/256.md +0 -10
  34. data/docs//346/225/231/347/250/213.md +0 -1647
  35. data/docs//347/264/242/345/274/225.md +0 -183
  36. data/examples/lobster.rb +0 -71
  37. data/examples/rack_app/README.md +0 -3
  38. data/examples/rack_app/config.ru +0 -6
  39. data/examples/rack_app/hello.rb +0 -6
  40. data/examples/rack_app/timing.rb +0 -15
  41. data/examples/rails_app/.gitattributes +0 -5
  42. data/examples/rails_app/.gitignore +0 -23
  43. data/examples/rails_app/.rspec +0 -1
  44. data/examples/rails_app/.ruby-version +0 -1
  45. data/examples/rails_app/Gemfile +0 -29
  46. data/examples/rails_app/Gemfile.lock +0 -190
  47. data/examples/rails_app/README.md +0 -11
  48. data/examples/rails_app/Rakefile +0 -6
  49. data/examples/rails_app/app/controllers/application_controller.rb +0 -7
  50. data/examples/rails_app/app/controllers/concerns/.keep +0 -0
  51. data/examples/rails_app/app/controllers/data_controller.rb +0 -63
  52. data/examples/rails_app/app/controllers/swagger_controller.rb +0 -13
  53. data/examples/rails_app/app/models/concerns/.keep +0 -0
  54. data/examples/rails_app/bin/rails +0 -4
  55. data/examples/rails_app/bin/rake +0 -4
  56. data/examples/rails_app/bin/setup +0 -25
  57. data/examples/rails_app/config/application.rb +0 -39
  58. data/examples/rails_app/config/boot.rb +0 -3
  59. data/examples/rails_app/config/credentials.yml.enc +0 -1
  60. data/examples/rails_app/config/environment.rb +0 -5
  61. data/examples/rails_app/config/environments/development.rb +0 -51
  62. data/examples/rails_app/config/environments/production.rb +0 -65
  63. data/examples/rails_app/config/environments/test.rb +0 -50
  64. data/examples/rails_app/config/initializers/cors.rb +0 -16
  65. data/examples/rails_app/config/initializers/filter_parameter_logging.rb +0 -8
  66. data/examples/rails_app/config/initializers/inflections.rb +0 -16
  67. data/examples/rails_app/config/initializers/meta_rails_plugin.rb +0 -3
  68. data/examples/rails_app/config/locales/en.yml +0 -33
  69. data/examples/rails_app/config/puma.rb +0 -43
  70. data/examples/rails_app/config/routes.rb +0 -13
  71. data/examples/rails_app/config.ru +0 -6
  72. data/examples/rails_app/lib/tasks/.keep +0 -0
  73. data/examples/rails_app/log/.keep +0 -0
  74. data/examples/rails_app/public/robots.txt +0 -1
  75. data/examples/rails_app/spec/data_controller_spec.rb +0 -60
  76. data/examples/rails_app/spec/rails_helper.rb +0 -55
  77. data/examples/rails_app/spec/spec_helper.rb +0 -94
  78. data/examples/rails_app/spec/swagger_controller_spec.rb +0 -13
  79. data/examples/rails_app/tmp/.keep +0 -0
  80. data/examples/rails_app/tmp/pids/.keep +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c114ab0d95b902ecefcd5e720f0c38fbcc420b2839194842c2974d2d073505fd
4
- data.tar.gz: 994b7e975626f9907467edfe4283875aa113957edc860b74ed1b29e27560f196
3
+ metadata.gz: 3394a5e2d3b6dfd45f1478d8477426a4a947253d3252a050dc9c1d7cafd3c3d1
4
+ data.tar.gz: 98d12919b2adf824bec651ba5625076ddb7571962cdc66cee188b3b250fd1b24
5
5
  SHA512:
6
- metadata.gz: 6b62b24879f309c6cc67e45bc9bfd250b47ec8ad6c25f5fe1e9f5098b970ff8466f239b8799b4e653ebdc5568cbbb87fdd7c086703a99ef8525945ce24c44617
7
- data.tar.gz: edec25f4ea37588611af7dceca41a443c5cb6d83586096f51f9f27ce75fdf290ec7085e4f27bf87043779e1ed289f7cbaa0a7e9568839e2ed206eef1d7d4f4b7
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
@@ -1,22 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Meta
4
- class Config
5
- attr_accessor :default_locked_scope,
6
- :json_schema_user_options,
7
- :json_schema_param_stage_options,
8
- :json_schema_render_stage_options
3
+ require 'hash_to_struct'
9
4
 
10
- def initialize
11
- @default_locked_scope = nil
12
- @json_schema_user_options = {}
13
- @json_schema_param_stage_options = {}
14
- @json_schema_render_stage_options = {}
15
- end
16
- end
5
+ module Meta
6
+ DEFAULT_OPTIONS = {
7
+ default_locked_scope: nil,
8
+ json_schema_user_options: {},
9
+ json_schema_param_stage_user_options: {},
10
+ json_schema_render_stage_user_options: {}
11
+ }
17
12
 
18
- @config = Config.new
19
13
  class << self
20
14
  attr_reader :config
15
+
16
+ def initialize_configuration(*options_list)
17
+ final_options = options_list.reduce(DEFAULT_OPTIONS, :deep_merge)
18
+ @config = HashToStruct.struct(final_options)
19
+ end
21
20
  end
22
21
  end
22
+ Meta.initialize_configuration
@@ -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