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.
- 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 +14 -14
- 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/route_dsl/uniformed_params_builder.rb +5 -5
- 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 -18
- data/Gemfile.lock +0 -66
- 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 -1647
- 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
@@ -1,22 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
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
|