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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/lib/meta/application/execution.rb +19 -49
- data/lib/meta/application/metadata.rb +65 -5
- data/lib/meta/application/route.rb +4 -26
- data/lib/meta/config.rb +2 -2
- data/lib/meta/json_schema/builders/schema_builder_tool.rb +2 -1
- data/lib/meta/json_schema/schemas/array_schema.rb +13 -14
- data/lib/meta/json_schema/schemas/base_schema.rb +101 -36
- data/lib/meta/json_schema/schemas/object_schema.rb +22 -9
- data/lib/meta/json_schema/schemas/properties.rb +20 -74
- data/lib/meta/json_schema/schemas/scoping_schema.rb +41 -0
- data/lib/meta/json_schema/schemas/staging_schema.rb +58 -0
- data/lib/meta/json_schema/schemas/unsupported_schema.rb +22 -0
- data/lib/meta/json_schema/support/errors.rb +3 -0
- data/lib/meta/json_schema/support/schema_options.rb +0 -9
- data/lib/meta/route_dsl/meta_builder.rb +2 -2
- data/lib/meta/swagger_doc.rb +1 -1
- data/lib/meta/utils/kwargs/builder.rb +2 -0
- data/lib/meta/utils/route_dsl_builders.rb +1 -0
- data/meta-api.gemspec +6 -5
- metadata +71 -111
- data/.autoenv.zsh +0 -1
- data/.gitignore +0 -7
- data/.rubocop.yml +0 -28
- data/Gemfile +0 -19
- data/Gemfile.lock +0 -68
- data/README.md +0 -166
- data/Rakefile +0 -3
- data/docs/Rails.md +0 -61
- data/docs//345/220/215/347/247/260/347/224/261/346/235/245.md +0 -7
- data/docs//345/246/202/344/275/225/350/264/241/347/214/256.md +0 -10
- data/docs//346/225/231/347/250/213.md +0 -1708
- data/docs//347/264/242/345/274/225.md +0 -183
- data/examples/lobster.rb +0 -71
- data/examples/rack_app/README.md +0 -3
- data/examples/rack_app/config.ru +0 -6
- data/examples/rack_app/hello.rb +0 -6
- data/examples/rack_app/timing.rb +0 -15
- data/examples/rails_app/.gitattributes +0 -5
- data/examples/rails_app/.gitignore +0 -23
- data/examples/rails_app/.rspec +0 -1
- data/examples/rails_app/.ruby-version +0 -1
- data/examples/rails_app/Gemfile +0 -29
- data/examples/rails_app/Gemfile.lock +0 -190
- data/examples/rails_app/README.md +0 -11
- data/examples/rails_app/Rakefile +0 -6
- data/examples/rails_app/app/controllers/application_controller.rb +0 -7
- data/examples/rails_app/app/controllers/concerns/.keep +0 -0
- data/examples/rails_app/app/controllers/data_controller.rb +0 -63
- data/examples/rails_app/app/controllers/swagger_controller.rb +0 -13
- data/examples/rails_app/app/models/concerns/.keep +0 -0
- data/examples/rails_app/bin/rails +0 -4
- data/examples/rails_app/bin/rake +0 -4
- data/examples/rails_app/bin/setup +0 -25
- data/examples/rails_app/config/application.rb +0 -39
- data/examples/rails_app/config/boot.rb +0 -3
- data/examples/rails_app/config/credentials.yml.enc +0 -1
- data/examples/rails_app/config/environment.rb +0 -5
- data/examples/rails_app/config/environments/development.rb +0 -51
- data/examples/rails_app/config/environments/production.rb +0 -65
- data/examples/rails_app/config/environments/test.rb +0 -50
- data/examples/rails_app/config/initializers/cors.rb +0 -16
- data/examples/rails_app/config/initializers/filter_parameter_logging.rb +0 -8
- data/examples/rails_app/config/initializers/inflections.rb +0 -16
- data/examples/rails_app/config/initializers/meta_rails_plugin.rb +0 -3
- data/examples/rails_app/config/locales/en.yml +0 -33
- data/examples/rails_app/config/puma.rb +0 -43
- data/examples/rails_app/config/routes.rb +0 -13
- data/examples/rails_app/config.ru +0 -6
- data/examples/rails_app/lib/tasks/.keep +0 -0
- data/examples/rails_app/log/.keep +0 -0
- data/examples/rails_app/public/robots.txt +0 -1
- data/examples/rails_app/spec/data_controller_spec.rb +0 -60
- data/examples/rails_app/spec/rails_helper.rb +0 -55
- data/examples/rails_app/spec/spec_helper.rb +0 -94
- data/examples/rails_app/spec/swagger_controller_spec.rb +0 -13
- data/examples/rails_app/tmp/.keep +0 -0
- data/examples/rails_app/tmp/pids/.keep +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3394a5e2d3b6dfd45f1478d8477426a4a947253d3252a050dc9c1d7cafd3c3d1
|
4
|
+
data.tar.gz: 98d12919b2adf824bec651ba5625076ddb7571962cdc66cee188b3b250fd1b24
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
8
|
-
attr_accessor :
|
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] =
|
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] =
|
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
|
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
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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
|
-
|
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 =
|
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].
|
112
|
-
final_value[key] =
|
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
|
-
|
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.
|
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
|
-
|
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
|
-
|
10
|
-
|
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
|
-
|
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 :
|
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
|
-
|
32
|
-
|
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 =
|
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
|
-
|
66
|
-
|
91
|
+
# 返回能够处理 scope 和 stage 的 schema(可以是 self),否则应返回 UnsupportedStageSchema 或 nil.
|
92
|
+
def find_schema(scope:, stage:)
|
93
|
+
staged(stage)&.scoped(scope)
|
67
94
|
end
|
68
95
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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
|
-
|
80
|
-
|
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
|
-
|
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
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
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
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
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
|
-
|
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(
|
61
|
-
|
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(
|
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
|