meta-api 0.1.1 → 0.2.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 +8 -0
- data/config/locales/zh-CN.yml +1 -1
- data/lib/meta/api.rb +2 -0
- data/lib/meta/application/metadata.rb +2 -4
- data/lib/meta/application/route.rb +2 -1
- data/lib/meta/entity.rb +7 -2
- data/lib/meta/json_schema/builders/object_schema_builder.rb +69 -51
- data/lib/meta/json_schema/builders/schema_builder_tool.rb +31 -22
- data/lib/meta/json_schema/schemas/base_schema.rb +3 -23
- data/lib/meta/json_schema/schemas/object_schema.rb +38 -39
- data/lib/meta/json_schema/schemas/properties.rb +19 -20
- data/lib/meta/json_schema/schemas/ref_schema.rb +2 -8
- data/lib/meta/json_schema/schemas/scoping_schema.rb +6 -3
- data/lib/meta/json_schema/schemas/staging_schema.rb +4 -1
- data/lib/meta/json_schema/support/schema_options.rb +87 -18
- 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 +0 -3
- data/lib/meta/route_dsl/parameters_builder.rb +4 -3
- data/lib/meta/route_dsl/route_builder.rb +0 -1
- data/lib/meta/route_dsl/uniformed_params_builder.rb +3 -3
- data/lib/meta/scope/base.rb +152 -0
- data/lib/meta/scope/utils.rb +56 -0
- data/lib/meta/utils/kwargs/builder.rb +17 -74
- data/lib/meta/utils/kwargs/checker.rb +27 -26
- data/lib/meta/utils/kwargs/consumers.rb +64 -0
- data/lib/meta/utils/kwargs/extras_consumers.rb +33 -0
- data/lib/meta/utils/kwargs/helpers.rb +36 -0
- data/meta-api.gemspec +1 -1
- metadata +7 -3
- data/lib/meta/utils/kwargs/check.rb +0 -91
@@ -1,14 +1,17 @@
|
|
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
|
7
8
|
class ScopingSchema < BaseSchema
|
8
9
|
attr_reader :scope_matcher, :schema
|
9
10
|
|
10
|
-
def initialize(
|
11
|
-
|
11
|
+
def initialize(scope_matcher:, schema:)
|
12
|
+
# raise ArgumentError, 'scope_matcher 不能是一个数组' if scope_matcher.is_a?(Array)
|
13
|
+
|
14
|
+
@scope_matcher = Scope::Utils.parse(scope_matcher)
|
12
15
|
@schema = schema
|
13
16
|
end
|
14
17
|
|
@@ -32,7 +35,7 @@ module Meta
|
|
32
35
|
options = options.dup
|
33
36
|
scope_matcher_options = options.delete(:scope)
|
34
37
|
schema = build_schema.call(options)
|
35
|
-
schema = ScopingSchema.new(
|
38
|
+
schema = ScopingSchema.new(scope_matcher: scope_matcher_options, schema: schema) if scope_matcher_options
|
36
39
|
schema
|
37
40
|
end
|
38
41
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '../../utils/kwargs/check'
|
4
3
|
require_relative '../support/schema_options'
|
5
4
|
require_relative 'scoping_schema'
|
6
5
|
require_relative 'unsupported_schema'
|
@@ -43,6 +42,10 @@ module Meta
|
|
43
42
|
staged(stage).defined_scopes(stage: stage, **kwargs)
|
44
43
|
end
|
45
44
|
|
45
|
+
def to_schema_doc(stage:, **kwargs)
|
46
|
+
staged(stage).to_schema_doc(stage: stage, **kwargs)
|
47
|
+
end
|
48
|
+
|
46
49
|
def self.build_from_options(options, build_schema = ->(opts) { BaseSchema.new(opts) })
|
47
50
|
param_opts, render_opts, common_opts = SchemaOptions.divide_to_param_and_render(options)
|
48
51
|
if param_opts == common_opts && render_opts == common_opts
|
@@ -5,12 +5,64 @@ require_relative '../../utils/kwargs/builder'
|
|
5
5
|
module Meta
|
6
6
|
module JsonSchema
|
7
7
|
module SchemaOptions
|
8
|
-
|
9
|
-
|
10
|
-
required:
|
11
|
-
|
8
|
+
BaseBuildOptions = Utils::Kwargs::Builder.build do
|
9
|
+
key :type, :items, :description, :presenter, :value, :default, :properties, :convert
|
10
|
+
key :validate, :required, :format
|
11
|
+
key :enum, alias_names: [:allowable]
|
12
|
+
key :ref, alias_names: [:using], normalizer: ->(entity) { entity }
|
13
|
+
key :dynamic_ref, alias_names: [:dynamic_using], normalizer: ->(value) { value.is_a?(Proc) ? { resolve: value } : value }
|
14
|
+
key :before, :after
|
15
|
+
key :if
|
16
|
+
end
|
17
|
+
|
18
|
+
module UserOptions
|
19
|
+
Common = Utils::Kwargs::Builder.build do
|
20
|
+
key :stage
|
21
|
+
key :scope, normalizer: ->(value) {
|
22
|
+
raise ArgumentError, 'scope 选项不可传递 nil' if value.nil?
|
23
|
+
value = [value] unless value.is_a?(Array)
|
24
|
+
value.map do |v|
|
25
|
+
# 只要加入了 Meta::Scope::Base 模块,就有与 Meta::Scope 一样的行为
|
26
|
+
next v if v.is_a?(Meta::Scope::Base)
|
27
|
+
|
28
|
+
# 将 v 类名化
|
29
|
+
scope_name = v.to_s.split('_').map(&:capitalize).join
|
30
|
+
# 如果符号对应的类名不存在,就报错
|
31
|
+
if !defined?(::Scopes) || !::Scopes.const_defined?(scope_name)
|
32
|
+
raise NameError, "未找到常量 Scopes::#{scope_name}。如果你用的是命名 Scope(字符串或符号),则检查一下是不是拼写错误"
|
33
|
+
end
|
34
|
+
# 返回对应的常量
|
35
|
+
::Scopes.const_get(scope_name)
|
36
|
+
end.compact
|
37
|
+
}
|
38
|
+
|
39
|
+
handle_extras :merged
|
40
|
+
end
|
41
|
+
ToDoc = Utils::Kwargs::Builder.build(Common) do
|
42
|
+
key :schema_docs_mapping, :defined_scopes_mapping
|
43
|
+
end
|
44
|
+
Filter = Utils::Kwargs::Builder.build(Common) do
|
45
|
+
key :discard_missing, :exclude, :extra_properties, :type_conversion, :validation
|
46
|
+
key :execution, :user_data, :object_value
|
47
|
+
end
|
48
|
+
end
|
12
49
|
|
13
50
|
class << self
|
51
|
+
def fix_type_option!(options)
|
52
|
+
if options[:type].is_a?(Class)
|
53
|
+
# 修复 type 为自定义类的情形
|
54
|
+
the_class = options[:type]
|
55
|
+
# 修复 param 选项
|
56
|
+
options[:param] = {} if options[:param].nil?
|
57
|
+
make_after_cast_to_class(options[:param], the_class) if options[:param]
|
58
|
+
# 修复 render 选项
|
59
|
+
options[:render] = {} if options[:render].nil?
|
60
|
+
make_before_match_to_class(options[:render], the_class) if options[:render]
|
61
|
+
# 最终确保 type 为 object
|
62
|
+
options.merge!(type: 'object')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
14
66
|
def divide_to_param_and_render(options)
|
15
67
|
common_opts = (options || {}).dup
|
16
68
|
param_opts = common_opts.delete(:param)
|
@@ -21,20 +73,6 @@ module Meta
|
|
21
73
|
[param_opts, render_opts, common_opts]
|
22
74
|
end
|
23
75
|
|
24
|
-
def normalize(options)
|
25
|
-
# 只要 options 中设置为 nil 的选项没有明确的意义,则下行代码是永远有效的
|
26
|
-
options = (@default_options.compact).merge(options.compact)
|
27
|
-
if options[:using]
|
28
|
-
if options[:type].nil?
|
29
|
-
options[:type] = 'object'
|
30
|
-
elsif options[:type] != 'object' && options[:type] != 'array'
|
31
|
-
raise "当使用 using 时,type 必须声明为 object 或 array"
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
options
|
36
|
-
end
|
37
|
-
|
38
76
|
private
|
39
77
|
|
40
78
|
def merge_common_to_stage(common_opts, stage_opts)
|
@@ -42,6 +80,37 @@ module Meta
|
|
42
80
|
stage_opts = common_opts.merge(stage_opts) if stage_opts
|
43
81
|
stage_opts
|
44
82
|
end
|
83
|
+
|
84
|
+
def make_after_cast_to_class(options, the_class)
|
85
|
+
if options[:after].nil?
|
86
|
+
options[:after] = ->(value) { the_class.new(value) }
|
87
|
+
else
|
88
|
+
# 如果用户自定义了 after,那么我们需要在 after 之后再包一层
|
89
|
+
original_after_block = options[:after]
|
90
|
+
options[:after] = ->(value) do
|
91
|
+
value = instance_exec(value, &original_after_block)
|
92
|
+
the_class.new(value)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def make_before_match_to_class(options, the_class)
|
98
|
+
match_class = ->(value) do
|
99
|
+
raise ValidationError, "value 必须是 #{the_class} 类型" unless value.is_a?(the_class)
|
100
|
+
value
|
101
|
+
end
|
102
|
+
if options[:before].nil?
|
103
|
+
options[:before] = match_class
|
104
|
+
else
|
105
|
+
# 如果用户自定义了 before,那么我们需要在 before 之前再包一层
|
106
|
+
original_before_block = options[:before]
|
107
|
+
options[:before] = ->(value) do
|
108
|
+
value = match_class.call(value)
|
109
|
+
instance_exec(value, &original_before_block)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
45
114
|
end
|
46
115
|
end
|
47
116
|
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
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative '../application/parameters'
|
4
|
-
require_relative '../utils/kwargs/
|
4
|
+
require_relative '../utils/kwargs/helpers'
|
5
5
|
|
6
6
|
module Meta
|
7
7
|
module RouteDSL
|
@@ -18,12 +18,13 @@ module Meta
|
|
18
18
|
# 修正 path 参数的选项
|
19
19
|
options = options.dup
|
20
20
|
if path_param_names.include?(name) # path 参数
|
21
|
-
options = Utils::
|
21
|
+
options = Utils::Kwargs::Helpers.fix!(options, in: 'path', required: true)
|
22
22
|
else
|
23
|
-
options = Utils::
|
23
|
+
options = Utils::Kwargs::Helpers.merge_defaults!(options, in: 'query')
|
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
|
|
@@ -16,11 +16,11 @@ module Meta
|
|
16
16
|
def param(name, options = {}, &block)
|
17
17
|
options = (options || {}).dup
|
18
18
|
if path_param_names.include?(name)
|
19
|
-
options = Utils::
|
19
|
+
options = Utils::Kwargs::Helpers.fix!(options, in: 'path', required: true)
|
20
20
|
elsif @route_method == :get
|
21
|
-
options = Utils::
|
21
|
+
options = Utils::Kwargs::Helpers.merge_defaults!(options, in: 'query')
|
22
22
|
else
|
23
|
-
options = Utils::
|
23
|
+
options = Utils::Kwargs::Helpers.merge_defaults!(options, in: 'body')
|
24
24
|
end
|
25
25
|
|
26
26
|
if options[:in] == 'body'
|
@@ -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
|
@@ -17,101 +17,44 @@
|
|
17
17
|
# key :a, :b, :c
|
18
18
|
# key :d, normalizer: ->(value) { normalize_to_array(value) }
|
19
19
|
# end
|
20
|
-
|
20
|
+
|
21
|
+
require_relative 'consumers'
|
22
|
+
require_relative 'extras_consumers'
|
23
|
+
require_relative 'checker'
|
21
24
|
|
22
25
|
module Meta
|
23
26
|
module Utils
|
24
|
-
class
|
25
|
-
def initialize(arguments, permit_extras = false, final_consumer = nil)
|
26
|
-
@arguments = arguments
|
27
|
-
@permit_extras = permit_extras
|
28
|
-
@final_consumer = final_consumer
|
29
|
-
end
|
30
|
-
|
31
|
-
def check(args)
|
32
|
-
args = args.dup
|
33
|
-
final_args = {}
|
34
|
-
|
35
|
-
@arguments.each do |argument|
|
36
|
-
argument.consume(final_args, args)
|
37
|
-
end
|
38
|
-
|
39
|
-
# 做最终的修饰
|
40
|
-
@final_consumer.call(final_args, args) if @final_consumer
|
41
|
-
|
42
|
-
# 处理剩余字段
|
43
|
-
unless args.keys.empty?
|
44
|
-
if @permit_extras
|
45
|
-
final_args.merge!(args)
|
46
|
-
else
|
47
|
-
extras = args.keys
|
48
|
-
raise "不接受额外的关键字参数:#{extras.join(', ')}" unless extras.empty?
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
final_args
|
53
|
-
end
|
54
|
-
|
55
|
-
class Argument
|
56
|
-
DEFAULT_TRANSFORMER = ->(value) { value }
|
57
|
-
|
58
|
-
def initialize(name:, normalizer: DEFAULT_TRANSFORMER, validator: nil, default: nil, alias_names: [])
|
59
|
-
@key_name = name
|
60
|
-
@consumer_names = [name] + alias_names
|
61
|
-
@default_value = default
|
62
|
-
@normalizer = normalizer
|
63
|
-
@validator = validator
|
64
|
-
end
|
65
|
-
|
66
|
-
def consume(final_args, args)
|
67
|
-
@consumer_names.each do |name|
|
68
|
-
return if consume_name(final_args, args, name)
|
69
|
-
end
|
70
|
-
|
71
|
-
final_args[@key_name] = @default_value unless @default_value.nil?
|
72
|
-
end
|
73
|
-
|
74
|
-
def consume_name(final_args, args, consumer_name)
|
75
|
-
if args.key?(consumer_name)
|
76
|
-
value = @normalizer.call(args.delete(consumer_name))
|
77
|
-
@validator.call(value) if @validator
|
78
|
-
final_args[@key_name] = value
|
79
|
-
true
|
80
|
-
else
|
81
|
-
false
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
27
|
+
class Kwargs
|
86
28
|
class Builder
|
87
29
|
def initialize
|
88
30
|
@arguments = []
|
89
|
-
@
|
90
|
-
@final_consumer = nil
|
31
|
+
@handle_extras = ExtrasConsumers::RaiseError
|
91
32
|
end
|
92
33
|
|
93
34
|
def key(*names, **options)
|
94
35
|
names.each do |name|
|
95
|
-
@arguments <<
|
36
|
+
@arguments << ArgumentConsumer.new(name: name, **options)
|
96
37
|
end
|
97
38
|
end
|
98
39
|
|
99
|
-
def
|
100
|
-
@
|
40
|
+
def handle_extras(sym)
|
41
|
+
@handle_extras = ExtrasConsumers.resolve_handle_extras(sym)
|
101
42
|
end
|
102
43
|
|
103
|
-
def
|
104
|
-
@
|
44
|
+
def after_handler(&blk)
|
45
|
+
@after_handler = blk
|
105
46
|
end
|
106
47
|
|
107
|
-
def build
|
108
|
-
|
48
|
+
def build(base_consumer = nil)
|
49
|
+
consumers = [base_consumer, *@arguments].compact
|
50
|
+
consumers = CompositeConsumer.new(*consumers)
|
51
|
+
Checker.new(arguments_consumer: consumers, extras_consumer: @handle_extras, after_handler: @after_handler)
|
109
52
|
end
|
110
53
|
|
111
|
-
def self.build(&block)
|
54
|
+
def self.build(base_checker = nil, &block)
|
112
55
|
builder = Builder.new
|
113
56
|
builder.instance_exec &block
|
114
|
-
builder.build
|
57
|
+
builder.build(base_checker&.arguments_consumer)
|
115
58
|
end
|
116
59
|
end
|
117
60
|
end
|
@@ -2,33 +2,34 @@
|
|
2
2
|
|
3
3
|
module Meta
|
4
4
|
module Utils
|
5
|
-
class
|
6
|
-
|
7
|
-
|
8
|
-
# 将 options 内的值修正为固定值,该方法会原地修改 options 选项。
|
9
|
-
# 如果 options 中的缺失相应的值,则使用 fixed_values 中的值补充;如果 options 中的值不等于 fixed_values 中对应的值,则抛出异常。
|
10
|
-
# 示例:
|
11
|
-
# (1)fix!({}, { a: 1, b: 2 }) # => { a: 1, b: 2 }
|
12
|
-
# (2)fix!({ a: 1 }, { a: 2 }) # raise error
|
13
|
-
def fix!(options, fixed_values)
|
14
|
-
fixed_values.each do |key, value|
|
15
|
-
if options.include?(key)
|
16
|
-
if options[key] != value
|
17
|
-
raise ArgumentError, "关键字参数 #{key} 的值不正确,必须为 #{value}"
|
18
|
-
end
|
19
|
-
else
|
20
|
-
options[key] = value
|
21
|
-
end
|
22
|
-
end
|
23
|
-
options
|
24
|
-
end
|
5
|
+
class Kwargs
|
6
|
+
class Checker
|
7
|
+
attr_reader :arguments_consumer
|
25
8
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
9
|
+
def initialize(arguments_consumer:, extras_consumer: nil, after_handler: nil)
|
10
|
+
@arguments_consumer = arguments_consumer
|
11
|
+
@extras_consumer = extras_consumer || ExtrasConsumers::RaiseError
|
12
|
+
@after_handler = after_handler
|
13
|
+
end
|
14
|
+
|
15
|
+
def check(args, extras_handler: nil)
|
16
|
+
# 准备工作
|
17
|
+
args = args.dup
|
18
|
+
final_args = {}
|
19
|
+
|
20
|
+
# 逐个消费参数
|
21
|
+
@arguments_consumer.consume(final_args, args)
|
22
|
+
|
23
|
+
# 处理额外参数
|
24
|
+
extras_consumer = ExtrasConsumers.resolve_handle_extras(extras_handler)
|
25
|
+
extras_consumer ||= @extras_consumer
|
26
|
+
extras_consumer&.consume(final_args, args)
|
27
|
+
|
28
|
+
# 后置处理器
|
29
|
+
@after_handler&.call(final_args)
|
30
|
+
|
31
|
+
# 返回最终参数
|
32
|
+
final_args
|
32
33
|
end
|
33
34
|
end
|
34
35
|
end
|