meta-api 0.1.0 → 0.1.1

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: bf3fb639df71a5353328e898711c9758c5e5fd3dd1760409ae823fbd0a7852e2
4
- data.tar.gz: ea5dd728c3691fcf542d1085fd21d787d727c97b588652de3ffdd53b67512437
3
+ metadata.gz: f5f1c4106f4106c1a3e69249e35f4efc02d4ca5d5981d4e32442c380f4228556
4
+ data.tar.gz: 7dc942b0a36759a0953a255b18a7c6c6312159365e9b829b48f223d0d83cd685
5
5
  SHA512:
6
- metadata.gz: 4a9f0a02d8091f77e7f9c2ea0ea67b86f1877327a7b75a7f4342fecd7f2822f9bde2ded59c20cb1c3227eb88a4b6b098c9438710a6a8eedcf4c8defb2f37c572
7
- data.tar.gz: 2e7911858914bd1b34ff60007f48757f6afb1183c59d15d9e490fb5fe781244522d5500251dcbdbb02fc5f26a17d48ab82bbc2f6dd4368c2540f97bb351f6da3
6
+ metadata.gz: 275731f780c2e5eb3dcae314a5b7832504f053092eee8aa3eb1e8b85d7cce0f4fec33bf8f46626cb307d6c7c98e6ff0ee13bf100d6a4610099323d069968d7ae
7
+ data.tar.gz: f3f21b6e62503946e26471f9199809247d0ee3fafa13a2190e57aadb038b47cb19757ed08be4f2054e2b0151c2ed9f1cf4d737c007e133479395aa936d2a338b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # 更新日志
2
2
 
3
+ ## 0.1.1(2024 年 6 月 1 日)
4
+
5
+ 1. 添加 `Meta::Entity.with_common_options` 方法,用于更有效地组织字段。
6
+ 2. 临时性地添加 `Meta::Entity.merge` 方法,作为合并其他的实体的暂时性实现。
7
+ 3. scope 分为全局 scope 和局部 scope.
8
+
3
9
  ## 0.1.0(2023 年 8 月 5 日)
4
10
 
5
11
  1. 删除 `on:` 选项。
data/lib/meta/entity.rb CHANGED
@@ -18,40 +18,9 @@ module Meta
18
18
  end
19
19
  end
20
20
 
21
- def_delegators :schema_builder, :property, :param, :expose, :use, :lock, :locked, :schema_name, :to_schema
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
21
+ def method_missing(method, *args, **kwargs, &)
22
+ schema_builder.send(method, *args, **kwargs, &)
54
23
  end
55
24
  end
56
25
  end
57
- end
26
+ 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,49 @@ 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 = common_options.merge(options)
67
+ object_schema_builder.property(name, options, &block)
68
+ end
69
+ end
70
+
71
+ attr_reader :properties
72
+
32
73
  def initialize(options = {}, &)
33
74
  raise 'type 选项必须是 object' if !options[:type].nil? && options[:type] != 'object'
34
75
 
35
- @properties = {}
76
+ @properties = {} # 所有的属性已经生成
36
77
  @required = []
37
78
  @validations = {}
38
79
 
@@ -47,10 +88,13 @@ module Meta
47
88
  instance_exec(&) if block_given?
48
89
  end
49
90
 
50
- def schema_name(schema_base_name)
51
- raise TypeError, "schema_base_name 必须是一个 String,当前是:#{schema_base_name.class}" unless schema_base_name.is_a?(String)
52
-
53
- @schema_name = schema_base_name
91
+ def schema_name(schema_base_name = nil)
92
+ if schema_base_name
93
+ raise TypeError, "schema_base_name 必须是一个 String,当前是:#{schema_base_name.class}" unless schema_base_name.is_a?(String)
94
+ @schema_name = schema_base_name
95
+ else
96
+ @schema_name
97
+ end
54
98
  end
55
99
 
56
100
  def property(name, options = {}, &block)
@@ -66,12 +110,50 @@ module Meta
66
110
  instance_exec(&proc)
67
111
  end
68
112
 
113
+ def with_common_options(common_options, &block)
114
+ WithCommonOptions.new(self, common_options, &block)
115
+ end
116
+
117
+ def scope(scope, options = {}, &)
118
+ with_common_options(**options, scope: scope, &)
119
+ end
120
+
121
+ def params(options = {}, &block)
122
+ with_common_options(**options, render: false, &block)
123
+ end
124
+
125
+ def render(options = {}, &block)
126
+ with_common_options(**options, params: false, &block)
127
+ end
128
+
129
+ def merge(schema_builder)
130
+ schema_builder = schema_builder.schema_builder if schema_builder.respond_to?(:schema_builder)
131
+
132
+ @properties.merge!(schema_builder.properties)
133
+ end
134
+
69
135
  def to_schema(locked_options = nil)
70
136
  properties = @schema_name ? NamedProperties.new(@properties, @schema_name) : Properties.new(@properties)
71
137
  ObjectSchema.new(properties: properties, options: @options, locked_options: locked_options)
72
138
  end
73
139
 
74
140
  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
156
+
75
157
  Locked.new(self, **options)
76
158
  end
77
159
  include LockedMethodAlias
@@ -85,27 +167,6 @@ module Meta
85
167
  def apply_object_scope?(options, block)
86
168
  (options[:type] == 'object' || block) && (options[:properties] || block)
87
169
  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
170
  end
110
171
  end
111
- end
172
+ end
@@ -89,7 +89,7 @@ module Meta
89
89
  end
90
90
 
91
91
  # 返回能够处理 scope 和 stage 的 schema(可以是 self),否则应返回 UnsupportedStageSchema 或 nil.
92
- def find_schema(scope:, stage:)
92
+ def find_schema(stage:, scope:)
93
93
  staged(stage)&.scoped(scope)
94
94
  end
95
95
 
@@ -103,19 +103,22 @@ module Meta
103
103
  self
104
104
  end
105
105
 
106
+ # defined_scopes_mapping 是一个 Hash,用于缓存已经计算出的 scopes,用于避免重复计算。其主要针对的是具有命名系统的 Schema,如 Meta::Entity
106
107
  def defined_scopes(stage:, defined_scopes_mapping:)
107
108
  []
108
109
  end
109
110
 
110
111
  # 执行 if: 选项,返回 true 或 false
111
- def if?(user_options)
112
- return true if options[:if].nil?
112
+ def if?(object_value, execution = nil)
113
+ if_block = options[:if]
114
+ return true if if_block.nil?
113
115
 
114
- execution = user_options[:execution]
116
+ args_length = if_block.lambda? ? if_block.arity : 1
117
+ args = args_length > 0 ? [object_value] : []
115
118
  if execution
116
- execution.instance_exec(&options[:if])
119
+ execution.instance_exec(*args, &options[:if])
117
120
  else
118
- options[:if]&.call
121
+ options[:if]&.call(*args)
119
122
  end
120
123
  end
121
124
 
@@ -42,17 +42,6 @@ module Meta
42
42
  @locked_options = USER_OPTIONS_CHECKER.check(locked_options || {})
43
43
  end
44
44
 
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
45
  def filter(object_value, user_options = {})
57
46
  # 合并 user_options
58
47
  user_options = USER_OPTIONS_CHECKER.check(user_options)
@@ -64,6 +53,10 @@ module Meta
64
53
  properties.is_a?(NamedProperties)
65
54
  end
66
55
 
56
+ def defined_scopes(stage:, defined_scopes_mapping:)
57
+ properties.defined_scopes(stage: stage, defined_scopes_mapping: defined_scopes_mapping)
58
+ end
59
+
67
60
  def resolve_name(stage, user_scopes, defined_scopes)
68
61
  raise ArgumentError, 'stage 不能为 nil' if stage.nil?
69
62
 
@@ -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
@@ -44,6 +44,12 @@ 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
47
53
  object[name] = property_schema.filter(value, **user_options, object_value: object_value)
48
54
  rescue JsonSchema::ValidationErrors => e
49
55
  cause = e.cause || e if cause.nil? # 将第一次出现的错误作为 cause
@@ -69,6 +75,12 @@ module Meta
69
75
  end
70
76
  end
71
77
 
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
+
72
84
  # user_options 包括 stage, scope, schema_docs_mapping, defined_scopes_mapping
73
85
  def to_swagger_doc(scope: [], stage: nil, **user_options)
74
86
  locked_scopes = scope
@@ -40,8 +40,11 @@ module Meta
40
40
  def defined_scopes(stage:, defined_scopes_mapping:)
41
41
  defined_scopes_mapping ||= {}
42
42
 
43
- schema_name = object_schema.properties.schema_name(stage)
44
- return defined_scopes_mapping[schema_name] if defined_scopes_mapping.key?(schema_name)
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] = []
47
50
  defined_scopes = object_schema.properties.each.map do |name, property|
@@ -17,7 +17,9 @@ module Meta
17
17
  end
18
18
 
19
19
  def defined_scopes(**kwargs)
20
- scope_matcher.defined_scopes
20
+ current = scope_matcher.defined_scopes
21
+ deep = schema.defined_scopes(**kwargs)
22
+ (current + deep).uniq
21
23
  end
22
24
 
23
25
  private
@@ -43,7 +43,16 @@ module Meta
43
43
  codes.each { |code| @meta[:responses][code] = entity_schema }
44
44
  end
45
45
 
46
- [:tags, :title, :description, :scope].each do |method_name|
46
+ def scope(scope)
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
+
52
+ @meta[:scope] = scope
53
+ end
54
+
55
+ [:tags, :title, :description].each do |method_name|
47
56
  define_method(method_name) do |value|
48
57
  @meta[method_name] = value
49
58
  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.0"
3
+ spec.version = "0.1.1"
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.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - yetrun
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-08-05 00:00:00.000000000 Z
11
+ date: 2024-06-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: hash_to_struct