meta-api 0.1.2 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 55ec631feb0038911007c4b1de6ca26fdf595ea84c41eecac046e417ff90a2e2
4
- data.tar.gz: e384752c6ce459cb38adb4123078f10ad835c8f75a73256fddb1af02a8bac3b0
3
+ metadata.gz: 176e67a6d2a121ef7841ec614a2104f7f5f201c863ea88eb805e450b9ab13ee4
4
+ data.tar.gz: bd9be253deb822db45e2bf1c0548467652155dbf39255d49709da7b7d5a56726
5
5
  SHA512:
6
- metadata.gz: 19c21dc3c99bebf4adfaea519ffb44d901481c8b88349b1b87677e3155a1c5e75e4c1e639596f589a28c32192e3ed10330f7d3b85dbc4e7238af73b0d8c996ef
7
- data.tar.gz: 3903b5879a3d4dc1092a32b41aa17891434363089a48225acef22267ad6a56416ba7576c6de6ebb4bb8bbb50ebe2bebc7cb5592cf3265bccafe0e3c98e3c670c
6
+ metadata.gz: 2d1e5c80f3699d00bd529b74da7881eb559c089ab0bbb088560599453eea2972dc38dd082d991fc0be5e2815509987e8a3176d4d3fb0b041d35ae4ec5682c665
7
+ data.tar.gz: 59fcbd0be04d0d94a768f5b63e46a5ffcbc3a0a67ed89959ff957f8be4d1f9b7d04551cc090f6cc3a031effe45cebcb369d4a10f31fbc7a5811e2760a7f0b335
data/lib/meta/api.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require_relative 'config'
2
2
  require_relative 'application'
3
+ require_relative 'entity'
3
4
  require_relative 'swagger_doc'
4
5
  require_relative 'scope/base'
5
6
  require_relative 'load_i18n'
@@ -7,37 +7,38 @@ module Meta
7
7
  class ObjectSchemaBuilder
8
8
  extend Forwardable
9
9
 
10
- module LockedMethodAlias
11
- # 我在这里说明一下 lock_scope 的逻辑。
12
- # 1. lock_scope 实际上是将 scope 传递到当前的 ObjectSchema 和它的子 Schema 中。
13
- # 2. lock_scope 会叠加,也就是如果子 schema 也有 lock_scope,那么子 Schema 会将两个 Schema 合并起来。
14
- # 3. 调用 filter(scope:) 和 to_schema_doc(scope:) 时,可以传递 scope 参数,这个 scope 遇到 lock_scope 时会合并。
15
- # 4. 这也就是说,在路由级别定义的 scope 宏会传递到下面的 Schema 中去。
16
- def add_scope(scope)
17
- lock_scope(scope)
18
- end
10
+ class Locked
11
+ # 定义一些 locked 的别名方法
12
+ module LockedMethodAlias
13
+ # 我在这里说明一下 lock_scope 的逻辑。
14
+ # 1. lock_scope 实际上是将 scope 传递到当前的 ObjectSchema 和它的子 Schema 中。
15
+ # 2. lock_scope 会叠加,也就是如果子 schema 也有 lock_scope,那么子 Schema 会将两个 Schema 合并起来。
16
+ # 3. 调用 filter(scope:) 和 to_schema_doc(scope:) 时,可以传递 scope 参数,这个 scope 遇到 lock_scope 时会合并。
17
+ # 4. 这也就是说,在路由级别定义的 scope 宏会传递到下面的 Schema 中去。
18
+ def add_scope(scope)
19
+ lock_scope(scope)
20
+ end
19
21
 
20
- def lock(key, value)
21
- locked(key => value)
22
- end
22
+ def lock(key, value)
23
+ locked(key => value)
24
+ end
23
25
 
24
- def method_missing(method, *args)
25
- if method =~ /^lock_(\w+)$/
26
- key = Regexp.last_match(1)
27
- lock(key.to_sym, *args)
28
- else
29
- super
26
+ def method_missing(method, *args)
27
+ if method =~ /^lock_(\w+)$/
28
+ key = Regexp.last_match(1)
29
+ lock(key.to_sym, *args)
30
+ else
31
+ super
32
+ end
30
33
  end
31
34
  end
32
- end
33
35
 
34
- class Locked
35
36
  attr_reader :object_schema_builder, :locked_options
36
37
 
37
38
  # locked_options 是用户传递的参数,这个参数会被合并到 object_schema_builder 的 locked_options 中。
38
- def initialize(builder, scope: nil, discard_missing: nil, exclude: nil)
39
+ def initialize(builder, **locked_options)
39
40
  @object_schema_builder = builder
40
- @locked_options = ObjectSchema::USER_OPTIONS_CHECKER.check({ scope: scope, discard_missing: discard_missing, exclude: exclude }.compact)
41
+ @locked_options = SchemaOptions::UserOptions::Filter.check(locked_options.compact)
41
42
  end
42
43
 
43
44
  def to_schema
@@ -45,7 +46,7 @@ module Meta
45
46
  end
46
47
 
47
48
  def locked(options)
48
- options = ObjectSchema::USER_OPTIONS_CHECKER.check(options)
49
+ options = SchemaOptions::UserOptions::Filter.check(options)
49
50
  options = ObjectSchema.merge_user_options(locked_options, options)
50
51
  Locked.new(self.object_schema_builder, **options)
51
52
  end
@@ -89,6 +90,8 @@ module Meta
89
90
  def initialize(options = {}, &)
90
91
  raise 'type 选项必须是 object' if !options[:type].nil? && options[:type] != 'object'
91
92
 
93
+ @schema_cache = {} # 用于缓存已经生成的 schema,重复利用
94
+
92
95
  @properties = {} # 所有的属性已经生成
93
96
  @required = []
94
97
  @validations = {}
@@ -98,7 +101,13 @@ module Meta
98
101
  @options = options
99
102
 
100
103
  properties&.each do |name, property_options|
101
- property name, property_options
104
+ if property_options.is_a?(Hash)
105
+ property name, property_options
106
+ elsif property_options.is_a?(BaseSchema)
107
+ @properties[name.to_sym] = property_options
108
+ else
109
+ raise ArgumentError, "属性 #{name} 的类型不正确"
110
+ end
102
111
  end
103
112
 
104
113
  instance_exec(&) if block_given?
@@ -115,6 +124,7 @@ module Meta
115
124
 
116
125
  def property(name, options = {}, &block)
117
126
  @properties[name.to_sym] = Properties.build_property(options, ->(options) { SchemaBuilderTool.build(options, &block) })
127
+ # @properties[name.to_sym] = SchemaBuilderTool.build(options, &block)
118
128
  end
119
129
 
120
130
  alias expose property
@@ -139,7 +149,7 @@ module Meta
139
149
  end
140
150
 
141
151
  def render(options = {}, &block)
142
- with_common_options(**options, params: false, &block)
152
+ with_common_options(**options, param: false, &block)
143
153
  end
144
154
 
145
155
  def merge(schema_builder)
@@ -148,41 +158,33 @@ module Meta
148
158
  @properties.merge!(schema_builder.properties)
149
159
  end
150
160
 
151
- def to_schema(locked_options = nil)
161
+ def within(*properties)
162
+ to_schema.properties.within(*properties)
163
+ end
164
+ alias_method :[], :within
165
+
166
+ def to_schema(locked_options = {})
167
+ locked_options = SchemaOptions::UserOptions::Filter.check(locked_options.compact)
168
+ return @schema_cache[locked_options] if @schema_cache[locked_options]
169
+
152
170
  properties = @schema_name ? NamedProperties.new(@properties, @schema_name) : Properties.new(@properties)
153
- ObjectSchema.new(properties: properties, options: @options, locked_options: locked_options)
171
+ @schema_cache[locked_options] = ObjectSchema.new(properties: properties, options: @options, locked_options: locked_options)
154
172
  end
155
173
 
156
174
  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
-
173
175
  Locked.new(self, **options)
174
176
  end
175
- include LockedMethodAlias
177
+ include Locked::LockedMethodAlias
176
178
 
177
179
  private
178
180
 
179
- def apply_array_scope?(options, block)
180
- options[:type] == 'array' && (options[:items] || block)
181
- end
181
+ def apply_array_scope?(options, block)
182
+ options[:type] == 'array' && (options[:items] || block)
183
+ end
182
184
 
183
- def apply_object_scope?(options, block)
184
- (options[:type] == 'object' || block) && (options[:properties] || block)
185
- end
185
+ def apply_object_scope?(options, block)
186
+ (options[:type] == 'object' || block) && (options[:properties] || block)
187
+ end
186
188
  end
187
189
  end
188
190
  end
@@ -10,16 +10,16 @@ module Meta
10
10
  module JsonSchema
11
11
  class SchemaBuilderTool
12
12
  class << self
13
- SCHEMA_BUILDER_OPTIONS = Utils::KeywordArgs::Builder.build do
14
- permit_extras true
15
-
16
- key :ref, alias_names: [:using], normalizer: ->(entity) { entity }
17
- key :dynamic_ref, alias_names: [:dynamic_using], normalizer: ->(value) { value.is_a?(Proc) ? { resolve: value } : value }
18
- end
19
13
  def build(options = {}, &block)
20
- options = SCHEMA_BUILDER_OPTIONS.check(options)
14
+ options = SchemaOptions::BaseBuildOptions.check(options)
15
+ SchemaOptions.fix_type_option!(options)
21
16
 
22
- if apply_array_schema?(options, block)
17
+ if apply_staging_schema?(options)
18
+ # 原则上,SchemaBuilderTool 不处理 param render scope 选项,这几个选项只会在 property 宏中出现,
19
+ # 并且交由 StagingSchema 和 ScopingSchema 专业处理。
20
+ # 只不过,经过后置修复后可能包含了 param 和 render 选项
21
+ StagingSchema.build_from_options(options)
22
+ elsif apply_array_schema?(options, block)
23
23
  ArraySchemaBuilder.new(options, &block).to_schema
24
24
  elsif apply_ref_schema?(options, block)
25
25
  RefSchemaBuilder.new(options).to_schema
@@ -34,21 +34,32 @@ module Meta
34
34
 
35
35
  private
36
36
 
37
- def apply_array_schema?(options, block)
38
- options[:type] == 'array'
39
- end
37
+ def apply_staging_schema?(options)
38
+ if options.key?(:param)
39
+ return true if options[:param] == false || !options[:param].empty?
40
+ end
41
+ if options.key?(:render)
42
+ return true if options[:render] == false || !options[:render].empty?
43
+ end
44
+ false
45
+ end
40
46
 
41
- def apply_object_schema?(options, block)
42
- (options[:type] == 'object' || options[:type].nil?) && (options[:properties] || block)
43
- end
47
+ def apply_array_schema?(options, block)
48
+ options[:type] == 'array'
49
+ end
44
50
 
45
- def apply_ref_schema?(options, block)
46
- options[:ref] != nil
47
- end
51
+ def apply_object_schema?(options, block)
52
+ options[:properties] || block
53
+ end
54
+
55
+ def apply_ref_schema?(options, block)
56
+ options[:ref] != nil
57
+ end
58
+
59
+ def apply_dynamic_schema?(options, block)
60
+ options[:dynamic_ref] != nil
61
+ end
48
62
 
49
- def apply_dynamic_schema?(options, block)
50
- options[:dynamic_ref] != nil
51
- end
52
63
  end
53
64
  end
54
65
  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
 
6
5
  module Meta
@@ -20,24 +19,6 @@ module Meta
20
19
  # 的地方都会抛出异常(NoMethodError: undefined method `[]' for nil:NilClass)。这种模式
21
20
  # 的案例很多,包括 StagingSchema、RefSchema 等。
22
21
  class BaseSchema
23
- OPTIONS_CHECKER = Utils::KeywordArgs::Builder.build do
24
- key :type, :items, :description, :presenter, :value, :default, :properties, :convert
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 }
28
- key :before, :after
29
- key :if
30
- end
31
-
32
- USER_OPTIONS_CHECKER = Utils::KeywordArgs::Builder.build do
33
- key :execution, :object_value, :type_conversion, :validation, :user_data
34
- key :stage, validator: ->(value) { raise ArgumentError, "stage 只能取值为 :param 或 :render" unless [:param, :render].include?(value) }
35
-
36
- # 以下是 ObjectSchema 需要的选项
37
- # extra_properties 只能取值为 :ignore、:raise_error
38
- key :discard_missing, :extra_properties, :exclude, :scope
39
- end
40
-
41
22
  # `options` 包含了转换器、验证器、文档、选项。
42
23
  #
43
24
  # 由于本对象可继承,基于不同的继承可分别表示基本类型、对象和数组,所以该属
@@ -49,10 +30,7 @@ module Meta
49
30
 
50
31
  def initialize(options = {})
51
32
  raise ArgumentError, 'options 必须是 Hash 类型' unless options.is_a?(Hash)
52
- options = OPTIONS_CHECKER.check(options)
53
- raise '不允许 BaseSchema 直接接受 array 类型,必须通过继承使用 ArraySchema' if options[:type] == 'array' && self.class == BaseSchema
54
-
55
- @options = SchemaOptions.normalize(options).freeze
33
+ @options = SchemaOptions::BaseBuildOptions.check(options)
56
34
  end
57
35
 
58
36
  def filter?
@@ -60,7 +38,7 @@ module Meta
60
38
  end
61
39
 
62
40
  def filter(value, user_options = {})
63
- user_options = USER_OPTIONS_CHECKER.check(user_options)
41
+ user_options = SchemaOptions::UserOptions::Filter.check(user_options)
64
42
 
65
43
  value = value_callback(user_options) if options[:value]
66
44
  value = before_callback(value, user_options) if options[:before]
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../../utils/kwargs/check'
4
3
  require_relative 'named_properties'
5
4
 
6
5
  module Meta
@@ -11,39 +10,6 @@ module Meta
11
10
  # scope:、discard_missing:、exclude: 等
12
11
  attr_reader :locked_options
13
12
 
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
- }
31
- # stage 和 scope 选项在两个 CHECKER 下都用到了
32
- USER_OPTIONS_CHECKER = Utils::KeywordArgs::Builder.build do
33
- key :stage
34
- key :scope, normalizer: normalize_scope
35
- key :discard_missing, :exclude, :extra_properties, :type_conversion, :validation
36
- key :execution, :user_data, :object_value
37
- end
38
- TO_SCHEMA_DOC_CHECKER = Utils::KeywordArgs::Builder.build do
39
- key :stage
40
- key :scope, normalizer: normalize_scope
41
- key :schema_docs_mapping, :defined_scopes_mapping
42
-
43
- # 以下是 filter 用到的选项,讲道理这些选项应该是不需要的,放置这些是为了防止报错
44
- key :discard_missing
45
- end
46
-
47
13
  def initialize(properties:, options: {}, locked_options: {})
48
14
  raise ArgumentError, 'properties 必须是 Properties 实例' unless properties.is_a?(Properties)
49
15
 
@@ -51,19 +17,20 @@ module Meta
51
17
 
52
18
  @properties = properties || Properties.new({}) # property 包含 stage,stage 包含 scope、schema
53
19
  @properties = Properties.new(@properties) if @properties.is_a?(Hash)
54
- @locked_options = USER_OPTIONS_CHECKER.check(locked_options || {})
20
+ @locked_options = SchemaOptions::UserOptions::Filter.check(locked_options || {}, extras_handler: :ignore)
21
+ @locked_to_doc_options = SchemaOptions::UserOptions::ToDoc.check(locked_options || {}, extras_handler: :ignore)
55
22
  end
56
23
 
57
24
  def filter(object_value, user_options = {})
58
25
  # 合并 user_options
59
- user_options = USER_OPTIONS_CHECKER.check(user_options)
26
+ user_options = SchemaOptions::UserOptions::Filter.check(user_options)
60
27
  user_options = self.class.merge_user_options(user_options, locked_options) if locked_options
61
28
  super
62
29
  end
63
30
 
64
31
  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
32
+ user_options = SchemaOptions::UserOptions::ToDoc.check(user_options)
33
+ user_options = self.class.merge_user_options(user_options, @locked_to_doc_options) if @locked_to_doc_options
67
34
 
68
35
  schema = { type: 'object' }
69
36
  schema[:description] = options[:description] if options[:description]
@@ -94,6 +94,10 @@ module Meta
94
94
  self.class.new(@properties.merge(properties.instance_eval { @properties }))
95
95
  end
96
96
 
97
+ def within(*properties)
98
+ self.class.new(@properties.slice(*properties))
99
+ end
100
+
97
101
  def self.build_property(*args)
98
102
  StagingSchema.build_from_options(*args)
99
103
  end
@@ -54,13 +54,6 @@ module Meta
54
54
  defined_scopes_mapping[schema_name] = defined_scopes
55
55
  defined_scopes
56
56
  end
57
-
58
- private
59
-
60
- # # TODO: 这种带有组合方式的 Schema,让我联想到,每次 BaseSchema 新增一个方法都要在子 Schema 中加一遍,很烦!
61
- # def defined_scopes
62
- # schema.defined_scopes
63
- # end
64
57
  end
65
58
  end
66
59
  end
@@ -5,7 +5,6 @@ require_relative '../../scope/utils'
5
5
 
6
6
  module Meta
7
7
  module JsonSchema
8
- # TODO: 作为模块引入
9
8
  class ScopingSchema < BaseSchema
10
9
  attr_reader :scope_matcher, :schema
11
10
 
@@ -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
- @default_options = {
9
- scope: [],
10
- required: false
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
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../application/parameters'
4
- require_relative '../utils/kwargs/checker'
4
+ require_relative '../utils/kwargs/helpers'
5
5
 
6
6
  module Meta
7
7
  module RouteDSL
@@ -18,9 +18,9 @@ module Meta
18
18
  # 修正 path 参数的选项
19
19
  options = options.dup
20
20
  if path_param_names.include?(name) # path 参数
21
- options = Utils::KeywordArgs::Checker.fix!(options, in: 'path', required: true)
21
+ options = Utils::Kwargs::Helpers.fix!(options, in: 'path', required: true)
22
22
  else
23
- options = Utils::KeywordArgs::Checker.merge_defaults!(options, in: 'query')
23
+ options = Utils::Kwargs::Helpers.merge_defaults!(options, in: 'query')
24
24
  end
25
25
 
26
26
  in_op = options.delete(:in)
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'json'
4
- require_relative '../entity' # TODO: 放在这里不太合适
5
4
  require_relative '../application/route'
6
5
  require_relative 'chain_builder'
7
6
  require_relative 'action_builder'
@@ -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::KeywordArgs::Checker.fix!(options, in: 'path', required: true)
19
+ options = Utils::Kwargs::Helpers.fix!(options, in: 'path', required: true)
20
20
  elsif @route_method == :get
21
- options = Utils::KeywordArgs::Checker.merge_defaults!(options, in: 'query')
21
+ options = Utils::Kwargs::Helpers.merge_defaults!(options, in: 'query')
22
22
  else
23
- options = Utils::KeywordArgs::Checker.merge_defaults!(options, in: 'body')
23
+ options = Utils::Kwargs::Helpers.merge_defaults!(options, in: 'body')
24
24
  end
25
25
 
26
26
  if options[:in] == 'body'
@@ -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 KeywordArgs
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 ArgumentError, "不接受额外的关键字参数:#{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
- @permit_extras = false
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 << Argument.new(name: name, **options)
36
+ @arguments << ArgumentConsumer.new(name: name, **options)
96
37
  end
97
38
  end
98
39
 
99
- def permit_extras(value)
100
- @permit_extras = value
40
+ def handle_extras(sym)
41
+ @handle_extras = ExtrasConsumers.resolve_handle_extras(sym)
101
42
  end
102
43
 
103
- def final_consumer(&block)
104
- @final_consumer = block
44
+ def after_handler(&blk)
45
+ @after_handler = blk
105
46
  end
106
47
 
107
- def build
108
- KeywordArgs.new(@arguments, @permit_extras, @final_consumer)
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 KeywordArgs
6
- module Checker
7
- class << self
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
- def merge_defaults!(options, defaults)
27
- defaults.each do |key, value|
28
- options[key] = value unless options[key]
29
- end
30
- options
31
- end
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
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Meta
4
+ module Utils
5
+ class Kwargs
6
+ class ArgumentConsumer
7
+ DEFAULT_TRANSFORMER = ->(value) { value }
8
+
9
+ def initialize(name:, normalizer: DEFAULT_TRANSFORMER, validator: nil, default: nil, alias_names: [])
10
+ @key_name = name
11
+ @consumer_names = [name] + alias_names
12
+ @default_proc = -> { default.dup } if default
13
+ @normalizer = normalizer
14
+ @validator = validator
15
+ end
16
+
17
+ def consume(final_args, args)
18
+ @consumer_names.each do |name|
19
+ return if consume_name(final_args, args, name)
20
+ end
21
+
22
+ if @default_proc
23
+ default_value = @default_proc.call
24
+ final_args[@key_name] = @normalizer.call(default_value)
25
+ end
26
+ end
27
+
28
+ def consume_name(final_args, args, consumer_name)
29
+ if args.key?(consumer_name)
30
+ value = @normalizer.call(args.delete(consumer_name))
31
+ @validator.call(value) if @validator
32
+ final_args[@key_name] = value
33
+ true
34
+ else
35
+ false
36
+ end
37
+ end
38
+ end
39
+
40
+ class ProcConsumer
41
+ def initialize(&blk)
42
+ @block = blk
43
+ end
44
+
45
+ def consume(final_args, args)
46
+ @block.call(final_args, args) if @block
47
+ end
48
+ end
49
+
50
+ class CompositeConsumer
51
+ def initialize(*consumers)
52
+ @consumers = consumers
53
+ end
54
+
55
+ def consume(final_args, args)
56
+ @consumers.each do |consumer|
57
+ consumer.consume(final_args, args)
58
+ end
59
+ end
60
+ end
61
+
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Meta
4
+ module Utils
5
+ class Kwargs
6
+ module ExtrasConsumers
7
+ Ignore = ProcConsumer.new
8
+ Merged = ProcConsumer.new do |final_args, args|
9
+ final_args.merge!(args)
10
+ end
11
+ RaiseError = ProcConsumer.new do |final_args, args|
12
+ extras_keys = args.keys
13
+ raise ArgumentError, "不接受额外的关键字参数:#{extras_keys.join(', ')}" unless extras_keys.empty?
14
+ end
15
+
16
+ def self.resolve_handle_extras(sym)
17
+ return nil if sym.nil?
18
+
19
+ case sym
20
+ when :ignore
21
+ ExtrasConsumers::Ignore
22
+ when :merged
23
+ ExtrasConsumers::Merged
24
+ when :raise_error
25
+ ExtrasConsumers::RaiseError
26
+ else
27
+ raise ArgumentError, "handle_extras 只接受 :ignore, :merged, :raise_error 三种值,当前传递:#{sym}"
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Meta
4
+ module Utils
5
+ class Kwargs
6
+ module Helpers
7
+ class << self
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
25
+
26
+ def merge_defaults!(options, defaults)
27
+ defaults.each do |key, value|
28
+ options[key] = value unless options[key]
29
+ end
30
+ options
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
data/meta-api.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "meta-api"
3
- spec.version = "0.1.2"
3
+ spec.version = "0.2.0"
4
4
  spec.authors = ["yetrun"]
5
5
  spec.email = ["yetrun@foxmail.com"]
6
6
 
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.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - yetrun
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-06-05 00:00:00.000000000 Z
11
+ date: 2024-06-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: hash_to_struct
@@ -85,8 +85,10 @@ files:
85
85
  - lib//meta/scope/utils.rb
86
86
  - lib//meta/swagger_doc.rb
87
87
  - lib//meta/utils/kwargs/builder.rb
88
- - lib//meta/utils/kwargs/check.rb
89
88
  - lib//meta/utils/kwargs/checker.rb
89
+ - lib//meta/utils/kwargs/consumers.rb
90
+ - lib//meta/utils/kwargs/extras_consumers.rb
91
+ - lib//meta/utils/kwargs/helpers.rb
90
92
  - lib//meta/utils/path.rb
91
93
  - lib//meta/utils/route_dsl_builders.rb
92
94
  - meta-api.gemspec
@@ -1,91 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # 运行时检查关键字参数。(已过时,请使用 Builder)
4
- #
5
- # 在 Ruby 3 中,关键字参数有所变化。简单来说,关键字参数和 Hash 类型不再自动转化,并且一般情况下推荐使用关键字参数。
6
- # 但关键字参数还是有稍稍不足之处,比如在做一些复杂的关键字参数定义时。
7
- #
8
- # 这个文件编写了一个方法,帮助我们在运行时检查关键字参数。这样,我们就可以像下面这样笼统的方式定义参数,不必用明确的关
9
- # 键字参数名称。
10
- #
11
- # def method_name(x, y, z, **kwargs); end
12
- # def method_name(x, y, z, kwargs={}); end
13
- #
14
- # 使用示例:
15
- #
16
- # # 返回 { x: 1, y: 2, z: 3 }
17
- # Meta::Utils::KeywordArgs.check(args: { x: 1, y: 2 }, schema: [:x, :y, { z: 3 }])
18
- #
19
- # # 返回 { x: 1, y: 2, z: 4 }
20
- # Meta::Utils::KeywordArgs.check(args: { x: 1, y: 2, z: 4 }, schema: [:x, :y, { z: 3 }])
21
- #
22
- # # Error: `x` is required
23
- # Meta::Utils::KeywordArgs.check(args: { y: 2, z: 3 }, schema: [:x, :y, { z: 3 }])
24
- #
25
- # # Error: `a` is not allowed
26
- # Meta::Utils::KeywordArgs.check(args: { a: 1, y: 2, z: 3 }, schema: [:x, :y, { z: 3 }])
27
-
28
- module Meta
29
- module Utils
30
- class KeywordArgs
31
- class << self
32
- def check(args:, schema:)
33
- schemas = build_schemas(schema)
34
-
35
- # 不接受额外的关键字参数
36
- extras = args.keys - schemas.keys
37
- raise "不接受额外的关键字参数:#{extras.join(', ')}" unless extras.empty?
38
-
39
- # 通过 schema 导出关键字参数
40
- missing = []
41
- result = schemas.map do |name, spec|
42
- if args.include?(name)
43
- [name, args[name]]
44
- elsif spec.include?(:default)
45
- [name, spec[:default]]
46
- else
47
- missing << name
48
- end
49
- end.to_h
50
-
51
- # 检查以上导出过程中是否找到缺失的参数
52
- if missing.empty?
53
- result
54
- else
55
- raise "缺失必要的关键字参数:#{missing.join(', ')}"
56
- end
57
- end
58
-
59
- private
60
-
61
- def build_schemas(spec)
62
- if spec.is_a?(Array)
63
- build_schemas_from_array(spec)
64
- elsif spec.is_a?(Hash)
65
- build_schemas_from_hash(spec)
66
- elsif spec.is_a?(Symbol)
67
- build_schemas_from_symbol(spec)
68
- else
69
- raise "未知的参数类型:#{spec.class}"
70
- end
71
- end
72
-
73
- def build_schemas_from_array(spec_array)
74
- spec_array.inject({}) do |accumulated, val|
75
- accumulated.merge!(build_schemas(val))
76
- end
77
- end
78
-
79
- def build_schemas_from_hash(spec_hash)
80
- spec_hash.transform_values do |val|
81
- { default: val }
82
- end
83
- end
84
-
85
- def build_schemas_from_symbol(spec_symbol)
86
- { spec_symbol => {} }
87
- end
88
- end
89
- end
90
- end
91
- end