meta-api 0.1.0 → 0.1.2
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/config/locales/zh-CN.yml +1 -1
- data/lib/meta/api.rb +1 -0
- data/lib/meta/application/metadata.rb +2 -4
- data/lib/meta/application/route.rb +2 -1
- data/lib/meta/entity.rb +8 -34
- data/lib/meta/json_schema/builders/object_schema_builder.rb +104 -27
- data/lib/meta/json_schema/builders/schema_builder_tool.rb +1 -3
- data/lib/meta/json_schema/schemas/base_schema.rb +13 -8
- data/lib/meta/json_schema/schemas/object_schema.rb +61 -36
- data/lib/meta/json_schema/schemas/properties.rb +16 -9
- data/lib/meta/json_schema/schemas/ref_schema.rb +7 -3
- data/lib/meta/json_schema/schemas/scoping_schema.rb +10 -4
- data/lib/meta/json_schema/support/scope_matcher.rb +8 -5
- data/lib/meta/json_schema/support/validators.rb +2 -2
- data/lib/meta/route_dsl/meta_builder.rb +7 -1
- data/lib/meta/route_dsl/parameters_builder.rb +1 -0
- data/lib/meta/route_dsl/route_builder.rb +1 -1
- data/lib/meta/scope/base.rb +152 -0
- data/lib/meta/scope/utils.rb +56 -0
- data/lib/meta/utils/kwargs/builder.rb +1 -1
- data/meta-api.gemspec +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 55ec631feb0038911007c4b1de6ca26fdf595ea84c41eecac046e417ff90a2e2
|
4
|
+
data.tar.gz: e384752c6ce459cb38adb4123078f10ad835c8f75a73256fddb1af02a8bac3b0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 19c21dc3c99bebf4adfaea519ffb44d901481c8b88349b1b87677e3155a1c5e75e4c1e639596f589a28c32192e3ed10330f7d3b85dbc4e7238af73b0d8c996ef
|
7
|
+
data.tar.gz: 3903b5879a3d4dc1092a32b41aa17891434363089a48225acef22267ad6a56416ba7576c6de6ebb4bb8bbb50ebe2bebc7cb5592cf3265bccafe0e3c98e3c670c
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,19 @@
|
|
1
1
|
# 更新日志
|
2
2
|
|
3
|
+
## 0.1.2(2024 年 6 月 5 日)
|
4
|
+
|
5
|
+
1. 实现新的基于常量的 Scope 场景化模式。
|
6
|
+
2. 属性的 `scope:` 选项如果传递的是字符串数组,则恢复为 `或` 模式。
|
7
|
+
3. 取消 HTTP method 自动生成对应的 Scope常量。
|
8
|
+
4. 修复 `locked(discard_missing: true)` 时生成文档报错。
|
9
|
+
5. 属性新增 `enum:` 选项,`allowable:` 作为其别名存在。
|
10
|
+
|
11
|
+
## 0.1.1(2024 年 6 月 1 日)
|
12
|
+
|
13
|
+
1. 添加 `Meta::Entity.with_common_options` 方法,用于更有效地组织字段。
|
14
|
+
2. 临时性地添加 `Meta::Entity.merge` 方法,作为合并其他的实体的暂时性实现。
|
15
|
+
3. scope 分为全局 scope 和局部 scope.
|
16
|
+
|
3
17
|
## 0.1.0(2023 年 8 月 5 日)
|
4
18
|
|
5
19
|
1. 删除 `on:` 选项。
|
data/config/locales/zh-CN.yml
CHANGED
@@ -6,7 +6,7 @@ zh-CN:
|
|
6
6
|
errors:
|
7
7
|
required: "未提供"
|
8
8
|
format: "格式不正确"
|
9
|
-
|
9
|
+
enum: "不在允许的值范围内,允许的值包括 [%{allowable_values}]"
|
10
10
|
type_convert:
|
11
11
|
basic: "类型转化失败,期望得到一个 `%{target_type}` 类型,但值 `%{value}` 无法转化"
|
12
12
|
array: "转化为数组类型时期望对象拥有 `to_a` 方法"
|
data/lib/meta/api.rb
CHANGED
@@ -13,23 +13,21 @@ module Meta
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def parse_request_body(execution, discard_missing: false)
|
16
|
-
method = execution.request.request_method.downcase.to_sym
|
17
16
|
request_body.filter(
|
18
17
|
execution.params(:raw),
|
19
18
|
**Meta.config.json_schema_user_options,
|
20
19
|
**Meta.config.json_schema_param_stage_user_options,
|
21
|
-
**{ execution: execution, stage: :param, scope: @scope
|
20
|
+
**{ execution: execution, stage: :param, scope: @scope, discard_missing: discard_missing }.compact
|
22
21
|
)
|
23
22
|
rescue JsonSchema::ValidationErrors => e
|
24
23
|
raise Errors::ParameterInvalid.new(e.errors)
|
25
24
|
end
|
26
25
|
|
27
26
|
def render_entity(execution, entity_schema, value, user_options)
|
28
|
-
method = execution.request.request_method.downcase.to_sym
|
29
27
|
entity_schema.filter(value,
|
30
28
|
**Meta.config.json_schema_user_options,
|
31
29
|
**Meta.config.json_schema_render_stage_user_options,
|
32
|
-
**{ execution: execution, stage: :render, scope: @scope
|
30
|
+
**{ execution: execution, stage: :render, scope: @scope }.compact,
|
33
31
|
**user_options,
|
34
32
|
)
|
35
33
|
end
|
data/lib/meta/entity.rb
CHANGED
@@ -14,43 +14,17 @@ module Meta
|
|
14
14
|
def inherited(base)
|
15
15
|
base.instance_eval do
|
16
16
|
@schema_builder = JsonSchema::ObjectSchemaBuilder.new
|
17
|
-
|
17
|
+
if self.name
|
18
|
+
mod_names = self.name.split("::")
|
19
|
+
mod_names.shift if mod_names.first == "Entities"
|
20
|
+
schema_name = mod_names.join('_')
|
21
|
+
@schema_builder.schema_name(schema_name)
|
22
|
+
end
|
18
23
|
end
|
19
24
|
end
|
20
25
|
|
21
|
-
|
22
|
-
|
23
|
-
def method_missing(method, *args)
|
24
|
-
if method =~ /^lock_(\w+)$/
|
25
|
-
schema_builder.send(method, *args)
|
26
|
-
else
|
27
|
-
super
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
private
|
32
|
-
|
33
|
-
# TODO: 不需要在 Entity 内自动生成名称了,交给 ObjectSchema::Naming 去做吧
|
34
|
-
def generate_schema_name(stage, locked_scopes)
|
35
|
-
# 匿名类不考虑自动生成名称
|
36
|
-
return nil unless self.name
|
37
|
-
|
38
|
-
schema_name = self.name.gsub('::', '_')
|
39
|
-
schema_name = schema_name.delete_suffix('Entity') unless schema_name == 'Entity'
|
40
|
-
|
41
|
-
# 先考虑 stage
|
42
|
-
case stage
|
43
|
-
when :param
|
44
|
-
schema_name += 'Params'
|
45
|
-
when :render
|
46
|
-
schema_name += 'Entity'
|
47
|
-
end
|
48
|
-
|
49
|
-
# 再考虑 locked_scope
|
50
|
-
scope_suffix = locked_scopes.join('_')
|
51
|
-
schema_name = "#{schema_name}_#{scope_suffix}" unless scope_suffix.empty?
|
52
|
-
|
53
|
-
schema_name
|
26
|
+
def method_missing(method, *args, **kwargs, &)
|
27
|
+
schema_builder.send(method, *args, **kwargs, &)
|
54
28
|
end
|
55
29
|
end
|
56
30
|
end
|
@@ -5,6 +5,8 @@ require_relative '../schemas/properties'
|
|
5
5
|
module Meta
|
6
6
|
module JsonSchema
|
7
7
|
class ObjectSchemaBuilder
|
8
|
+
extend Forwardable
|
9
|
+
|
8
10
|
module LockedMethodAlias
|
9
11
|
# 我在这里说明一下 lock_scope 的逻辑。
|
10
12
|
# 1. lock_scope 实际上是将 scope 传递到当前的 ObjectSchema 和它的子 Schema 中。
|
@@ -29,10 +31,65 @@ module Meta
|
|
29
31
|
end
|
30
32
|
end
|
31
33
|
|
34
|
+
class Locked
|
35
|
+
attr_reader :object_schema_builder, :locked_options
|
36
|
+
|
37
|
+
# locked_options 是用户传递的参数,这个参数会被合并到 object_schema_builder 的 locked_options 中。
|
38
|
+
def initialize(builder, scope: nil, discard_missing: nil, exclude: nil)
|
39
|
+
@object_schema_builder = builder
|
40
|
+
@locked_options = ObjectSchema::USER_OPTIONS_CHECKER.check({ scope: scope, discard_missing: discard_missing, exclude: exclude }.compact)
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_schema
|
44
|
+
object_schema_builder.to_schema(locked_options)
|
45
|
+
end
|
46
|
+
|
47
|
+
def locked(options)
|
48
|
+
options = ObjectSchema::USER_OPTIONS_CHECKER.check(options)
|
49
|
+
options = ObjectSchema.merge_user_options(locked_options, options)
|
50
|
+
Locked.new(self.object_schema_builder, **options)
|
51
|
+
end
|
52
|
+
include LockedMethodAlias
|
53
|
+
end
|
54
|
+
|
55
|
+
class WithCommonOptions
|
56
|
+
attr_reader :object_schema_builder, :common_options
|
57
|
+
|
58
|
+
def initialize(builder, common_options, &)
|
59
|
+
@object_schema_builder = builder
|
60
|
+
@common_options = common_options
|
61
|
+
|
62
|
+
instance_exec(&) if block_given?
|
63
|
+
end
|
64
|
+
|
65
|
+
def property(name, options = {}, &block)
|
66
|
+
options = merge_options(options)
|
67
|
+
object_schema_builder.property(name, options, &block)
|
68
|
+
end
|
69
|
+
|
70
|
+
def merge_options(options)
|
71
|
+
self.class.merge_options(common_options, options)
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.merge_options(common_options, options)
|
75
|
+
common_options.merge(options) do |key, oldVal, newVal|
|
76
|
+
if key == :scope
|
77
|
+
# 合并 common_options 和 options 中的 scope 选项
|
78
|
+
Scope::Utils.and(oldVal, newVal)
|
79
|
+
else
|
80
|
+
# 关于 param、render 内部选项的合并问题暂不考虑
|
81
|
+
newVal
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
attr_reader :properties
|
88
|
+
|
32
89
|
def initialize(options = {}, &)
|
33
90
|
raise 'type 选项必须是 object' if !options[:type].nil? && options[:type] != 'object'
|
34
91
|
|
35
|
-
@properties = {}
|
92
|
+
@properties = {} # 所有的属性已经生成
|
36
93
|
@required = []
|
37
94
|
@validations = {}
|
38
95
|
|
@@ -47,10 +104,13 @@ module Meta
|
|
47
104
|
instance_exec(&) if block_given?
|
48
105
|
end
|
49
106
|
|
50
|
-
def schema_name(schema_base_name)
|
51
|
-
|
52
|
-
|
53
|
-
|
107
|
+
def schema_name(schema_base_name = nil)
|
108
|
+
if schema_base_name
|
109
|
+
raise TypeError, "schema_base_name 必须是一个 String,当前是:#{schema_base_name.class}" unless schema_base_name.is_a?(String)
|
110
|
+
@schema_name = schema_base_name
|
111
|
+
else
|
112
|
+
@schema_name
|
113
|
+
end
|
54
114
|
end
|
55
115
|
|
56
116
|
def property(name, options = {}, &block)
|
@@ -66,12 +126,50 @@ module Meta
|
|
66
126
|
instance_exec(&proc)
|
67
127
|
end
|
68
128
|
|
129
|
+
def with_common_options(common_options, &block)
|
130
|
+
WithCommonOptions.new(self, common_options, &block)
|
131
|
+
end
|
132
|
+
|
133
|
+
def scope(scope, options = {}, &)
|
134
|
+
with_common_options(**options, scope: scope, &)
|
135
|
+
end
|
136
|
+
|
137
|
+
def params(options = {}, &block)
|
138
|
+
with_common_options(**options, render: false, &block)
|
139
|
+
end
|
140
|
+
|
141
|
+
def render(options = {}, &block)
|
142
|
+
with_common_options(**options, params: false, &block)
|
143
|
+
end
|
144
|
+
|
145
|
+
def merge(schema_builder)
|
146
|
+
schema_builder = schema_builder.schema_builder if schema_builder.respond_to?(:schema_builder)
|
147
|
+
|
148
|
+
@properties.merge!(schema_builder.properties)
|
149
|
+
end
|
150
|
+
|
69
151
|
def to_schema(locked_options = nil)
|
70
152
|
properties = @schema_name ? NamedProperties.new(@properties, @schema_name) : Properties.new(@properties)
|
71
153
|
ObjectSchema.new(properties: properties, options: @options, locked_options: locked_options)
|
72
154
|
end
|
73
155
|
|
74
156
|
def locked(options)
|
157
|
+
# defined_scopes_mapping = {}
|
158
|
+
# # TODO: 将 properties 搞成 Properties 可以吗?
|
159
|
+
# defined_scopes = properties.map do |key, property|
|
160
|
+
# property.defined_scopes(stage: :param, defined_scopes_mapping: defined_scopes_mapping)
|
161
|
+
# end.flatten.uniq
|
162
|
+
#
|
163
|
+
# user_scopes = options[:scope] || []
|
164
|
+
# user_scopes = [user_scopes] unless user_scopes.is_a?(Array)
|
165
|
+
#
|
166
|
+
# # 判断 user_scopes 中提供的局部 scope 是否在 defined_scopes 中
|
167
|
+
# local_user_scopes = user_scopes.reject { |scope| scope.start_with?('$') }
|
168
|
+
# if (local_user_scopes - defined_scopes).any?
|
169
|
+
# extra_scopes = local_user_scopes - defined_scopes
|
170
|
+
# raise ArgumentError, "scope #{extra_scopes.join(',')} 未在实体中定义"
|
171
|
+
# end
|
172
|
+
|
75
173
|
Locked.new(self, **options)
|
76
174
|
end
|
77
175
|
include LockedMethodAlias
|
@@ -85,27 +183,6 @@ module Meta
|
|
85
183
|
def apply_object_scope?(options, block)
|
86
184
|
(options[:type] == 'object' || block) && (options[:properties] || block)
|
87
185
|
end
|
88
|
-
|
89
|
-
class Locked
|
90
|
-
attr_reader :object_schema_builder, :locked_options
|
91
|
-
|
92
|
-
# locked_options 是用户传递的参数,这个参数会被合并到 object_schema_builder 的 locked_options 中。
|
93
|
-
def initialize(builder, scope: nil, discard_missing: nil, exclude: nil)
|
94
|
-
@object_schema_builder = builder
|
95
|
-
@locked_options = ObjectSchema::USER_OPTIONS_CHECKER.check({ scope: scope, discard_missing: discard_missing, exclude: exclude }.compact)
|
96
|
-
end
|
97
|
-
|
98
|
-
def to_schema
|
99
|
-
object_schema_builder.to_schema(locked_options)
|
100
|
-
end
|
101
|
-
|
102
|
-
def locked(options)
|
103
|
-
options = ObjectSchema::USER_OPTIONS_CHECKER.check(options)
|
104
|
-
options = ObjectSchema.merge_user_options(locked_options, options)
|
105
|
-
Locked.new(self.object_schema_builder, **options)
|
106
|
-
end
|
107
|
-
include LockedMethodAlias
|
108
|
-
end
|
109
186
|
end
|
110
187
|
end
|
111
|
-
end
|
188
|
+
end
|
@@ -13,9 +13,7 @@ module Meta
|
|
13
13
|
SCHEMA_BUILDER_OPTIONS = Utils::KeywordArgs::Builder.build do
|
14
14
|
permit_extras true
|
15
15
|
|
16
|
-
key :ref, alias_names: [:using], normalizer: ->(entity) {
|
17
|
-
entity
|
18
|
-
}
|
16
|
+
key :ref, alias_names: [:using], normalizer: ->(entity) { entity }
|
19
17
|
key :dynamic_ref, alias_names: [:dynamic_using], normalizer: ->(value) { value.is_a?(Proc) ? { resolve: value } : value }
|
20
18
|
end
|
21
19
|
def build(options = {}, &block)
|
@@ -22,7 +22,9 @@ module Meta
|
|
22
22
|
class BaseSchema
|
23
23
|
OPTIONS_CHECKER = Utils::KeywordArgs::Builder.build do
|
24
24
|
key :type, :items, :description, :presenter, :value, :default, :properties, :convert
|
25
|
-
key :validate, :required, :format
|
25
|
+
key :validate, :required, :format
|
26
|
+
key :enum, alias_names: [:allowable]
|
27
|
+
key :dynamic_ref, alias_names: [:dynamic_using], normalizer: ->(value) { value.is_a?(Proc) ? { resolve: value } : value }
|
26
28
|
key :before, :after
|
27
29
|
key :if
|
28
30
|
end
|
@@ -89,7 +91,7 @@ module Meta
|
|
89
91
|
end
|
90
92
|
|
91
93
|
# 返回能够处理 scope 和 stage 的 schema(可以是 self),否则应返回 UnsupportedStageSchema 或 nil.
|
92
|
-
def find_schema(
|
94
|
+
def find_schema(stage:, scope:)
|
93
95
|
staged(stage)&.scoped(scope)
|
94
96
|
end
|
95
97
|
|
@@ -103,19 +105,22 @@ module Meta
|
|
103
105
|
self
|
104
106
|
end
|
105
107
|
|
108
|
+
# defined_scopes_mapping 是一个 Hash,用于缓存已经计算出的 scopes,用于避免重复计算。其主要针对的是具有命名系统的 Schema,如 Meta::Entity
|
106
109
|
def defined_scopes(stage:, defined_scopes_mapping:)
|
107
110
|
[]
|
108
111
|
end
|
109
112
|
|
110
113
|
# 执行 if: 选项,返回 true 或 false
|
111
|
-
def if?(
|
112
|
-
|
114
|
+
def if?(object_value, execution = nil)
|
115
|
+
if_block = options[:if]
|
116
|
+
return true if if_block.nil?
|
113
117
|
|
114
|
-
|
118
|
+
args_length = if_block.lambda? ? if_block.arity : 1
|
119
|
+
args = args_length > 0 ? [object_value] : []
|
115
120
|
if execution
|
116
|
-
execution.instance_exec(&options[:if])
|
121
|
+
execution.instance_exec(*args, &options[:if])
|
117
122
|
else
|
118
|
-
options[:if]&.call
|
123
|
+
options[:if]&.call(*args)
|
119
124
|
end
|
120
125
|
end
|
121
126
|
|
@@ -136,7 +141,7 @@ module Meta
|
|
136
141
|
schema = {}
|
137
142
|
schema[:type] = options[:type] if options[:type]
|
138
143
|
schema[:description] = options[:description] if options[:description]
|
139
|
-
schema[:enum] = options[:
|
144
|
+
schema[:enum] = options[:enum] if options[:enum]
|
140
145
|
|
141
146
|
schema
|
142
147
|
end
|
@@ -11,25 +11,37 @@ module Meta
|
|
11
11
|
# scope:、discard_missing:、exclude: 等
|
12
12
|
attr_reader :locked_options
|
13
13
|
|
14
|
+
normalize_scope = ->(value) {
|
15
|
+
raise ArgumentError, 'scope 选项不可传递 nil' if value.nil?
|
16
|
+
value = [value] unless value.is_a?(Array)
|
17
|
+
value.map do |v|
|
18
|
+
# 只要加入了 Meta::Scope::Base 模块,就有与 Meta::Scope 一样的行为
|
19
|
+
next v if v.is_a?(Meta::Scope::Base)
|
20
|
+
|
21
|
+
# 将 v 类名化
|
22
|
+
scope_name = v.to_s.split('_').map(&:capitalize).join
|
23
|
+
# 如果符号对应的类名不存在,就报错
|
24
|
+
if !defined?(::Scopes) || !::Scopes.const_defined?(scope_name)
|
25
|
+
raise NameError, "未找到常量 Scopes::#{scope_name}。如果你用的是命名 Scope(字符串或符号),则检查一下是不是拼写错误"
|
26
|
+
end
|
27
|
+
# 返回对应的常量
|
28
|
+
::Scopes.const_get(scope_name)
|
29
|
+
end.compact
|
30
|
+
}
|
14
31
|
# stage 和 scope 选项在两个 CHECKER 下都用到了
|
15
32
|
USER_OPTIONS_CHECKER = Utils::KeywordArgs::Builder.build do
|
16
33
|
key :stage
|
17
|
-
key :scope, normalizer:
|
18
|
-
raise ArgumentError, 'scope 选项不可传递 nil' if value.nil?
|
19
|
-
value = [value] unless value.is_a?(Array)
|
20
|
-
value
|
21
|
-
}
|
34
|
+
key :scope, normalizer: normalize_scope
|
22
35
|
key :discard_missing, :exclude, :extra_properties, :type_conversion, :validation
|
23
36
|
key :execution, :user_data, :object_value
|
24
37
|
end
|
25
38
|
TO_SCHEMA_DOC_CHECKER = Utils::KeywordArgs::Builder.build do
|
26
39
|
key :stage
|
27
|
-
key :scope, normalizer:
|
28
|
-
raise ArgumentError, 'scope 选项不可传递 nil' if value.nil?
|
29
|
-
value = [value] unless value.is_a?(Array)
|
30
|
-
value
|
31
|
-
}
|
40
|
+
key :scope, normalizer: normalize_scope
|
32
41
|
key :schema_docs_mapping, :defined_scopes_mapping
|
42
|
+
|
43
|
+
# 以下是 filter 用到的选项,讲道理这些选项应该是不需要的,放置这些是为了防止报错
|
44
|
+
key :discard_missing
|
33
45
|
end
|
34
46
|
|
35
47
|
def initialize(properties:, options: {}, locked_options: {})
|
@@ -42,17 +54,6 @@ module Meta
|
|
42
54
|
@locked_options = USER_OPTIONS_CHECKER.check(locked_options || {})
|
43
55
|
end
|
44
56
|
|
45
|
-
# 复制一个新的 ObjectSchema,只有 options 不同
|
46
|
-
def dup(options)
|
47
|
-
raise UnsupportedError, 'dup 不应该再执行了'
|
48
|
-
|
49
|
-
self.class.new(
|
50
|
-
properties: properties,
|
51
|
-
options: options,
|
52
|
-
locked_options: locked_options,
|
53
|
-
)
|
54
|
-
end
|
55
|
-
|
56
57
|
def filter(object_value, user_options = {})
|
57
58
|
# 合并 user_options
|
58
59
|
user_options = USER_OPTIONS_CHECKER.check(user_options)
|
@@ -60,35 +61,59 @@ module Meta
|
|
60
61
|
super
|
61
62
|
end
|
62
63
|
|
64
|
+
def to_schema_doc(user_options = {})
|
65
|
+
user_options = TO_SCHEMA_DOC_CHECKER.check(user_options)
|
66
|
+
user_options = self.class.merge_user_options(user_options, locked_options) if locked_options
|
67
|
+
|
68
|
+
schema = { type: 'object' }
|
69
|
+
schema[:description] = options[:description] if options[:description]
|
70
|
+
properties, required_keys = @properties.to_swagger_doc(**user_options)
|
71
|
+
schema[:properties] = properties unless properties.empty?
|
72
|
+
schema[:required] = required_keys unless required_keys.empty?
|
73
|
+
schema
|
74
|
+
end
|
75
|
+
|
63
76
|
def naming?
|
64
77
|
properties.is_a?(NamedProperties)
|
65
78
|
end
|
66
79
|
|
80
|
+
def defined_scopes(stage:, defined_scopes_mapping:)
|
81
|
+
properties.defined_scopes(stage: stage, defined_scopes_mapping: defined_scopes_mapping)
|
82
|
+
end
|
83
|
+
|
84
|
+
# 解析当前 Schema 的名称
|
85
|
+
#
|
86
|
+
# 名称解析的规则是:
|
87
|
+
# 1. 结合基础的 schema_name,如果它是参数,就加上 Params 后缀;如果它是返回值,就加上 Entity 后缀
|
88
|
+
# 2. 而后跟上 locked_scope. 这里,会把多余的 scope 去掉。比如,一个实体内部只处理 Admin 的 scope,但是外部传进来了
|
89
|
+
# Admin 和 Detail,那么名称只会包括 Admin
|
67
90
|
def resolve_name(stage, user_scopes, defined_scopes)
|
68
91
|
raise ArgumentError, 'stage 不能为 nil' if stage.nil?
|
69
92
|
|
70
93
|
# 先合成外面传进来的 scope
|
71
94
|
locked_scopes = (locked_options || {})[:scope] || []
|
72
95
|
user_scopes = (user_scopes + locked_scopes).uniq
|
73
|
-
scopes = user_scopes & defined_scopes
|
74
96
|
|
75
97
|
# 再根据 stage 和 scope 生成为当前的 Schema 生成一个合适的名称,要求保证唯一性
|
76
|
-
|
77
|
-
schema_name += "__#{scopes.join('__')}" unless scopes.empty?
|
78
|
-
schema_name
|
79
|
-
end
|
80
|
-
|
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
|
98
|
+
base_schema_name = properties.schema_name(stage)
|
84
99
|
|
85
|
-
|
86
|
-
|
100
|
+
# 将调用转移到 Scopes 模块下
|
101
|
+
resolve_name_helper(base_schema_name, user_scopes, defined_scopes)
|
102
|
+
end
|
87
103
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
104
|
+
# 帮助实体解析名称,这里主要是考虑 scope 的作用
|
105
|
+
#
|
106
|
+
# base_schema_name: 具有 Params 或 Entity 后缀的基础名称
|
107
|
+
# user_scopes: 用户传进来的 scope 数组
|
108
|
+
# candidate_scopes: 从实体中找出的能够参与命名的备选的 scope 数组
|
109
|
+
def resolve_name_helper(base_schema_name, user_scopes, candidate_scopes)
|
110
|
+
# 从备选的 scope 中获取到被利用到的
|
111
|
+
scopes = candidate_scopes.filter { |candidate_scope| candidate_scope.match?(user_scopes) }
|
112
|
+
scope_names = scopes.map(&:scope_name)
|
113
|
+
|
114
|
+
# 合成新的名字
|
115
|
+
schema_name_parts = [base_schema_name] + scope_names
|
116
|
+
schema_name_parts.join('__')
|
92
117
|
end
|
93
118
|
|
94
119
|
def locked_scope
|
@@ -30,7 +30,7 @@ module Meta
|
|
30
30
|
next false if exclude && exclude.include?(name)
|
31
31
|
|
32
32
|
# 通过 if 选项过滤
|
33
|
-
next false unless property_schema.if?(user_options)
|
33
|
+
next false unless property_schema.if?(object_value, user_options[:execution])
|
34
34
|
|
35
35
|
# 默认返回 true
|
36
36
|
next true
|
@@ -71,8 +71,7 @@ module Meta
|
|
71
71
|
|
72
72
|
# user_options 包括 stage, scope, schema_docs_mapping, defined_scopes_mapping
|
73
73
|
def to_swagger_doc(scope: [], stage: nil, **user_options)
|
74
|
-
|
75
|
-
properties = filter_by(stage: stage, user_scope: locked_scopes)
|
74
|
+
properties = filter_by(stage: stage, user_scope: scope)
|
76
75
|
required_keys = properties.filter do |key, property_schema|
|
77
76
|
property_schema.options[:required]
|
78
77
|
end.keys
|
@@ -82,6 +81,12 @@ module Meta
|
|
82
81
|
[properties, required_keys]
|
83
82
|
end
|
84
83
|
|
84
|
+
def defined_scopes(stage:, defined_scopes_mapping:)
|
85
|
+
@properties.each_with_object([]) do |(name, property), defined_scopes|
|
86
|
+
defined_scopes.concat(property.defined_scopes(stage: stage, defined_scopes_mapping: defined_scopes_mapping))
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
85
90
|
# 程序中有些地方用到了这三个方法
|
86
91
|
def_delegators :@properties, :empty?, :key?, :[], :each
|
87
92
|
|
@@ -95,13 +100,15 @@ module Meta
|
|
95
100
|
|
96
101
|
private
|
97
102
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
+
# 根据所示的关键字参数,过滤出符合条件的属性
|
104
|
+
# 注意,这里会结合自身的 locked_scope 考量
|
105
|
+
def filter_by(stage:, user_scope: false)
|
106
|
+
@properties.transform_values do |property|
|
107
|
+
property.find_schema(stage: stage, scope: user_scope)
|
108
|
+
end.filter do |name, schema|
|
109
|
+
schema.filter?
|
110
|
+
end
|
103
111
|
end
|
104
|
-
end
|
105
112
|
|
106
113
|
def resolve_property_value(object_value, name, property_schema)
|
107
114
|
if property_schema.value?
|
@@ -40,13 +40,17 @@ module Meta
|
|
40
40
|
def defined_scopes(stage:, defined_scopes_mapping:)
|
41
41
|
defined_scopes_mapping ||= {}
|
42
42
|
|
43
|
-
|
44
|
-
|
43
|
+
if object_schema.properties.respond_to?(:schema_name)
|
44
|
+
# 只有命名实体才会被缓存
|
45
|
+
schema_name = object_schema.properties.schema_name(stage)
|
46
|
+
return defined_scopes_mapping[schema_name] if defined_scopes_mapping.key?(schema_name)
|
47
|
+
end
|
45
48
|
|
46
49
|
defined_scopes_mapping[schema_name] = []
|
50
|
+
# 求解 defined_scopes,最终结果去重 + 排序
|
47
51
|
defined_scopes = object_schema.properties.each.map do |name, property|
|
48
52
|
property.defined_scopes(stage: stage, defined_scopes_mapping: defined_scopes_mapping)
|
49
|
-
end.flatten.uniq.
|
53
|
+
end.flatten.uniq.sort_by(&:name)
|
50
54
|
defined_scopes_mapping[schema_name] = defined_scopes
|
51
55
|
defined_scopes
|
52
56
|
end
|
@@ -1,14 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative '../support/scope_matcher'
|
4
|
+
require_relative '../../scope/utils'
|
4
5
|
|
5
6
|
module Meta
|
6
7
|
module JsonSchema
|
8
|
+
# TODO: 作为模块引入
|
7
9
|
class ScopingSchema < BaseSchema
|
8
10
|
attr_reader :scope_matcher, :schema
|
9
11
|
|
10
|
-
def initialize(
|
11
|
-
|
12
|
+
def initialize(scope_matcher:, schema:)
|
13
|
+
# raise ArgumentError, 'scope_matcher 不能是一个数组' if scope_matcher.is_a?(Array)
|
14
|
+
|
15
|
+
@scope_matcher = Scope::Utils.parse(scope_matcher)
|
12
16
|
@schema = schema
|
13
17
|
end
|
14
18
|
|
@@ -17,7 +21,9 @@ module Meta
|
|
17
21
|
end
|
18
22
|
|
19
23
|
def defined_scopes(**kwargs)
|
20
|
-
scope_matcher.defined_scopes
|
24
|
+
current = scope_matcher.defined_scopes
|
25
|
+
deep = schema.defined_scopes(**kwargs)
|
26
|
+
(current + deep).uniq
|
21
27
|
end
|
22
28
|
|
23
29
|
private
|
@@ -30,7 +36,7 @@ module Meta
|
|
30
36
|
options = options.dup
|
31
37
|
scope_matcher_options = options.delete(:scope)
|
32
38
|
schema = build_schema.call(options)
|
33
|
-
schema = ScopingSchema.new(
|
39
|
+
schema = ScopingSchema.new(scope_matcher: scope_matcher_options, schema: schema) if scope_matcher_options
|
34
40
|
schema
|
35
41
|
end
|
36
42
|
end
|
@@ -7,19 +7,22 @@ module Meta
|
|
7
7
|
|
8
8
|
def initialize(query_clause)
|
9
9
|
query_clause = [query_clause] if query_clause.is_a?(String) || query_clause.is_a?(Symbol)
|
10
|
-
query_clause = {
|
10
|
+
query_clause = { all_of: query_clause } if query_clause.is_a?(Array)
|
11
11
|
|
12
12
|
@match_type, @defined_scopes = query_clause.first
|
13
13
|
end
|
14
14
|
|
15
|
-
def match?(
|
16
|
-
|
15
|
+
def match?(providing_scopes)
|
16
|
+
# 目前认为空数组就是不做 scope 筛选
|
17
|
+
return false if providing_scopes.empty?
|
17
18
|
|
18
19
|
case @match_type
|
19
20
|
when :some_of
|
20
|
-
|
21
|
+
# 只要相交就可以
|
22
|
+
(@defined_scopes & providing_scopes).any?
|
21
23
|
when :all_of
|
22
|
-
|
24
|
+
# @defined_scopes 一定要被包含在 providing_scopes 内
|
25
|
+
(@defined_scopes - providing_scopes).empty?
|
23
26
|
else
|
24
27
|
raise "Unknown match type: #{@match_type}"
|
25
28
|
end
|
@@ -26,9 +26,9 @@ module Meta
|
|
26
26
|
next if value.nil?
|
27
27
|
raise JsonSchema::ValidationError, I18n.t(:'json_schema.errors.format') unless value =~ format
|
28
28
|
},
|
29
|
-
|
29
|
+
enum: proc { |value, allowable_values|
|
30
30
|
next if value.nil?
|
31
|
-
raise JsonSchema::ValidationError, I18n.t(:'json_schema.errors.
|
31
|
+
raise JsonSchema::ValidationError, I18n.t(:'json_schema.errors.enum', allowable_values: allowable_values) unless allowable_values.include?(value)
|
32
32
|
}
|
33
33
|
}
|
34
34
|
|
@@ -43,7 +43,13 @@ module Meta
|
|
43
43
|
codes.each { |code| @meta[:responses][code] = entity_schema }
|
44
44
|
end
|
45
45
|
|
46
|
-
|
46
|
+
def scope(scope)
|
47
|
+
scope = [scope] unless scope.is_a?(Array)
|
48
|
+
|
49
|
+
@meta[:scope] = scope
|
50
|
+
end
|
51
|
+
|
52
|
+
[:tags, :title, :description].each do |method_name|
|
47
53
|
define_method(method_name) do |value|
|
48
54
|
@meta[method_name] = value
|
49
55
|
end
|
@@ -24,6 +24,7 @@ module Meta
|
|
24
24
|
end
|
25
25
|
|
26
26
|
in_op = options.delete(:in)
|
27
|
+
raise ArgumentError, "in 选项只能是 path, query, header, body" unless %w[path query header body].include?(in_op)
|
27
28
|
@parameter_options[name] = { in: in_op, schema: JsonSchema::BaseSchema.new(options) }
|
28
29
|
end
|
29
30
|
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Meta
|
4
|
+
class Scope
|
5
|
+
module Errors
|
6
|
+
class NameNotSet < StandardError; end
|
7
|
+
end
|
8
|
+
|
9
|
+
# 基本的 Scope 方法,引入该模块即可获取系列方法
|
10
|
+
module Base
|
11
|
+
def match?(scopes)
|
12
|
+
scopes = [scopes] unless scopes.is_a?(Array)
|
13
|
+
match_scopes?(scopes)
|
14
|
+
end
|
15
|
+
|
16
|
+
def match_scopes?(scopes)
|
17
|
+
return true if @forwarded_scope&.match?(scopes)
|
18
|
+
scopes.include?(self)
|
19
|
+
end
|
20
|
+
|
21
|
+
def defined_scopes
|
22
|
+
[self]
|
23
|
+
end
|
24
|
+
|
25
|
+
def scope_name
|
26
|
+
scope_name = @scope_name || self.name
|
27
|
+
raise Errors::NameNotSet, '未设置 scope 名称' if scope_name.nil?
|
28
|
+
|
29
|
+
scope_name.split('::').last
|
30
|
+
end
|
31
|
+
|
32
|
+
def scope_name=(name)
|
33
|
+
@scope_name = name.to_s
|
34
|
+
end
|
35
|
+
|
36
|
+
def include_scope(*scopes)
|
37
|
+
@forwarded_scope = Composite.concat(@forwarded_scope, *scopes)
|
38
|
+
end
|
39
|
+
|
40
|
+
# 既作为类方法,也作为实例方法
|
41
|
+
def and(*scopes)
|
42
|
+
scopes = [self, *scopes] if self != Meta::Scope
|
43
|
+
And.new(*scopes)
|
44
|
+
end
|
45
|
+
alias_method :&, :and
|
46
|
+
|
47
|
+
# 既可以是类方法,也可以是实例方法
|
48
|
+
def or(*scopes)
|
49
|
+
scopes = [self, *scopes] if self != Meta::Scope
|
50
|
+
Or.new(*scopes)
|
51
|
+
end
|
52
|
+
alias_method :|, :or
|
53
|
+
|
54
|
+
def inspect
|
55
|
+
scope_name || super
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# 将 Scope 类的子类作为 Scope 实例
|
60
|
+
class << self
|
61
|
+
include Base
|
62
|
+
|
63
|
+
def new(*args)
|
64
|
+
raise NoMethodError, 'Meta::Scope 类不能实例化'
|
65
|
+
end
|
66
|
+
|
67
|
+
def inherited(subclass)
|
68
|
+
# subclass.instance_variable_set(:@forwarded_scope, Or.new)
|
69
|
+
|
70
|
+
# 如果是 Meta::Scope 的具体子类被继承,该子类加入到 @included_scopes 原语中
|
71
|
+
subclass.include_scope(self) if self != Meta::Scope
|
72
|
+
end
|
73
|
+
|
74
|
+
def include(*mods)
|
75
|
+
scopes = mods.filter { |m| m < Meta::Scope }
|
76
|
+
mods = mods - scopes
|
77
|
+
|
78
|
+
include_scope(*scopes) if scopes.any?
|
79
|
+
super(*mods) if mods.any?
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# 组合式 Scope 实例,用于表示多个 Scope 的逻辑操作
|
84
|
+
class Composite
|
85
|
+
include Base
|
86
|
+
|
87
|
+
def self.new(*scopes)
|
88
|
+
if scopes.length == 0
|
89
|
+
@empty || (@empty = self.new)
|
90
|
+
elsif scopes.length == 1
|
91
|
+
scopes[0]
|
92
|
+
else
|
93
|
+
super(*scopes)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.concat(*scopes)
|
98
|
+
composite_classes = scopes.filter { |scope| scope.is_a?(Composite) }
|
99
|
+
.map(&:class).uniq
|
100
|
+
raise ArgumentError, "不能执行 concat,参数中包含了多个逻辑运算符:#{composite_classes.join(',')}" if composite_classes.length > 1
|
101
|
+
|
102
|
+
if composite_classes.empty?
|
103
|
+
Or.new(*scopes)
|
104
|
+
else
|
105
|
+
composite_classes[0].new(*scopes)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def initialize(*scopes)
|
110
|
+
@scopes = scopes.compact.map do |scope|
|
111
|
+
scope.is_a?(self.class) ? scope.defined_scopes : scope
|
112
|
+
end.flatten.freeze
|
113
|
+
end
|
114
|
+
|
115
|
+
def defined_scopes
|
116
|
+
@scopes
|
117
|
+
end
|
118
|
+
|
119
|
+
def scope_name
|
120
|
+
@scope_name || @scopes.map(&:scope_name).sort.join('_')
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# 逻辑 And 操作
|
125
|
+
class And < Composite
|
126
|
+
# scopes 需要包含所有的 @scopes
|
127
|
+
def match_scopes?(scopes)
|
128
|
+
@scopes.all? { |scope| scope.match?(scopes) }
|
129
|
+
end
|
130
|
+
|
131
|
+
# 重定义 scope_name,如果用得上的话
|
132
|
+
def scope_name
|
133
|
+
@scope_name || @scopes.map(&:scope_name).sort.join('_and_')
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# 另一种 Scope 实例,用于表示多个 Scope 的逻辑 Or 操作
|
138
|
+
class Or < Composite
|
139
|
+
include Base
|
140
|
+
|
141
|
+
# scopes 只需要包含一个 @scopes
|
142
|
+
def match_scopes?(scopes)
|
143
|
+
@scopes.any? { |scope| scope.match?(scopes) }
|
144
|
+
end
|
145
|
+
|
146
|
+
# 重定义 scope_name,如果用得上的话
|
147
|
+
def scope_name
|
148
|
+
@scope_name || @scopes.map(&:scope_name).sort.join('_or_')
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
# 兼容以前的字符串 scope
|
6
|
+
module Meta
|
7
|
+
class Scope
|
8
|
+
module Utils
|
9
|
+
class << self
|
10
|
+
# 根据选项构建 Scope 实例,这是兼容之前的字符串写法。
|
11
|
+
def parse(scope)
|
12
|
+
scope = [scope] if scope.is_a?(String) || scope.is_a?(Symbol)
|
13
|
+
|
14
|
+
# 只会有两种类型:Array[String] 和 Scope 子类
|
15
|
+
if scope.is_a?(Meta::Scope::Base)
|
16
|
+
scope
|
17
|
+
elsif scope.is_a?(Array)
|
18
|
+
scopes = scope.map { |s| parse_string(s) }
|
19
|
+
Or.new(*scopes)
|
20
|
+
else
|
21
|
+
raise ArgumentError, 'scope 参数必须是一个数组或者 Scope 子类'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def and(*scopes)
|
26
|
+
scopes = scopes.map { |s| parse(s) }
|
27
|
+
And.new(*scopes)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def parse_string(str)
|
33
|
+
# 确保全局存在一个 Scopes 模块
|
34
|
+
unless defined?(::Scopes)
|
35
|
+
scopes = Module.new
|
36
|
+
Object.const_set(:Scopes, scopes)
|
37
|
+
end
|
38
|
+
|
39
|
+
# 获取类名化的 scope 名称
|
40
|
+
scope_name = str.to_s.split('_').map(&:capitalize).join
|
41
|
+
|
42
|
+
# 如果 Scopes 模块中已经存在该 scope 类,直接返回
|
43
|
+
return ::Scopes.const_get(scope_name) if ::Scopes.const_defined?(scope_name)
|
44
|
+
|
45
|
+
# 如果不存在,创建一个新的类
|
46
|
+
scope_class = Class.new(Meta::Scope)
|
47
|
+
scope_class.scope_name = str
|
48
|
+
::Scopes.const_set(scope_name, scope_class)
|
49
|
+
|
50
|
+
# 返回结果
|
51
|
+
scope_class
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/meta-api.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: meta-api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- yetrun
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-06-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: hash_to_struct
|
@@ -81,6 +81,8 @@ files:
|
|
81
81
|
- lib//meta/route_dsl/parameters_builder.rb
|
82
82
|
- lib//meta/route_dsl/route_builder.rb
|
83
83
|
- lib//meta/route_dsl/uniformed_params_builder.rb
|
84
|
+
- lib//meta/scope/base.rb
|
85
|
+
- lib//meta/scope/utils.rb
|
84
86
|
- lib//meta/swagger_doc.rb
|
85
87
|
- lib//meta/utils/kwargs/builder.rb
|
86
88
|
- lib//meta/utils/kwargs/check.rb
|