meta-api 0.0.8 → 0.1.0
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 +22 -0
- data/lib/meta/application/execution.rb +19 -49
- data/lib/meta/application/metadata.rb +68 -15
- data/lib/meta/application/responses.rb +32 -0
- data/lib/meta/application/route.rb +4 -26
- data/lib/meta/config.rb +2 -3
- data/lib/meta/entity.rb +2 -3
- data/lib/meta/json_schema/builders/object_schema_builder.rb +45 -40
- data/lib/meta/json_schema/builders/schema_builder_tool.rb +2 -5
- data/lib/meta/json_schema/schemas/array_schema.rb +24 -14
- data/lib/meta/json_schema/schemas/base_schema.rb +107 -37
- data/lib/meta/json_schema/schemas/named_properties.rb +37 -0
- data/lib/meta/json_schema/schemas/object_schema.rb +58 -20
- data/lib/meta/json_schema/schemas/properties.rb +27 -76
- data/lib/meta/json_schema/schemas/ref_schema.rb +36 -9
- data/lib/meta/json_schema/schemas/scoping_schema.rb +38 -0
- data/lib/meta/json_schema/schemas/staging_schema.rb +59 -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/json_object.rb +37 -0
- data/lib/meta/json_schema/support/schema_options.rb +0 -9
- data/lib/meta/json_schema/support/scope_matcher.rb +29 -0
- data/lib/meta/json_schema/support/type_converter.rb +3 -24
- data/lib/meta/route_dsl/meta_builder.rb +2 -2
- data/lib/meta/swagger_doc.rb +2 -25
- data/lib/meta/utils/kwargs/builder.rb +5 -3
- data/lib/meta/utils/route_dsl_builders.rb +2 -1
- data/meta-api.gemspec +6 -5
- metadata +75 -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
@@ -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,35 +80,55 @@ 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
|
+
def defined_scopes(stage:, defined_scopes_mapping:)
|
107
|
+
[]
|
108
|
+
end
|
109
|
+
|
110
|
+
# 执行 if: 选项,返回 true 或 false
|
111
|
+
def if?(user_options)
|
112
|
+
return true if options[:if].nil?
|
113
|
+
|
114
|
+
execution = user_options[:execution]
|
115
|
+
if execution
|
116
|
+
execution.instance_exec(&options[:if])
|
81
117
|
else
|
82
|
-
|
118
|
+
options[:if]&.call
|
83
119
|
end
|
84
120
|
end
|
85
121
|
|
122
|
+
def value?
|
123
|
+
options[:value] != nil
|
124
|
+
end
|
125
|
+
|
86
126
|
# 生成 Swagger 文档的 schema 格式。
|
87
127
|
#
|
88
128
|
# 选项:
|
89
129
|
# - stage: 传递 :param 或 :render
|
90
|
-
# -
|
130
|
+
# - schema_docs_mapping: 用于保存已经生成的 Schema
|
131
|
+
# - defined_scopes_mapping: 用于缓存已经定义的 scopes
|
91
132
|
# - presenter: 兼容 Grape 框架的实体类
|
92
133
|
def to_schema_doc(**user_options)
|
93
134
|
return Presenters.to_schema_doc(options[:presenter], options) if options[:presenter]
|
@@ -106,22 +147,51 @@ module Meta
|
|
106
147
|
|
107
148
|
private
|
108
149
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
end
|
150
|
+
def validate!(value, stage_options)
|
151
|
+
stage_options.each do |key, option|
|
152
|
+
validator = JsonSchema::Validators[key]
|
153
|
+
validator&.call(value, option, stage_options)
|
114
154
|
end
|
155
|
+
end
|
115
156
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
157
|
+
def resolve_value(value, user_options, value_proc, arguments)
|
158
|
+
if value_proc.lambda?
|
159
|
+
value_proc_params = arguments[0...value_proc.arity]
|
160
|
+
else
|
161
|
+
value_proc_params = arguments
|
162
|
+
end
|
163
|
+
if user_options[:execution]
|
164
|
+
user_options[:execution].instance_exec(*value_proc_params, &value_proc)
|
165
|
+
else
|
166
|
+
value_proc.call(*value_proc_params)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def resolve_default_value(default_resolver)
|
171
|
+
if default_resolver.respond_to?(:call)
|
172
|
+
default_resolver.call
|
173
|
+
elsif default_resolver.respond_to?(:dup)
|
174
|
+
default_resolver.dup
|
175
|
+
else
|
176
|
+
default_resolver
|
124
177
|
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def filter_internal(value, user_options)
|
181
|
+
value
|
182
|
+
end
|
183
|
+
|
184
|
+
def value_callback(user_options)
|
185
|
+
resolve_value(nil, user_options, options[:value], [user_options[:object_value], user_options[:user_data]])
|
186
|
+
end
|
187
|
+
|
188
|
+
def before_callback(value, user_options)
|
189
|
+
resolve_value(value, user_options, options[:before], [value, user_options[:object_value], user_options[:user_data]])
|
190
|
+
end
|
191
|
+
|
192
|
+
def after_callback(value, user_options)
|
193
|
+
resolve_value(value, user_options, options[:after], [value, user_options[:object_value], user_options[:user_data]])
|
194
|
+
end
|
125
195
|
end
|
126
196
|
end
|
127
197
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require_relative 'properties'
|
5
|
+
|
6
|
+
module Meta
|
7
|
+
module JsonSchema
|
8
|
+
class NamedProperties < Properties
|
9
|
+
attr_reader :schema_base_name
|
10
|
+
|
11
|
+
def initialize(properties, schema_base_name)
|
12
|
+
super(properties)
|
13
|
+
|
14
|
+
raise TypeError, "schema_name 必须是一个 String,当前是:#{schema_base_name.class}" unless schema_base_name.is_a?(String)
|
15
|
+
raise ArgumentError, 'schema_name 不能为 nil 或空字符串' if schema_base_name.nil? || schema_base_name.empty?
|
16
|
+
|
17
|
+
# 修正 base_name,确保其不包含 Entity 后缀
|
18
|
+
schema_base_name = schema_base_name.delete_suffix('Entity') if schema_base_name&.end_with?('Entity')
|
19
|
+
@schema_base_name = schema_base_name
|
20
|
+
end
|
21
|
+
|
22
|
+
def schema_name(stage)
|
23
|
+
if stage == :render
|
24
|
+
"#{schema_base_name}Entity"
|
25
|
+
elsif stage == :param
|
26
|
+
"#{schema_base_name}Params"
|
27
|
+
else
|
28
|
+
raise ArgumentError, "stage 必须是 :render 或 :param,当前是:#{stage}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def merge(other_properties)
|
33
|
+
raise UnsupportedError, 'NamedProperties 不支持 merge 操作'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -1,69 +1,91 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative '../../utils/kwargs/check'
|
4
|
+
require_relative 'named_properties'
|
4
5
|
|
5
6
|
module Meta
|
6
7
|
module JsonSchema
|
7
8
|
class ObjectSchema < BaseSchema
|
8
|
-
attr_reader :properties
|
9
|
+
attr_reader :properties
|
10
|
+
# 只有 ObjectSchema 对象才有 locked_options,因为 locked_options 多是用来锁定属性的行为的,包括:
|
11
|
+
# scope:、discard_missing:、exclude: 等
|
12
|
+
attr_reader :locked_options
|
9
13
|
|
14
|
+
# stage 和 scope 选项在两个 CHECKER 下都用到了
|
10
15
|
USER_OPTIONS_CHECKER = Utils::KeywordArgs::Builder.build do
|
11
|
-
|
12
|
-
|
16
|
+
key :stage
|
13
17
|
key :scope, normalizer: ->(value) {
|
14
18
|
raise ArgumentError, 'scope 选项不可传递 nil' if value.nil?
|
15
19
|
value = [value] unless value.is_a?(Array)
|
16
20
|
value
|
17
21
|
}
|
22
|
+
key :discard_missing, :exclude, :extra_properties, :type_conversion, :validation
|
23
|
+
key :execution, :user_data, :object_value
|
18
24
|
end
|
25
|
+
TO_SCHEMA_DOC_CHECKER = Utils::KeywordArgs::Builder.build do
|
26
|
+
key :stage
|
27
|
+
key :scope, normalizer: ->(value) {
|
28
|
+
raise ArgumentError, 'scope 选项不可传递 nil' if value.nil?
|
29
|
+
value = [value] unless value.is_a?(Array)
|
30
|
+
value
|
31
|
+
}
|
32
|
+
key :schema_docs_mapping, :defined_scopes_mapping
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize(properties:, options: {}, locked_options: {})
|
36
|
+
raise ArgumentError, 'properties 必须是 Properties 实例' unless properties.is_a?(Properties)
|
19
37
|
|
20
|
-
def initialize(properties: nil, options: {}, locked_options: {}, schema_name_resolver: proc { nil })
|
21
38
|
super(options)
|
22
39
|
|
23
40
|
@properties = properties || Properties.new({}) # property 包含 stage,stage 包含 scope、schema
|
24
41
|
@properties = Properties.new(@properties) if @properties.is_a?(Hash)
|
25
42
|
@locked_options = USER_OPTIONS_CHECKER.check(locked_options || {})
|
26
|
-
@schema_name_resolver = schema_name_resolver || proc { nil }
|
27
43
|
end
|
28
44
|
|
29
45
|
# 复制一个新的 ObjectSchema,只有 options 不同
|
30
46
|
def dup(options)
|
47
|
+
raise UnsupportedError, 'dup 不应该再执行了'
|
48
|
+
|
31
49
|
self.class.new(
|
32
50
|
properties: properties,
|
33
51
|
options: options,
|
34
52
|
locked_options: locked_options,
|
35
|
-
schema_name_resolver: @schema_name_resolver
|
36
53
|
)
|
37
54
|
end
|
38
55
|
|
39
56
|
def filter(object_value, user_options = {})
|
40
57
|
# 合并 user_options
|
41
|
-
user_options = user_options.merge(locked_options) if locked_options
|
42
58
|
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)
|
59
|
+
user_options = self.class.merge_user_options(user_options, locked_options) if locked_options
|
60
|
+
super
|
48
61
|
end
|
49
62
|
|
50
|
-
|
51
|
-
|
52
|
-
ObjectSchema.new(properties: self.properties.merge(properties))
|
63
|
+
def naming?
|
64
|
+
properties.is_a?(NamedProperties)
|
53
65
|
end
|
54
66
|
|
55
|
-
def resolve_name(stage)
|
67
|
+
def resolve_name(stage, user_scopes, defined_scopes)
|
68
|
+
raise ArgumentError, 'stage 不能为 nil' if stage.nil?
|
69
|
+
|
70
|
+
# 先合成外面传进来的 scope
|
56
71
|
locked_scopes = (locked_options || {})[:scope] || []
|
57
|
-
|
72
|
+
user_scopes = (user_scopes + locked_scopes).uniq
|
73
|
+
scopes = user_scopes & defined_scopes
|
74
|
+
|
75
|
+
# 再根据 stage 和 scope 生成为当前的 Schema 生成一个合适的名称,要求保证唯一性
|
76
|
+
schema_name = properties.schema_name(stage)
|
77
|
+
schema_name += "__#{scopes.join('__')}" unless scopes.empty?
|
78
|
+
schema_name
|
58
79
|
end
|
59
80
|
|
60
|
-
def to_schema_doc(
|
61
|
-
|
81
|
+
def to_schema_doc(user_options = {})
|
82
|
+
user_options = TO_SCHEMA_DOC_CHECKER.check(user_options)
|
83
|
+
user_options = self.class.merge_user_options(user_options, locked_options) if locked_options
|
62
84
|
|
63
85
|
schema = { type: 'object' }
|
64
86
|
schema[:description] = options[:description] if options[:description]
|
65
87
|
|
66
|
-
properties, required_keys = @properties.to_swagger_doc(
|
88
|
+
properties, required_keys = @properties.to_swagger_doc(**user_options)
|
67
89
|
schema[:properties] = properties unless properties.empty?
|
68
90
|
schema[:required] = required_keys unless required_keys.empty?
|
69
91
|
schema
|
@@ -76,6 +98,22 @@ module Meta
|
|
76
98
|
def locked_exclude
|
77
99
|
locked_options && locked_options[:exclude]
|
78
100
|
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def filter_internal(object_value, user_options)
|
105
|
+
@properties.filter(object_value, user_options)
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.merge_user_options(user_options, locked_options)
|
109
|
+
user_options.merge(locked_options) do |key, user_value, locked_value|
|
110
|
+
if key == :scope
|
111
|
+
user_value + locked_value
|
112
|
+
else
|
113
|
+
locked_value
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
79
117
|
end
|
80
118
|
end
|
81
119
|
end
|
@@ -5,85 +5,33 @@ require 'forwardable'
|
|
5
5
|
module Meta
|
6
6
|
module JsonSchema
|
7
7
|
class Properties
|
8
|
-
class StagingProperty
|
9
|
-
def initialize(param:, render:, none:)
|
10
|
-
@param_stage = param
|
11
|
-
@render_stage = render
|
12
|
-
@none_stage = none
|
13
|
-
end
|
14
|
-
|
15
|
-
def stage(stage = nil)
|
16
|
-
case stage
|
17
|
-
when :param
|
18
|
-
@param_stage
|
19
|
-
when :render
|
20
|
-
@render_stage
|
21
|
-
else
|
22
|
-
@none_stage
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def stage?(stage)
|
27
|
-
stage(stage) != nil
|
28
|
-
end
|
29
|
-
|
30
|
-
def schema(stage = nil)
|
31
|
-
stage(stage).schema
|
32
|
-
end
|
33
|
-
|
34
|
-
def self.build(options, build_schema)
|
35
|
-
param_opts, render_opts, common_opts = SchemaOptions.divide_to_param_and_render(options)
|
36
|
-
|
37
|
-
StagingProperty.new(
|
38
|
-
param: options[:param] === false ? nil : ScopingProperty.build(param_opts, build_schema),
|
39
|
-
render: options[:render] === false ? nil : ScopingProperty.build(render_opts, build_schema),
|
40
|
-
none: ScopingProperty.build(common_opts, build_schema)
|
41
|
-
)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
class ScopingProperty
|
46
|
-
attr_reader :scope, :schema
|
47
|
-
|
48
|
-
def initialize(scope: :all, schema:)
|
49
|
-
scope = :all if scope.nil?
|
50
|
-
scope = [scope] unless scope.is_a?(Array) || scope == :all
|
51
|
-
if scope.is_a?(Array) && scope.any? { |s| s.is_a?(Integer) }
|
52
|
-
raise ArgumentError, 'scope 选项内不可传递数字'
|
53
|
-
end
|
54
|
-
@scope = scope
|
55
|
-
|
56
|
-
@schema = schema
|
57
|
-
end
|
58
|
-
|
59
|
-
def self.build(options, build_schema)
|
60
|
-
options = options.dup
|
61
|
-
scope = options.delete(:scope)
|
62
|
-
schema = build_schema.call(options)
|
63
|
-
ScopingProperty.new(scope: scope, schema: schema)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
8
|
extend Forwardable
|
68
9
|
|
69
10
|
def initialize(properties)
|
70
11
|
@properties = properties
|
71
12
|
end
|
72
13
|
|
14
|
+
# user_options 包括 stage, scope, extra_properties, discard_missing, exclude、execution、user_data
|
73
15
|
def filter(object_value, user_options = {})
|
16
|
+
# 首先,要将 object_value 转化为 ObjectWrapper
|
17
|
+
object_value = JsonObject.wrap(object_value)
|
18
|
+
|
74
19
|
# 第一步,根据 user_options[:scope] 需要过滤一些字段
|
75
20
|
stage = user_options[:stage]
|
76
21
|
# 传递一个数字;因为 scope 不能包含数字,这里传递一个数字,使得凡是配置 scope 的属性都会被过滤
|
77
22
|
user_scope = user_options[:scope] || [0]
|
78
23
|
exclude = user_options.delete(:exclude) # 这里删除 exclude 选项,不要传递给下一层
|
79
24
|
properties = filter_by(stage: stage, user_scope: user_scope)
|
80
|
-
filtered_properties = properties.filter do |name,
|
25
|
+
filtered_properties = properties.filter do |name, property_schema|
|
81
26
|
# 通过 discard_missing 过滤
|
82
27
|
next false if user_options[:discard_missing] && !object_value.key?(name.to_s)
|
83
28
|
|
84
29
|
# 通过 locked_exclude 选项过滤
|
85
30
|
next false if exclude && exclude.include?(name)
|
86
31
|
|
32
|
+
# 通过 if 选项过滤
|
33
|
+
next false unless property_schema.if?(user_options)
|
34
|
+
|
87
35
|
# 默认返回 true
|
88
36
|
next true
|
89
37
|
end
|
@@ -91,12 +39,14 @@ module Meta
|
|
91
39
|
# 第二步,递归过滤每一个属性
|
92
40
|
object = {}
|
93
41
|
errors = {}
|
42
|
+
cause = nil
|
94
43
|
filtered_properties.each do |name, property_schema|
|
95
44
|
value = resolve_property_value(object_value, name, property_schema)
|
96
45
|
|
97
46
|
begin
|
98
47
|
object[name] = property_schema.filter(value, **user_options, object_value: object_value)
|
99
48
|
rescue JsonSchema::ValidationErrors => e
|
49
|
+
cause = e.cause || e if cause.nil? # 将第一次出现的错误作为 cause
|
100
50
|
errors.merge! e.prepend_root(name).errors
|
101
51
|
end
|
102
52
|
end.to_h
|
@@ -108,54 +58,55 @@ module Meta
|
|
108
58
|
|
109
59
|
if errors.empty?
|
110
60
|
object
|
61
|
+
elsif cause
|
62
|
+
begin
|
63
|
+
raise cause
|
64
|
+
rescue
|
65
|
+
raise JsonSchema::ValidationErrors.new(errors)
|
66
|
+
end
|
111
67
|
else
|
112
68
|
raise JsonSchema::ValidationErrors.new(errors)
|
113
69
|
end
|
114
70
|
end
|
115
71
|
|
116
|
-
|
72
|
+
# user_options 包括 stage, scope, schema_docs_mapping, defined_scopes_mapping
|
73
|
+
def to_swagger_doc(scope: [], stage: nil, **user_options)
|
74
|
+
locked_scopes = scope
|
117
75
|
properties = filter_by(stage: stage, user_scope: locked_scopes)
|
118
76
|
required_keys = properties.filter do |key, property_schema|
|
119
77
|
property_schema.options[:required]
|
120
78
|
end.keys
|
121
79
|
properties = properties.transform_values do |property_schema |
|
122
|
-
property_schema.to_schema_doc(stage: stage, **user_options)
|
80
|
+
property_schema.to_schema_doc(stage: stage, scope: scope, **user_options)
|
123
81
|
end
|
124
82
|
[properties, required_keys]
|
125
83
|
end
|
126
84
|
|
127
85
|
# 程序中有些地方用到了这三个方法
|
128
|
-
def_delegators :@properties, :empty?, :key?, :[]
|
86
|
+
def_delegators :@properties, :empty?, :key?, :[], :each
|
129
87
|
|
130
88
|
def merge(properties)
|
131
89
|
self.class.new(@properties.merge(properties.instance_eval { @properties }))
|
132
90
|
end
|
133
91
|
|
134
92
|
def self.build_property(*args)
|
135
|
-
|
93
|
+
StagingSchema.build_from_options(*args)
|
136
94
|
end
|
137
95
|
|
138
96
|
private
|
139
97
|
|
140
98
|
def filter_by(stage:, user_scope: false)
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
# 通过 user_scope 过滤
|
147
|
-
next true if property.scope == :all
|
148
|
-
(user_scope - property.scope).empty? # user_scope 应被消耗殆尽
|
149
|
-
end
|
150
|
-
properties.transform_values do |property|
|
151
|
-
property.stage(stage).schema
|
99
|
+
@properties.transform_values do |property|
|
100
|
+
property.find_schema(stage: stage, scope: user_scope)
|
101
|
+
end.filter do |name, schema|
|
102
|
+
schema.filter?
|
152
103
|
end
|
153
104
|
end
|
154
105
|
|
155
106
|
def resolve_property_value(object_value, name, property_schema)
|
156
107
|
if property_schema.value?
|
157
108
|
nil
|
158
|
-
elsif object_value.is_a?(Hash) || object_value.is_a?(
|
109
|
+
elsif object_value.is_a?(Hash) || object_value.is_a?(JsonObject)
|
159
110
|
object_value.key?(name.to_s) ? object_value[name.to_s] : object_value[name.to_sym]
|
160
111
|
else
|
161
112
|
raise "不应该还有其他类型了,已经在类型转换中将其转换为 Meta::JsonSchema::ObjectWrapper 了"
|
@@ -5,31 +5,58 @@ require_relative 'base_schema'
|
|
5
5
|
module Meta
|
6
6
|
module JsonSchema
|
7
7
|
class RefSchema < BaseSchema
|
8
|
-
attr_reader :
|
8
|
+
attr_reader :object_schema
|
9
|
+
|
10
|
+
def initialize(object_schema, options = {})
|
11
|
+
raise ArgumentError, 'object_schema 必须是一个 ObjectSchema' unless object_schema.is_a?(ObjectSchema)
|
9
12
|
|
10
|
-
def initialize(schema, options = {})
|
11
13
|
super(options)
|
12
|
-
@
|
14
|
+
@object_schema = object_schema
|
13
15
|
end
|
14
16
|
|
15
17
|
def filter(value, user_options = {})
|
16
18
|
value = super
|
17
|
-
|
19
|
+
object_schema.filter(value, user_options)
|
18
20
|
end
|
19
21
|
|
20
22
|
def to_schema_doc(user_options)
|
21
|
-
|
23
|
+
raise '引用的 ObjectSchema 没有包含命名逻辑,无法生成文档' unless object_schema.naming?
|
22
24
|
|
23
|
-
#
|
24
|
-
|
25
|
+
# 首先,要求出 defined_scopes
|
26
|
+
defined_scopes = self.defined_scopes(stage: user_options[:stage], defined_scopes_mapping: user_options[:defined_scopes_mapping])
|
27
|
+
# 然后,求出 schema_name
|
28
|
+
schema_name = object_schema.resolve_name(user_options[:stage], user_options[:scope], defined_scopes)
|
29
|
+
# 接着将 Schema 写进 schemas 选项中去
|
30
|
+
schema_components = user_options[:schema_docs_mapping] || {}
|
25
31
|
unless schema_components.key?(schema_name)
|
26
32
|
schema_components[schema_name] = nil # 首先设置 schemas 防止出现无限循环
|
27
|
-
schema_components[schema_name] =
|
33
|
+
schema_components[schema_name] = object_schema.to_schema_doc(**user_options) # 原地修改 schemas,无妨
|
28
34
|
end
|
29
35
|
|
30
|
-
#
|
36
|
+
# 最后,返回这个 $ref 结构
|
31
37
|
{ '$ref': "#/components/schemas/#{schema_name}" }
|
32
38
|
end
|
39
|
+
|
40
|
+
def defined_scopes(stage:, defined_scopes_mapping:)
|
41
|
+
defined_scopes_mapping ||= {}
|
42
|
+
|
43
|
+
schema_name = object_schema.properties.schema_name(stage)
|
44
|
+
return defined_scopes_mapping[schema_name] if defined_scopes_mapping.key?(schema_name)
|
45
|
+
|
46
|
+
defined_scopes_mapping[schema_name] = []
|
47
|
+
defined_scopes = object_schema.properties.each.map do |name, property|
|
48
|
+
property.defined_scopes(stage: stage, defined_scopes_mapping: defined_scopes_mapping)
|
49
|
+
end.flatten.uniq.sort
|
50
|
+
defined_scopes_mapping[schema_name] = defined_scopes
|
51
|
+
defined_scopes
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# # TODO: 这种带有组合方式的 Schema,让我联想到,每次 BaseSchema 新增一个方法都要在子 Schema 中加一遍,很烦!
|
57
|
+
# def defined_scopes
|
58
|
+
# schema.defined_scopes
|
59
|
+
# end
|
33
60
|
end
|
34
61
|
end
|
35
62
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../support/scope_matcher'
|
4
|
+
|
5
|
+
module Meta
|
6
|
+
module JsonSchema
|
7
|
+
class ScopingSchema < BaseSchema
|
8
|
+
attr_reader :scope_matcher, :schema
|
9
|
+
|
10
|
+
def initialize(scope_matcher_options: , schema:)
|
11
|
+
@scope_matcher = ScopeMatcher.new(scope_matcher_options)
|
12
|
+
@schema = schema
|
13
|
+
end
|
14
|
+
|
15
|
+
def scoped(user_scopes)
|
16
|
+
@scope_matcher.match?(user_scopes) ? schema : unsupported_schema(user_scopes)
|
17
|
+
end
|
18
|
+
|
19
|
+
def defined_scopes(**kwargs)
|
20
|
+
scope_matcher.defined_scopes
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def unsupported_schema(user_scopes)
|
26
|
+
UnsupportedSchema.new(:scope, user_scopes)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.build_from_options(options, build_schema)
|
30
|
+
options = options.dup
|
31
|
+
scope_matcher_options = options.delete(:scope)
|
32
|
+
schema = build_schema.call(options)
|
33
|
+
schema = ScopingSchema.new(scope_matcher_options: scope_matcher_options, schema: schema) if scope_matcher_options
|
34
|
+
schema
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|