meta-api 0.1.1 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f5f1c4106f4106c1a3e69249e35f4efc02d4ca5d5981d4e32442c380f4228556
4
- data.tar.gz: 7dc942b0a36759a0953a255b18a7c6c6312159365e9b829b48f223d0d83cd685
3
+ metadata.gz: 55ec631feb0038911007c4b1de6ca26fdf595ea84c41eecac046e417ff90a2e2
4
+ data.tar.gz: e384752c6ce459cb38adb4123078f10ad835c8f75a73256fddb1af02a8bac3b0
5
5
  SHA512:
6
- metadata.gz: 275731f780c2e5eb3dcae314a5b7832504f053092eee8aa3eb1e8b85d7cce0f4fec33bf8f46626cb307d6c7c98e6ff0ee13bf100d6a4610099323d069968d7ae
7
- data.tar.gz: f3f21b6e62503946e26471f9199809247d0ee3fafa13a2190e57aadb038b47cb19757ed08be4f2054e2b0151c2ed9f1cf4d737c007e133479395aa936d2a338b
6
+ metadata.gz: 19c21dc3c99bebf4adfaea519ffb44d901481c8b88349b1b87677e3155a1c5e75e4c1e639596f589a28c32192e3ed10330f7d3b85dbc4e7238af73b0d8c996ef
7
+ data.tar.gz: 3903b5879a3d4dc1092a32b41aa17891434363089a48225acef22267ad6a56416ba7576c6de6ebb4bb8bbb50ebe2bebc7cb5592cf3265bccafe0e3c98e3c670c
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
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
+
3
11
  ## 0.1.1(2024 年 6 月 1 日)
4
12
 
5
13
  1. 添加 `Meta::Entity.with_common_options` 方法,用于更有效地组织字段。
@@ -6,7 +6,7 @@ zh-CN:
6
6
  errors:
7
7
  required: "未提供"
8
8
  format: "格式不正确"
9
- allowable: "不在允许的值范围内,允许的值包括 [%{allowable_values}]"
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
@@ -1,4 +1,5 @@
1
1
  require_relative 'config'
2
2
  require_relative 'application'
3
3
  require_relative 'swagger_doc'
4
+ require_relative 'scope/base'
4
5
  require_relative 'load_i18n'
@@ -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.concat(["$#{method}"]), discard_missing: discard_missing }.compact
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.concat([method]) }.compact,
30
+ **{ execution: execution, stage: :render, scope: @scope }.compact,
33
31
  **user_options,
34
32
  )
35
33
  end
@@ -39,7 +39,8 @@ module Meta
39
39
  end
40
40
 
41
41
  def generate_operation_doc(schemas)
42
- meta.generate_operation_doc(schemas, scope: ["$#{method}"])
42
+ # 不再自动添加 $post 等 scope
43
+ meta.generate_operation_doc(schemas, scope: [])
43
44
  end
44
45
  end
45
46
  end
data/lib/meta/entity.rb CHANGED
@@ -14,7 +14,12 @@ module Meta
14
14
  def inherited(base)
15
15
  base.instance_eval do
16
16
  @schema_builder = JsonSchema::ObjectSchemaBuilder.new
17
- @schema_builder.schema_name(self.name) if self.name
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
 
@@ -23,4 +28,4 @@ module Meta
23
28
  end
24
29
  end
25
30
  end
26
- end
31
+ end
@@ -63,9 +63,25 @@ module Meta
63
63
  end
64
64
 
65
65
  def property(name, options = {}, &block)
66
- options = common_options.merge(options)
66
+ options = merge_options(options)
67
67
  object_schema_builder.property(name, options, &block)
68
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
69
85
  end
70
86
 
71
87
  attr_reader :properties
@@ -138,21 +154,21 @@ module Meta
138
154
  end
139
155
 
140
156
  def locked(options)
141
- defined_scopes_mapping = {}
142
- # TODO: 将 properties 搞成 Properties 可以吗?
143
- defined_scopes = properties.map do |key, property|
144
- property.defined_scopes(stage: :param, defined_scopes_mapping: defined_scopes_mapping)
145
- end.flatten.uniq
146
-
147
- user_scopes = options[:scope] || []
148
- user_scopes = [user_scopes] unless user_scopes.is_a?(Array)
149
-
150
- # 判断 user_scopes 中提供的局部 scope 是否在 defined_scopes 中
151
- local_user_scopes = user_scopes.reject { |scope| scope.start_with?('$') }
152
- if (local_user_scopes - defined_scopes).any?
153
- extra_scopes = local_user_scopes - defined_scopes
154
- raise ArgumentError, "scope #{extra_scopes.join(',')} 未在实体中定义"
155
- end
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
156
172
 
157
173
  Locked.new(self, **options)
158
174
  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, :allowable
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
@@ -139,7 +141,7 @@ module Meta
139
141
  schema = {}
140
142
  schema[:type] = options[:type] if options[:type]
141
143
  schema[:description] = options[:description] if options[:description]
142
- schema[:enum] = options[:allowable] if options[:allowable]
144
+ schema[:enum] = options[:enum] if options[:enum]
143
145
 
144
146
  schema
145
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: ->(value) {
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: ->(value) {
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: {})
@@ -49,6 +61,18 @@ module Meta
49
61
  super
50
62
  end
51
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
+
52
76
  def naming?
53
77
  properties.is_a?(NamedProperties)
54
78
  end
@@ -57,31 +81,39 @@ module Meta
57
81
  properties.defined_scopes(stage: stage, defined_scopes_mapping: defined_scopes_mapping)
58
82
  end
59
83
 
84
+ # 解析当前 Schema 的名称
85
+ #
86
+ # 名称解析的规则是:
87
+ # 1. 结合基础的 schema_name,如果它是参数,就加上 Params 后缀;如果它是返回值,就加上 Entity 后缀
88
+ # 2. 而后跟上 locked_scope. 这里,会把多余的 scope 去掉。比如,一个实体内部只处理 Admin 的 scope,但是外部传进来了
89
+ # Admin 和 Detail,那么名称只会包括 Admin
60
90
  def resolve_name(stage, user_scopes, defined_scopes)
61
91
  raise ArgumentError, 'stage 不能为 nil' if stage.nil?
62
92
 
63
93
  # 先合成外面传进来的 scope
64
94
  locked_scopes = (locked_options || {})[:scope] || []
65
95
  user_scopes = (user_scopes + locked_scopes).uniq
66
- scopes = user_scopes & defined_scopes
67
96
 
68
97
  # 再根据 stage 和 scope 生成为当前的 Schema 生成一个合适的名称,要求保证唯一性
69
- schema_name = properties.schema_name(stage)
70
- schema_name += "__#{scopes.join('__')}" unless scopes.empty?
71
- schema_name
72
- end
98
+ base_schema_name = properties.schema_name(stage)
73
99
 
74
- def to_schema_doc(user_options = {})
75
- user_options = TO_SCHEMA_DOC_CHECKER.check(user_options)
76
- user_options = self.class.merge_user_options(user_options, locked_options) if locked_options
77
-
78
- schema = { type: 'object' }
79
- schema[:description] = options[:description] if options[:description]
100
+ # 将调用转移到 Scopes 模块下
101
+ resolve_name_helper(base_schema_name, user_scopes, defined_scopes)
102
+ end
80
103
 
81
- properties, required_keys = @properties.to_swagger_doc(**user_options)
82
- schema[:properties] = properties unless properties.empty?
83
- schema[:required] = required_keys unless required_keys.empty?
84
- schema
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('__')
85
117
  end
86
118
 
87
119
  def locked_scope
@@ -44,12 +44,6 @@ module Meta
44
44
  value = resolve_property_value(object_value, name, property_schema)
45
45
 
46
46
  begin
47
- # 如果 property_schema 是 RefSchema,则局部的 scope 不会传递下去
48
- if property_schema.is_a?(RefSchema)
49
- # 只接受全局的 scope,其以 $ 符合开头
50
- new_scopes = user_options[:scope].find_all { |scope| scope.start_with?('$') }
51
- user_options = user_options.merge(scope: new_scopes)
52
- end
53
47
  object[name] = property_schema.filter(value, **user_options, object_value: object_value)
54
48
  rescue JsonSchema::ValidationErrors => e
55
49
  cause = e.cause || e if cause.nil? # 将第一次出现的错误作为 cause
@@ -75,16 +69,9 @@ module Meta
75
69
  end
76
70
  end
77
71
 
78
- def defined_scopes(stage:, defined_scopes_mapping:)
79
- @properties.each_with_object([]) do |(name, property), defined_scopes|
80
- defined_scopes.concat(property.defined_scopes(stage: stage, defined_scopes_mapping: defined_scopes_mapping))
81
- end
82
- end
83
-
84
72
  # user_options 包括 stage, scope, schema_docs_mapping, defined_scopes_mapping
85
73
  def to_swagger_doc(scope: [], stage: nil, **user_options)
86
- locked_scopes = scope
87
- properties = filter_by(stage: stage, user_scope: locked_scopes)
74
+ properties = filter_by(stage: stage, user_scope: scope)
88
75
  required_keys = properties.filter do |key, property_schema|
89
76
  property_schema.options[:required]
90
77
  end.keys
@@ -94,6 +81,12 @@ module Meta
94
81
  [properties, required_keys]
95
82
  end
96
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
+
97
90
  # 程序中有些地方用到了这三个方法
98
91
  def_delegators :@properties, :empty?, :key?, :[], :each
99
92
 
@@ -107,13 +100,15 @@ module Meta
107
100
 
108
101
  private
109
102
 
110
- def filter_by(stage:, user_scope: false)
111
- @properties.transform_values do |property|
112
- property.find_schema(stage: stage, scope: user_scope)
113
- end.filter do |name, schema|
114
- schema.filter?
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
115
111
  end
116
- end
117
112
 
118
113
  def resolve_property_value(object_value, name, property_schema)
119
114
  if property_schema.value?
@@ -47,9 +47,10 @@ module Meta
47
47
  end
48
48
 
49
49
  defined_scopes_mapping[schema_name] = []
50
+ # 求解 defined_scopes,最终结果去重 + 排序
50
51
  defined_scopes = object_schema.properties.each.map do |name, property|
51
52
  property.defined_scopes(stage: stage, defined_scopes_mapping: defined_scopes_mapping)
52
- end.flatten.uniq.sort
53
+ end.flatten.uniq.sort_by(&:name)
53
54
  defined_scopes_mapping[schema_name] = defined_scopes
54
55
  defined_scopes
55
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(scope_matcher_options: , schema:)
11
- @scope_matcher = ScopeMatcher.new(scope_matcher_options)
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
 
@@ -32,7 +36,7 @@ module Meta
32
36
  options = options.dup
33
37
  scope_matcher_options = options.delete(:scope)
34
38
  schema = build_schema.call(options)
35
- schema = ScopingSchema.new(scope_matcher_options: scope_matcher_options, schema: schema) if scope_matcher_options
39
+ schema = ScopingSchema.new(scope_matcher: scope_matcher_options, schema: schema) if scope_matcher_options
36
40
  schema
37
41
  end
38
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 = { some_of: query_clause } if query_clause.is_a?(Array)
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?(scopes)
16
- return false if scopes.empty?
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
- (@defined_scopes & scopes).any?
21
+ # 只要相交就可以
22
+ (@defined_scopes & providing_scopes).any?
21
23
  when :all_of
22
- (@defined_scopes - scopes).empty?
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
- allowable: proc { |value, allowable_values|
29
+ enum: proc { |value, allowable_values|
30
30
  next if value.nil?
31
- raise JsonSchema::ValidationError, I18n.t(:'json_schema.errors.allowable', allowable_values: allowable_values) unless allowable_values.include?(value)
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
 
@@ -45,9 +45,6 @@ module Meta
45
45
 
46
46
  def scope(scope)
47
47
  scope = [scope] unless scope.is_a?(Array)
48
- unless scope.all? { |s| s.start_with?('$') }
49
- raise ArgumentError, 'namespace 和 route 中声明的 scope 必须是全局 scope(以 $ 开头)'
50
- end
51
48
 
52
49
  @meta[:scope] = scope
53
50
  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
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'json'
4
- require_relative '../entity'
4
+ require_relative '../entity' # TODO: 放在这里不太合适
5
5
  require_relative '../application/route'
6
6
  require_relative 'chain_builder'
7
7
  require_relative 'action_builder'
@@ -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
@@ -45,7 +45,7 @@ module Meta
45
45
  final_args.merge!(args)
46
46
  else
47
47
  extras = args.keys
48
- raise "不接受额外的关键字参数:#{extras.join(', ')}" unless extras.empty?
48
+ raise ArgumentError, "不接受额外的关键字参数:#{extras.join(', ')}" unless extras.empty?
49
49
  end
50
50
  end
51
51
 
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.1"
3
+ spec.version = "0.1.2"
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.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: 2024-06-01 00:00:00.000000000 Z
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