meta-api 0.0.9 → 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: 3394a5e2d3b6dfd45f1478d8477426a4a947253d3252a050dc9c1d7cafd3c3d1
4
- data.tar.gz: 98d12919b2adf824bec651ba5625076ddb7571962cdc66cee188b3b250fd1b24
3
+ metadata.gz: f5f1c4106f4106c1a3e69249e35f4efc02d4ca5d5981d4e32442c380f4228556
4
+ data.tar.gz: 7dc942b0a36759a0953a255b18a7c6c6312159365e9b829b48f223d0d83cd685
5
5
  SHA512:
6
- metadata.gz: c1e80fecb3cf0bce148345660f19a8f44136b1e1647471e89aa9fb4d1c4f55a0ddf7ccb11fdc26ca7880c318173ed4f3254e0835d5bfd96e77048a301804d729
7
- data.tar.gz: 16d7fdf0f82b89155eb0fc655e424d54ba5bf880c984dd322266afdb2952c4c34ff1ce2c83f8d174adcb086057d6c63aa7534b75defd4c3f7ff84f8a7f948ec0
6
+ metadata.gz: 275731f780c2e5eb3dcae314a5b7832504f053092eee8aa3eb1e8b85d7cce0f4fec33bf8f46626cb307d6c7c98e6ff0ee13bf100d6a4610099323d069968d7ae
7
+ data.tar.gz: f3f21b6e62503946e26471f9199809247d0ee3fafa13a2190e57aadb038b47cb19757ed08be4f2054e2b0151c2ed9f1cf4d737c007e133479395aa936d2a338b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
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
+
9
+ ## 0.1.0(2023 年 8 月 5 日)
10
+
11
+ 1. 删除 `on:` 选项。
12
+ 2. `type_conversion` 为 `false` 时不影响对象和数组类型的转化。
13
+ 3. 修复 `ref:` 嵌套造成的文档问题。
14
+ 4. 将 HTTP Method 的 scope 添加 `$` 符号前缀,如 `$get`、`$post` 等。
15
+ 5. `Meta.config` 去掉了 `default_locked_scope` 的配置项。
16
+
3
17
  ## 0.0.9(2023 年 7 月 22 日)
4
18
 
5
19
  1. JsonSchema 添加 before:、after: 选项,用于在过滤前后执行一些操作。
@@ -1,13 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'parameters'
4
+ require_relative 'responses'
4
5
 
5
6
  module Meta
6
7
  class Metadata
7
8
  module ExecutionMethods
8
9
  def parse_parameters(execution)
9
10
  parameters.filter(execution.request)
10
- # TODO: 未捕获 JsonSchema::ValidationErrors 异常
11
+ rescue JsonSchema::ValidationErrors => e
12
+ raise Errors::ParameterInvalid.new(e.errors)
11
13
  end
12
14
 
13
15
  def parse_request_body(execution, discard_missing: false)
@@ -16,7 +18,7 @@ module Meta
16
18
  execution.params(:raw),
17
19
  **Meta.config.json_schema_user_options,
18
20
  **Meta.config.json_schema_param_stage_user_options,
19
- **{ execution: execution, stage: :param, scope: @scope.concat([method]), discard_missing: discard_missing }.compact
21
+ **{ execution: execution, stage: :param, scope: @scope.concat(["$#{method}"]), discard_missing: discard_missing }.compact
20
22
  )
21
23
  rescue JsonSchema::ValidationErrors => e
22
24
  raise Errors::ParameterInvalid.new(e.errors)
@@ -70,7 +72,7 @@ module Meta
70
72
  @tags = tags
71
73
  @parameters = parameters.is_a?(Parameters) ? parameters : Parameters.new(parameters)
72
74
  @request_body = request_body
73
- @responses = responses || {} # || { 204 => nil }
75
+ @responses = responses.is_a?(Responses) ? responses : Responses.new(responses)
74
76
  @scope = scope
75
77
  end
76
78
 
@@ -88,7 +90,7 @@ module Meta
88
90
  operation_object[:parameters] = parameters.to_swagger_doc
89
91
 
90
92
  if request_body
91
- schema = request_body.to_schema_doc(stage: :param, scope: self.scope + scope, schemas: schemas)
93
+ schema = request_body.to_schema_doc(stage: :param, scope: self.scope + scope, schema_docs_mapping: schemas)
92
94
  if schema || true
93
95
  operation_object[:requestBody] = {
94
96
  content: {
@@ -100,16 +102,7 @@ module Meta
100
102
  end
101
103
  end
102
104
 
103
- operation_object[:responses] = responses.transform_values do |schema|
104
- {
105
- description: '', # description 属性必须存在
106
- content: schema ? {
107
- 'application/json' => {
108
- schema: schema.to_schema_doc(stage: :render, scope: self.scope + scope, schemas: schemas)
109
- }
110
- } : nil
111
- }.compact
112
- end unless responses.empty?
105
+ operation_object[:responses] = responses.to_swagger_doc(schemas, scope: self.scope + scope)
113
106
 
114
107
  operation_object.compact
115
108
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Meta
4
+ class Responses
5
+ extend Forwardable
6
+
7
+ attr_reader :responses
8
+
9
+ def initialize(responses = {})
10
+ @responses = responses || {}
11
+ end
12
+
13
+ def_delegators :@responses, :[], :empty?, :length, :keys
14
+
15
+ def to_swagger_doc(schemas, scope:)
16
+ if responses.empty?
17
+ { '200' => { description: '' } }
18
+ else
19
+ responses.transform_values do |schema|
20
+ {
21
+ description: '', # description 属性必须存在
22
+ content: schema ? {
23
+ 'application/json' => {
24
+ schema: schema.to_schema_doc(stage: :render, scope: scope, schema_docs_mapping: schemas)
25
+ }
26
+ } : nil
27
+ }.compact
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -39,7 +39,7 @@ module Meta
39
39
  end
40
40
 
41
41
  def generate_operation_doc(schemas)
42
- meta.generate_operation_doc(schemas, scope: [method])
42
+ meta.generate_operation_doc(schemas, scope: ["$#{method}"])
43
43
  end
44
44
  end
45
45
  end
data/lib/meta/config.rb CHANGED
@@ -4,7 +4,6 @@ require 'hash_to_struct'
4
4
 
5
5
  module Meta
6
6
  DEFAULT_OPTIONS = {
7
- default_locked_scope: nil,
8
7
  json_schema_user_options: {},
9
8
  json_schema_param_stage_user_options: {},
10
9
  json_schema_render_stage_user_options: {}
data/lib/meta/entity.rb CHANGED
@@ -14,45 +14,13 @@ 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(proc { |locked_scope, stage|
18
- generate_schema_name(locked_scope, stage)
19
- })
17
+ @schema_builder.schema_name(self.name) if self.name
20
18
  end
21
19
  end
22
20
 
23
- def_delegators :schema_builder, :property, :param, :expose, :use, :lock, :locked, :schema_name, :to_schema
24
-
25
- def method_missing(method, *args)
26
- if method =~ /^lock_(\w+)$/
27
- schema_builder.send(method, *args)
28
- else
29
- super
30
- end
31
- end
32
-
33
- private
34
-
35
- def generate_schema_name(stage, locked_scopes)
36
- # 匿名类不考虑自动生成名称
37
- return nil unless self.name
38
-
39
- schema_name = self.name.gsub('::', '_')
40
- schema_name = schema_name.delete_suffix('Entity') unless schema_name == 'Entity'
41
-
42
- # 先考虑 stage
43
- case stage
44
- when :param
45
- schema_name += 'Params'
46
- when :render
47
- schema_name += 'Entity'
48
- end
49
-
50
- # 再考虑 locked_scope
51
- scope_suffix = locked_scopes.join('_')
52
- schema_name = "#{schema_name}_#{scope_suffix}" unless scope_suffix.empty?
53
-
54
- schema_name
21
+ def method_missing(method, *args, **kwargs, &)
22
+ schema_builder.send(method, *args, **kwargs, &)
55
23
  end
56
24
  end
57
25
  end
58
- end
26
+ end
@@ -5,10 +5,75 @@ require_relative '../schemas/properties'
5
5
  module Meta
6
6
  module JsonSchema
7
7
  class ObjectSchemaBuilder
8
+ extend Forwardable
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
19
+
20
+ def lock(key, value)
21
+ locked(key => value)
22
+ end
23
+
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
30
+ end
31
+ end
32
+ end
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
+
8
73
  def initialize(options = {}, &)
9
74
  raise 'type 选项必须是 object' if !options[:type].nil? && options[:type] != 'object'
10
75
 
11
- @properties = {}
76
+ @properties = {} # 所有的属性已经生成
12
77
  @required = []
13
78
  @validations = {}
14
79
 
@@ -23,22 +88,12 @@ module Meta
23
88
  instance_exec(&) if block_given?
24
89
  end
25
90
 
26
- # 设置 schema_name.
27
- #
28
- # 一、可以传递一个块,该块会接收 locked_scope 参数,需要返回一个带有 param render 键的 Hash.
29
- # 二、可以传递一个 Hash,它包含 param 和 render 键。
30
- # 三、可以传递一个字符串。
31
- def schema_name(schema_name_resolver)
32
- if schema_name_resolver.is_a?(Proc)
33
- @schema_name_resolver = schema_name_resolver
34
- elsif schema_name_resolver.is_a?(Hash)
35
- @schema_name_resolver = proc { |stage, locked_scopes| schema_name_resolver[stage] }
36
- elsif schema_name_resolver.is_a?(String)
37
- @schema_name_resolver = proc { |stage, locked_scopes| schema_name_resolver }
38
- elsif schema_name_resolver.nil?
39
- @schema_name_resolver = proc { nil }
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
40
95
  else
41
- raise TypeError, "schema_name_resolver 必须是一个 Proc、Hash 或 String,当前是:#{schema_name_resolver.class}"
96
+ @schema_name
42
97
  end
43
98
  end
44
99
 
@@ -55,52 +110,63 @@ module Meta
55
110
  instance_exec(&proc)
56
111
  end
57
112
 
58
- def to_schema(locked_options = nil)
59
- ObjectSchema.new(properties: @properties, options: @options, locked_options: locked_options, schema_name_resolver: @schema_name_resolver)
113
+ def with_common_options(common_options, &block)
114
+ WithCommonOptions.new(self, common_options, &block)
60
115
  end
61
116
 
62
- def lock(key, value)
63
- locked(key => value)
117
+ def scope(scope, options = {}, &)
118
+ with_common_options(**options, scope: scope, &)
64
119
  end
65
120
 
66
- def locked(options)
67
- Locked.new(self, options)
121
+ def params(options = {}, &block)
122
+ with_common_options(**options, render: false, &block)
68
123
  end
69
124
 
70
- private
125
+ def render(options = {}, &block)
126
+ with_common_options(**options, params: false, &block)
127
+ end
71
128
 
72
- def apply_array_scope?(options, block)
73
- options[:type] == 'array' && (options[:items] || block)
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)
74
133
  end
75
134
 
76
- def apply_object_scope?(options, block)
77
- (options[:type] == 'object' || block) && (options[:properties] || block)
135
+ def to_schema(locked_options = nil)
136
+ properties = @schema_name ? NamedProperties.new(@properties, @schema_name) : Properties.new(@properties)
137
+ ObjectSchema.new(properties: properties, options: @options, locked_options: locked_options)
78
138
  end
79
139
 
80
- def method_missing(method, *args)
81
- if method =~ /^lock_(\w+)$/
82
- key = Regexp.last_match(1)
83
- lock(key.to_sym, *args)
84
- else
85
- super
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(',')} 未在实体中定义"
86
155
  end
156
+
157
+ Locked.new(self, **options)
87
158
  end
159
+ include LockedMethodAlias
88
160
 
89
- class Locked
90
- attr_reader :builder, :locked_options
161
+ private
91
162
 
92
- def initialize(builder, locked_options)
93
- @builder = builder
94
- @locked_options = locked_options
95
- end
163
+ def apply_array_scope?(options, block)
164
+ options[:type] == 'array' && (options[:items] || block)
165
+ end
96
166
 
97
- # 当调用 Entity.locked 方法后,生成 schema 的方法会掉到这里面来。
98
- # 在生成 schema 时,locked_options 会覆盖;当生成 schema 文档时,由于缺失 schema_name
99
- # 信息,故而 schema_name 相关的影响就消失不见了。
100
- def to_schema
101
- builder.to_schema(locked_options)
102
- end
167
+ def apply_object_scope?(options, block)
168
+ (options[:type] == 'object' || block) && (options[:properties] || block)
103
169
  end
104
170
  end
105
171
  end
106
- end
172
+ end
@@ -14,11 +14,7 @@ module Meta
14
14
  permit_extras true
15
15
 
16
16
  key :ref, alias_names: [:using], normalizer: ->(entity) {
17
- if Meta.config.default_locked_scope && entity.is_a?(Class) && entity < Meta::Entity
18
- entity.locked(scope: Meta.config.default_locked_scope)
19
- else
20
- entity
21
- end
17
+ entity
22
18
  }
23
19
  key :dynamic_ref, alias_names: [:dynamic_using], normalizer: ->(value) { value.is_a?(Proc) ? { resolve: value } : value }
24
20
  end
@@ -34,7 +30,7 @@ module Meta
34
30
  elsif apply_object_schema?(options, block)
35
31
  ObjectSchemaBuilder.new(options, &block).to_schema
36
32
  else
37
- StagingSchema.build_from_options(options)
33
+ BaseSchema.new(options)
38
34
  end
39
35
  end
40
36
 
@@ -3,6 +3,8 @@
3
3
  module Meta
4
4
  module JsonSchema
5
5
  class ArraySchema < BaseSchema
6
+ extend Forwardable
7
+
6
8
  attr_reader :items
7
9
 
8
10
  def initialize(items, options = {})
@@ -22,10 +24,19 @@ module Meta
22
24
  schema
23
25
  end
24
26
 
27
+ def_delegator :@items, :defined_scopes
28
+
25
29
  private
26
30
 
27
31
  def filter_internal(array_value, user_options)
28
- raise ValidationError.new('参数应该传递一个数组') unless array_value.respond_to?(:each_with_index)
32
+ if array_value.respond_to?(:each_with_index)
33
+ array_value = array_value
34
+ elsif array_value.respond_to?(:to_a)
35
+ array_value = array_value.to_a
36
+ else
37
+ raise ValidationError.new('参数应该传递一个数组或者数组 Like 的对象(实现了 each_with_index 或者 to_a 方法)')
38
+ end
39
+
29
40
  array_value.each_with_index.map do |item, index|
30
41
  begin
31
42
  @items.filter(item, user_options)
@@ -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,15 +103,22 @@ module Meta
103
103
  self
104
104
  end
105
105
 
106
+ # defined_scopes_mapping 是一个 Hash,用于缓存已经计算出的 scopes,用于避免重复计算。其主要针对的是具有命名系统的 Schema,如 Meta::Entity
107
+ def defined_scopes(stage:, defined_scopes_mapping:)
108
+ []
109
+ end
110
+
106
111
  # 执行 if: 选项,返回 true 或 false
107
- def if?(user_options)
108
- 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?
109
115
 
110
- execution = user_options[:execution]
116
+ args_length = if_block.lambda? ? if_block.arity : 1
117
+ args = args_length > 0 ? [object_value] : []
111
118
  if execution
112
- execution.instance_exec(&options[:if])
119
+ execution.instance_exec(*args, &options[:if])
113
120
  else
114
- options[:if]&.call
121
+ options[:if]&.call(*args)
115
122
  end
116
123
  end
117
124
 
@@ -123,7 +130,8 @@ module Meta
123
130
  #
124
131
  # 选项:
125
132
  # - stage: 传递 :param 或 :render
126
- # - schemas: 用于保存已经生成的 Schema
133
+ # - schema_docs_mapping: 用于保存已经生成的 Schema
134
+ # - defined_scopes_mapping: 用于缓存已经定义的 scopes
127
135
  # - presenter: 兼容 Grape 框架的实体类
128
136
  def to_schema_doc(**user_options)
129
137
  return Presenters.to_schema_doc(options[:presenter], options) if options[:presenter]
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+ require_relative 'properties'
5
+
6
+ module Meta
7
+ module JsonSchema
8
+ class NamedProperties < Properties
9
+ attr_reader :schema_base_name
10
+
11
+ def initialize(properties, schema_base_name)
12
+ super(properties)
13
+
14
+ raise TypeError, "schema_name 必须是一个 String,当前是:#{schema_base_name.class}" unless schema_base_name.is_a?(String)
15
+ raise ArgumentError, 'schema_name 不能为 nil 或空字符串' if schema_base_name.nil? || schema_base_name.empty?
16
+
17
+ # 修正 base_name,确保其不包含 Entity 后缀
18
+ schema_base_name = schema_base_name.delete_suffix('Entity') if schema_base_name&.end_with?('Entity')
19
+ @schema_base_name = schema_base_name
20
+ end
21
+
22
+ def schema_name(stage)
23
+ if stage == :render
24
+ "#{schema_base_name}Entity"
25
+ elsif stage == :param
26
+ "#{schema_base_name}Params"
27
+ else
28
+ raise ArgumentError, "stage 必须是 :render 或 :param,当前是:#{stage}"
29
+ end
30
+ end
31
+
32
+ def merge(other_properties)
33
+ raise UnsupportedError, 'NamedProperties 不支持 merge 操作'
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,61 +1,79 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../../utils/kwargs/check'
4
+ require_relative 'named_properties'
4
5
 
5
6
  module Meta
6
7
  module JsonSchema
7
8
  class ObjectSchema < BaseSchema
8
- attr_reader :properties, :locked_options
9
+ attr_reader :properties
10
+ # 只有 ObjectSchema 对象才有 locked_options,因为 locked_options 多是用来锁定属性的行为的,包括:
11
+ # scope:、discard_missing:、exclude: 等
12
+ attr_reader :locked_options
9
13
 
14
+ # stage 和 scope 选项在两个 CHECKER 下都用到了
10
15
  USER_OPTIONS_CHECKER = Utils::KeywordArgs::Builder.build do
11
- permit_extras true
12
-
16
+ 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
+ }
22
+ key :discard_missing, :exclude, :extra_properties, :type_conversion, :validation
23
+ key :execution, :user_data, :object_value
24
+ end
25
+ TO_SCHEMA_DOC_CHECKER = Utils::KeywordArgs::Builder.build do
26
+ key :stage
13
27
  key :scope, normalizer: ->(value) {
14
28
  raise ArgumentError, 'scope 选项不可传递 nil' if value.nil?
15
29
  value = [value] unless value.is_a?(Array)
16
30
  value
17
31
  }
32
+ key :schema_docs_mapping, :defined_scopes_mapping
18
33
  end
19
34
 
20
- def initialize(properties: nil, options: {}, locked_options: {}, schema_name_resolver: proc { nil })
35
+ def initialize(properties:, options: {}, locked_options: {})
36
+ raise ArgumentError, 'properties 必须是 Properties 实例' unless properties.is_a?(Properties)
37
+
21
38
  super(options)
22
39
 
23
40
  @properties = properties || Properties.new({}) # property 包含 stage,stage 包含 scope、schema
24
41
  @properties = Properties.new(@properties) if @properties.is_a?(Hash)
25
42
  @locked_options = USER_OPTIONS_CHECKER.check(locked_options || {})
26
- @schema_name_resolver = schema_name_resolver || proc { nil }
27
- end
28
-
29
- # 复制一个新的 ObjectSchema,只有 options 不同
30
- def dup(options)
31
- self.class.new(
32
- properties: properties,
33
- options: options,
34
- locked_options: locked_options,
35
- schema_name_resolver: @schema_name_resolver
36
- )
37
43
  end
38
44
 
39
45
  def filter(object_value, user_options = {})
40
46
  # 合并 user_options
41
47
  user_options = USER_OPTIONS_CHECKER.check(user_options)
42
- user_options = merge_user_options(user_options, locked_options) if locked_options
48
+ user_options = self.class.merge_user_options(user_options, locked_options) if locked_options
43
49
  super
44
50
  end
45
51
 
46
- # 合并其他的属性,并返回一个新的 ObjectSchema
47
- def merge_other_properties(properties)
48
- ObjectSchema.new(properties: self.properties.merge(properties))
52
+ def naming?
53
+ properties.is_a?(NamedProperties)
54
+ end
55
+
56
+ def defined_scopes(stage:, defined_scopes_mapping:)
57
+ properties.defined_scopes(stage: stage, defined_scopes_mapping: defined_scopes_mapping)
49
58
  end
50
59
 
51
- def resolve_name(stage)
60
+ def resolve_name(stage, user_scopes, defined_scopes)
61
+ raise ArgumentError, 'stage 不能为 nil' if stage.nil?
62
+
63
+ # 先合成外面传进来的 scope
52
64
  locked_scopes = (locked_options || {})[:scope] || []
53
- @schema_name_resolver.call(stage, locked_scopes)
65
+ user_scopes = (user_scopes + locked_scopes).uniq
66
+ scopes = user_scopes & defined_scopes
67
+
68
+ # 再根据 stage 和 scope 生成为当前的 Schema 生成一个合适的名称,要求保证唯一性
69
+ schema_name = properties.schema_name(stage)
70
+ schema_name += "__#{scopes.join('__')}" unless scopes.empty?
71
+ schema_name
54
72
  end
55
73
 
56
74
  def to_schema_doc(user_options = {})
57
- user_options = USER_OPTIONS_CHECKER.check(user_options)
58
- user_options = merge_user_options(user_options, locked_options) if locked_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
59
77
 
60
78
  schema = { type: 'object' }
61
79
  schema[:description] = options[:description] if options[:description]
@@ -80,7 +98,7 @@ module Meta
80
98
  @properties.filter(object_value, user_options)
81
99
  end
82
100
 
83
- def merge_user_options(user_options, locked_options)
101
+ def self.merge_user_options(user_options, locked_options)
84
102
  user_options.merge(locked_options) do |key, user_value, locked_value|
85
103
  if key == :scope
86
104
  user_value + locked_value
@@ -11,7 +11,11 @@ module Meta
11
11
  @properties = properties
12
12
  end
13
13
 
14
+ # user_options 包括 stage, scope, extra_properties, discard_missing, exclude、execution、user_data
14
15
  def filter(object_value, user_options = {})
16
+ # 首先,要将 object_value 转化为 ObjectWrapper
17
+ object_value = JsonObject.wrap(object_value)
18
+
15
19
  # 第一步,根据 user_options[:scope] 需要过滤一些字段
16
20
  stage = user_options[:stage]
17
21
  # 传递一个数字;因为 scope 不能包含数字,这里传递一个数字,使得凡是配置 scope 的属性都会被过滤
@@ -26,7 +30,7 @@ module Meta
26
30
  next false if exclude && exclude.include?(name)
27
31
 
28
32
  # 通过 if 选项过滤
29
- next false unless property_schema.if?(user_options)
33
+ next false unless property_schema.if?(object_value, user_options[:execution])
30
34
 
31
35
  # 默认返回 true
32
36
  next true
@@ -40,6 +44,12 @@ module Meta
40
44
  value = resolve_property_value(object_value, name, property_schema)
41
45
 
42
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
43
53
  object[name] = property_schema.filter(value, **user_options, object_value: object_value)
44
54
  rescue JsonSchema::ValidationErrors => e
45
55
  cause = e.cause || e if cause.nil? # 将第一次出现的错误作为 cause
@@ -65,6 +75,13 @@ module Meta
65
75
  end
66
76
  end
67
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
+
84
+ # user_options 包括 stage, scope, schema_docs_mapping, defined_scopes_mapping
68
85
  def to_swagger_doc(scope: [], stage: nil, **user_options)
69
86
  locked_scopes = scope
70
87
  properties = filter_by(stage: stage, user_scope: locked_scopes)
@@ -78,7 +95,7 @@ module Meta
78
95
  end
79
96
 
80
97
  # 程序中有些地方用到了这三个方法
81
- def_delegators :@properties, :empty?, :key?, :[]
98
+ def_delegators :@properties, :empty?, :key?, :[], :each
82
99
 
83
100
  def merge(properties)
84
101
  self.class.new(@properties.merge(properties.instance_eval { @properties }))
@@ -101,7 +118,7 @@ module Meta
101
118
  def resolve_property_value(object_value, name, property_schema)
102
119
  if property_schema.value?
103
120
  nil
104
- elsif object_value.is_a?(Hash) || object_value.is_a?(ObjectWrapper)
121
+ elsif object_value.is_a?(Hash) || object_value.is_a?(JsonObject)
105
122
  object_value.key?(name.to_s) ? object_value[name.to_s] : object_value[name.to_sym]
106
123
  else
107
124
  raise "不应该还有其他类型了,已经在类型转换中将其转换为 Meta::JsonSchema::ObjectWrapper 了"
@@ -5,31 +5,61 @@ require_relative 'base_schema'
5
5
  module Meta
6
6
  module JsonSchema
7
7
  class RefSchema < BaseSchema
8
- attr_reader :schema
8
+ attr_reader :object_schema
9
+
10
+ def initialize(object_schema, options = {})
11
+ raise ArgumentError, 'object_schema 必须是一个 ObjectSchema' unless object_schema.is_a?(ObjectSchema)
9
12
 
10
- def initialize(schema, options = {})
11
13
  super(options)
12
- @schema = schema
14
+ @object_schema = object_schema
13
15
  end
14
16
 
15
17
  def filter(value, user_options = {})
16
18
  value = super
17
- schema.filter(value, user_options)
19
+ object_schema.filter(value, user_options)
18
20
  end
19
21
 
20
22
  def to_schema_doc(user_options)
21
- schema_name = schema.resolve_name(user_options[:stage])
23
+ raise '引用的 ObjectSchema 没有包含命名逻辑,无法生成文档' unless object_schema.naming?
22
24
 
23
- # 首先将 Schema 写进 schemas 选项中去
24
- schema_components = user_options[:schemas]
25
+ # 首先,要求出 defined_scopes
26
+ defined_scopes = self.defined_scopes(stage: user_options[:stage], defined_scopes_mapping: user_options[:defined_scopes_mapping])
27
+ # 然后,求出 schema_name
28
+ schema_name = object_schema.resolve_name(user_options[:stage], user_options[:scope], defined_scopes)
29
+ # 接着将 Schema 写进 schemas 选项中去
30
+ schema_components = user_options[:schema_docs_mapping] || {}
25
31
  unless schema_components.key?(schema_name)
26
32
  schema_components[schema_name] = nil # 首先设置 schemas 防止出现无限循环
27
- schema_components[schema_name] = schema.to_schema_doc(**user_options) # 原地修改 schemas,无妨
33
+ schema_components[schema_name] = object_schema.to_schema_doc(**user_options) # 原地修改 schemas,无妨
28
34
  end
29
35
 
30
- # 返回的是 $ref 结构
36
+ # 最后,返回这个 $ref 结构
31
37
  { '$ref': "#/components/schemas/#{schema_name}" }
32
38
  end
39
+
40
+ def defined_scopes(stage:, defined_scopes_mapping:)
41
+ defined_scopes_mapping ||= {}
42
+
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
48
+
49
+ defined_scopes_mapping[schema_name] = []
50
+ defined_scopes = object_schema.properties.each.map do |name, property|
51
+ property.defined_scopes(stage: stage, defined_scopes_mapping: defined_scopes_mapping)
52
+ end.flatten.uniq.sort
53
+ defined_scopes_mapping[schema_name] = defined_scopes
54
+ defined_scopes
55
+ end
56
+
57
+ private
58
+
59
+ # # TODO: 这种带有组合方式的 Schema,让我联想到,每次 BaseSchema 新增一个方法都要在子 Schema 中加一遍,很烦!
60
+ # def defined_scopes
61
+ # schema.defined_scopes
62
+ # end
33
63
  end
34
64
  end
35
65
  end
@@ -1,39 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../support/scope_matcher'
4
+
3
5
  module Meta
4
6
  module JsonSchema
5
7
  class ScopingSchema < BaseSchema
6
- attr_reader :on, :schema
7
-
8
- def initialize(required_scope: [], schema:)
9
- raise ArgumentError, 'required_scope 选项不可传递 nil' if required_scope.nil?
10
- required_scope = [required_scope] unless required_scope.is_a?(Array)
8
+ attr_reader :scope_matcher, :schema
11
9
 
12
- @on = required_scope
10
+ def initialize(scope_matcher_options: , schema:)
11
+ @scope_matcher = ScopeMatcher.new(scope_matcher_options)
13
12
  @schema = schema
14
13
  end
15
14
 
16
15
  def scoped(user_scopes)
17
- return schema if (on - user_scopes).empty? # required_scopes 应被消耗殆尽
16
+ @scope_matcher.match?(user_scopes) ? schema : unsupported_schema(user_scopes)
17
+ end
18
18
 
19
- UnsupportedSchema.new(:on, user_scopes)
19
+ def defined_scopes(**kwargs)
20
+ current = scope_matcher.defined_scopes
21
+ deep = schema.defined_scopes(**kwargs)
22
+ (current + deep).uniq
20
23
  end
21
24
 
22
- STAGING_SCHEMA_OPTIONS = Utils::KeywordArgs::Builder.build do
23
- permit_extras true
25
+ private
24
26
 
25
- # TODO: 如果我想把 on 改名为 require_scopes,关键字参数的机制是否支持?
26
- key :on, alias_names: [:scope], default: [], normalizer: ->(required_scopes) {
27
- required_scopes = [] if required_scopes.nil?
28
- required_scopes = [required_scopes] unless required_scopes.is_a?(Array)
29
- required_scopes
30
- }
27
+ def unsupported_schema(user_scopes)
28
+ UnsupportedSchema.new(:scope, user_scopes)
31
29
  end
30
+
32
31
  def self.build_from_options(options, build_schema)
33
- options = STAGING_SCHEMA_OPTIONS.check(options)
34
- required_scope = options.delete(:on) || []
32
+ options = options.dup
33
+ scope_matcher_options = options.delete(:scope)
35
34
  schema = build_schema.call(options)
36
- schema = ScopingSchema.new(required_scope: required_scope, schema: schema) unless required_scope.empty?
35
+ schema = ScopingSchema.new(scope_matcher_options: scope_matcher_options, schema: schema) if scope_matcher_options
37
36
  schema
38
37
  end
39
38
  end
@@ -9,16 +9,14 @@ module Meta
9
9
  module JsonSchema
10
10
  # 内含 param_schema, render_schema, default_schema,分别用于不同的阶段。
11
11
  class StagingSchema < BaseSchema
12
- attr_reader :param_schema, :render_schema, :default_schema
12
+ attr_reader :param_schema, :render_schema
13
13
 
14
- def initialize(param_schema:, render_schema:, default_schema:)
14
+ def initialize(param_schema:, render_schema:)
15
15
  raise ArgumentError, 'param_schema 选项重复提交为 StagingSchema' if param_schema.is_a?(StagingSchema)
16
16
  raise ArgumentError, 'render_schema 选项重复提交为 StagingSchema' if render_schema.is_a?(StagingSchema)
17
- raise ArgumentError, 'default_schema 选项重复提交为 StagingSchema' if default_schema.is_a?(StagingSchema)
18
17
 
19
18
  @param_schema = param_schema
20
19
  @render_schema = render_schema
21
- @default_schema = default_schema
22
20
  end
23
21
 
24
22
  def filter(value, user_options = {})
@@ -27,7 +25,7 @@ module Meta
27
25
  elsif user_options[:stage] == :render
28
26
  render_schema.filter(value, user_options)
29
27
  else
30
- default_schema.filter(value, user_options)
28
+ raise ArgumentError, "stage 选项必须是 :param 或 :render"
31
29
  end
32
30
  end
33
31
 
@@ -37,10 +35,14 @@ module Meta
37
35
  elsif stage == :render
38
36
  render_schema
39
37
  else
40
- default_schema
38
+ raise ArgumentError, "stage 选项必须是 :param 或 :render"
41
39
  end
42
40
  end
43
41
 
42
+ def defined_scopes(stage:, **kwargs)
43
+ staged(stage).defined_scopes(stage: stage, **kwargs)
44
+ end
45
+
44
46
  def self.build_from_options(options, build_schema = ->(opts) { BaseSchema.new(opts) })
45
47
  param_opts, render_opts, common_opts = SchemaOptions.divide_to_param_and_render(options)
46
48
  if param_opts == common_opts && render_opts == common_opts
@@ -48,9 +50,8 @@ module Meta
48
50
  else
49
51
  StagingSchema.new(
50
52
  param_schema: param_opts ? ScopingSchema.build_from_options(param_opts, build_schema) : UnsupportedSchema.new(:stage, :param),
51
- render_schema: render_opts ? ScopingSchema.build_from_options(render_opts, build_schema) : UnsupportedSchema.new(:stage, :render),
52
- default_schema: ScopingSchema.build_from_options(common_opts, build_schema),
53
- )
53
+ render_schema: render_opts ? ScopingSchema.build_from_options(render_opts, build_schema) : UnsupportedSchema.new(:stage, :render)
54
+ )
54
55
  end
55
56
  end
56
57
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ # 将 Ruby 类型包装成 JsonObject 类型,以便可以通过 [key] 访问。同时,保留其他方法的调用,将其转发到原始对象上。
4
+ module Meta
5
+ module JsonSchema
6
+ class JsonObject
7
+ def initialize(target)
8
+ @target = target
9
+ end
10
+
11
+ def __target__
12
+ @target
13
+ end
14
+
15
+ def key?(key)
16
+ @target.respond_to?(key)
17
+ end
18
+
19
+ def [](key)
20
+ @target.__send__(key)
21
+ end
22
+
23
+ def method_missing(method, *args)
24
+ @target.__send__(method, *args)
25
+ end
26
+
27
+ def self.wrap(target)
28
+ case target
29
+ when JsonObject, Hash
30
+ target
31
+ else
32
+ new(target)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Meta
4
+ module JsonSchema
5
+ class ScopeMatcher
6
+ attr_reader :defined_scopes
7
+
8
+ def initialize(query_clause)
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)
11
+
12
+ @match_type, @defined_scopes = query_clause.first
13
+ end
14
+
15
+ def match?(scopes)
16
+ return false if scopes.empty?
17
+
18
+ case @match_type
19
+ when :some_of
20
+ (@defined_scopes & scopes).any?
21
+ when :all_of
22
+ (@defined_scopes - scopes).empty?
23
+ else
24
+ raise "Unknown match type: #{@match_type}"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,31 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'bigdecimal'
4
+ require_relative 'json_object'
4
5
 
5
6
  module Meta
6
7
  module JsonSchema
7
- class ObjectWrapper
8
- def initialize(target)
9
- @target = target
10
- end
11
-
12
- def __target__
13
- @target
14
- end
15
-
16
- def key?(key)
17
- @target.respond_to?(key)
18
- end
19
-
20
- def [](key)
21
- @target.__send__(key)
22
- end
23
-
24
- def method_missing(method, *args)
25
- @target.__send__(method, *args)
26
- end
27
- end
28
-
29
8
  module TypeConverter
30
9
  # 定义客户类型对应的 Ruby 类型
31
10
  @definity_types = {
@@ -34,7 +13,7 @@ module Meta
34
13
  'number' => [Integer, Float, BigDecimal],
35
14
  'string' => [String],
36
15
  'array' => [Array],
37
- 'object' => [Hash, ObjectWrapper]
16
+ 'object' => [Hash, JsonObject]
38
17
  }
39
18
 
40
19
  # 定义从 Ruby 类型转化为对应类型的逻辑
@@ -101,7 +80,7 @@ module Meta
101
80
  raise TypeConvertError, I18n.t(:'json_schema.errors.type_convert.object', value: value, real_type: I18n.t(:'json_schema.type_names.array'))
102
81
  end
103
82
 
104
- ObjectWrapper.new(value)
83
+ JsonObject.new(value)
105
84
  end
106
85
  }
107
86
 
@@ -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
@@ -5,7 +5,7 @@ module Meta
5
5
  class << self
6
6
  def generate(application, info: {}, servers: [])
7
7
  paths_and_routes = get_paths_and_routes!(application)
8
- return generate_from_paths_and_routes(paths_and_routes, info: info, servers: servers)
8
+ generate_from_paths_and_routes(paths_and_routes, info: info, servers: servers)
9
9
  end
10
10
 
11
11
  def generate_from_paths_and_routes(paths_and_routes, info: {}, servers: [])
@@ -62,28 +62,5 @@ module Meta
62
62
  store_routes
63
63
  end
64
64
  end
65
-
66
- class Path
67
- def initialize(parts = [])
68
- @parts = parts.freeze
69
- end
70
-
71
- def append(part)
72
- part = part[1..-1] if part.start_with?('/')
73
- parts = part.split('/')
74
-
75
- self.class.new(@parts + parts)
76
- end
77
-
78
- def to_s
79
- '/' + @parts.join('/')
80
- end
81
-
82
- def self.from_string(path)
83
- path = path[1..-1] if path.start_with?('/')
84
- parts = path.split('/')
85
- self.class.new(parts)
86
- end
87
- end
88
65
  end
89
66
  end
@@ -58,20 +58,20 @@ module Meta
58
58
  def initialize(name:, normalizer: DEFAULT_TRANSFORMER, validator: nil, default: nil, alias_names: [])
59
59
  @key_name = name
60
60
  @consumer_names = [name] + alias_names
61
- # TODO: 当且仅当 value 是 nil 时,才使用 default
62
- @normalizer = default ? ->(value) { normalizer.call(value || default) } : normalizer
61
+ @default_value = default
62
+ @normalizer = normalizer
63
63
  @validator = validator
64
64
  end
65
65
 
66
66
  def consume(final_args, args)
67
67
  @consumer_names.each do |name|
68
- return true if consume_name(final_args, args, name)
68
+ return if consume_name(final_args, args, name)
69
69
  end
70
- return false
70
+
71
+ final_args[@key_name] = @default_value unless @default_value.nil?
71
72
  end
72
73
 
73
74
  def consume_name(final_args, args, consumer_name)
74
- # TODO: default 未起作用
75
75
  if args.key?(consumer_name)
76
76
  value = @normalizer.call(args.delete(consumer_name))
77
77
  @validator.call(value) if @validator
@@ -10,7 +10,7 @@ module Meta
10
10
  final_options[:parameters] = options1[:parameters].merge(options2[:parameters])
11
11
  end
12
12
  if options1[:request_body].is_a?(Meta::JsonSchema::ObjectSchema) && options2[:request_body].is_a?(Meta::JsonSchema::ObjectSchema)
13
- final_options[:request_body] = options1[:request_body].merge_other_properties(options2[:request_body].properties)
13
+ final_options[:request_body] = options1[:request_body].properties.merge(options2[:request_body].properties)
14
14
  end
15
15
  if options1[:responses] && options2[:responses]
16
16
  final_options[:responses] = options1[:responses].merge(options2[:responses])
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.0.9"
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.0.9
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-07-22 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
@@ -42,6 +42,7 @@ files:
42
42
  - lib//meta/application/metadata.rb
43
43
  - lib//meta/application/parameters.rb
44
44
  - lib//meta/application/path_matching_mod.rb
45
+ - lib//meta/application/responses.rb
45
46
  - lib//meta/application/route.rb
46
47
  - lib//meta/config.rb
47
48
  - lib//meta/entity.rb
@@ -56,6 +57,7 @@ files:
56
57
  - lib//meta/json_schema/schemas/array_schema.rb
57
58
  - lib//meta/json_schema/schemas/base_schema.rb
58
59
  - lib//meta/json_schema/schemas/dynamic_schema.rb
60
+ - lib//meta/json_schema/schemas/named_properties.rb
59
61
  - lib//meta/json_schema/schemas/object_schema.rb
60
62
  - lib//meta/json_schema/schemas/properties.rb
61
63
  - lib//meta/json_schema/schemas/ref_schema.rb
@@ -63,8 +65,10 @@ files:
63
65
  - lib//meta/json_schema/schemas/staging_schema.rb
64
66
  - lib//meta/json_schema/schemas/unsupported_schema.rb
65
67
  - lib//meta/json_schema/support/errors.rb
68
+ - lib//meta/json_schema/support/json_object.rb
66
69
  - lib//meta/json_schema/support/presenters.rb
67
70
  - lib//meta/json_schema/support/schema_options.rb
71
+ - lib//meta/json_schema/support/scope_matcher.rb
68
72
  - lib//meta/json_schema/support/type_converter.rb
69
73
  - lib//meta/json_schema/support/validators.rb
70
74
  - lib//meta/load_i18n.rb