meta-api 0.0.9 → 0.1.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: 3394a5e2d3b6dfd45f1478d8477426a4a947253d3252a050dc9c1d7cafd3c3d1
4
- data.tar.gz: 98d12919b2adf824bec651ba5625076ddb7571962cdc66cee188b3b250fd1b24
3
+ metadata.gz: bf3fb639df71a5353328e898711c9758c5e5fd3dd1760409ae823fbd0a7852e2
4
+ data.tar.gz: ea5dd728c3691fcf542d1085fd21d787d727c97b588652de3ffdd53b67512437
5
5
  SHA512:
6
- metadata.gz: c1e80fecb3cf0bce148345660f19a8f44136b1e1647471e89aa9fb4d1c4f55a0ddf7ccb11fdc26ca7880c318173ed4f3254e0835d5bfd96e77048a301804d729
7
- data.tar.gz: 16d7fdf0f82b89155eb0fc655e424d54ba5bf880c984dd322266afdb2952c4c34ff1ce2c83f8d174adcb086057d6c63aa7534b75defd4c3f7ff84f8a7f948ec0
6
+ metadata.gz: 4a9f0a02d8091f77e7f9c2ea0ea67b86f1877327a7b75a7f4342fecd7f2822f9bde2ded59c20cb1c3227eb88a4b6b098c9438710a6a8eedcf4c8defb2f37c572
7
+ data.tar.gz: 2e7911858914bd1b34ff60007f48757f6afb1183c59d15d9e490fb5fe781244522d5500251dcbdbb02fc5f26a17d48ab82bbc2f6dd4368c2540f97bb351f6da3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # 更新日志
2
2
 
3
+ ## 0.1.0(2023 年 8 月 5 日)
4
+
5
+ 1. 删除 `on:` 选项。
6
+ 2. `type_conversion` 为 `false` 时不影响对象和数组类型的转化。
7
+ 3. 修复 `ref:` 嵌套造成的文档问题。
8
+ 4. 将 HTTP Method 的 scope 添加 `$` 符号前缀,如 `$get`、`$post` 等。
9
+ 5. `Meta.config` 去掉了 `default_locked_scope` 的配置项。
10
+
3
11
  ## 0.0.9(2023 年 7 月 22 日)
4
12
 
5
13
  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,9 +14,7 @@ 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
 
@@ -32,6 +30,7 @@ module Meta
32
30
 
33
31
  private
34
32
 
33
+ # TODO: 不需要在 Entity 内自动生成名称了,交给 ObjectSchema::Naming 去做吧
35
34
  def generate_schema_name(stage, locked_scopes)
36
35
  # 匿名类不考虑自动生成名称
37
36
  return nil unless self.name
@@ -5,6 +5,30 @@ require_relative '../schemas/properties'
5
5
  module Meta
6
6
  module JsonSchema
7
7
  class ObjectSchemaBuilder
8
+ module LockedMethodAlias
9
+ # 我在这里说明一下 lock_scope 的逻辑。
10
+ # 1. lock_scope 实际上是将 scope 传递到当前的 ObjectSchema 和它的子 Schema 中。
11
+ # 2. lock_scope 会叠加,也就是如果子 schema 也有 lock_scope,那么子 Schema 会将两个 Schema 合并起来。
12
+ # 3. 调用 filter(scope:) 和 to_schema_doc(scope:) 时,可以传递 scope 参数,这个 scope 遇到 lock_scope 时会合并。
13
+ # 4. 这也就是说,在路由级别定义的 scope 宏会传递到下面的 Schema 中去。
14
+ def add_scope(scope)
15
+ lock_scope(scope)
16
+ end
17
+
18
+ def lock(key, value)
19
+ locked(key => value)
20
+ end
21
+
22
+ def method_missing(method, *args)
23
+ if method =~ /^lock_(\w+)$/
24
+ key = Regexp.last_match(1)
25
+ lock(key.to_sym, *args)
26
+ else
27
+ super
28
+ end
29
+ end
30
+ end
31
+
8
32
  def initialize(options = {}, &)
9
33
  raise 'type 选项必须是 object' if !options[:type].nil? && options[:type] != 'object'
10
34
 
@@ -23,23 +47,10 @@ module Meta
23
47
  instance_exec(&) if block_given?
24
48
  end
25
49
 
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 }
40
- else
41
- raise TypeError, "schema_name_resolver 必须是一个 Proc、Hash 或 String,当前是:#{schema_name_resolver.class}"
42
- end
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
43
54
  end
44
55
 
45
56
  def property(name, options = {}, &block)
@@ -56,16 +67,14 @@ module Meta
56
67
  end
57
68
 
58
69
  def to_schema(locked_options = nil)
59
- ObjectSchema.new(properties: @properties, options: @options, locked_options: locked_options, schema_name_resolver: @schema_name_resolver)
60
- end
61
-
62
- def lock(key, value)
63
- locked(key => value)
70
+ properties = @schema_name ? NamedProperties.new(@properties, @schema_name) : Properties.new(@properties)
71
+ ObjectSchema.new(properties: properties, options: @options, locked_options: locked_options)
64
72
  end
65
73
 
66
74
  def locked(options)
67
- Locked.new(self, options)
75
+ Locked.new(self, **options)
68
76
  end
77
+ include LockedMethodAlias
69
78
 
70
79
  private
71
80
 
@@ -77,29 +86,25 @@ module Meta
77
86
  (options[:type] == 'object' || block) && (options[:properties] || block)
78
87
  end
79
88
 
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
86
- end
87
- end
88
-
89
89
  class Locked
90
- attr_reader :builder, :locked_options
90
+ attr_reader :object_schema_builder, :locked_options
91
91
 
92
- def initialize(builder, locked_options)
93
- @builder = builder
94
- @locked_options = locked_options
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)
95
96
  end
96
97
 
97
- # 当调用 Entity.locked 方法后,生成 schema 的方法会掉到这里面来。
98
- # 在生成 schema 时,locked_options 会覆盖;当生成 schema 文档时,由于缺失 schema_name 的
99
- # 信息,故而 schema_name 相关的影响就消失不见了。
100
98
  def to_schema
101
- builder.to_schema(locked_options)
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)
102
106
  end
107
+ include LockedMethodAlias
103
108
  end
104
109
  end
105
110
  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)
@@ -103,6 +103,10 @@ module Meta
103
103
  self
104
104
  end
105
105
 
106
+ def defined_scopes(stage:, defined_scopes_mapping:)
107
+ []
108
+ end
109
+
106
110
  # 执行 if: 选项,返回 true 或 false
107
111
  def if?(user_options)
108
112
  return true if options[:if].nil?
@@ -123,7 +127,8 @@ module Meta
123
127
  #
124
128
  # 选项:
125
129
  # - stage: 传递 :param 或 :render
126
- # - schemas: 用于保存已经生成的 Schema
130
+ # - schema_docs_mapping: 用于保存已经生成的 Schema
131
+ # - defined_scopes_mapping: 用于缓存已经定义的 scopes
127
132
  # - presenter: 兼容 Grape 框架的实体类
128
133
  def to_schema_doc(**user_options)
129
134
  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,86 @@
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
43
  end
28
44
 
29
45
  # 复制一个新的 ObjectSchema,只有 options 不同
30
46
  def dup(options)
47
+ raise UnsupportedError, 'dup 不应该再执行了'
48
+
31
49
  self.class.new(
32
50
  properties: properties,
33
51
  options: options,
34
52
  locked_options: locked_options,
35
- schema_name_resolver: @schema_name_resolver
36
53
  )
37
54
  end
38
55
 
39
56
  def filter(object_value, user_options = {})
40
57
  # 合并 user_options
41
58
  user_options = USER_OPTIONS_CHECKER.check(user_options)
42
- user_options = merge_user_options(user_options, locked_options) if locked_options
59
+ user_options = self.class.merge_user_options(user_options, locked_options) if locked_options
43
60
  super
44
61
  end
45
62
 
46
- # 合并其他的属性,并返回一个新的 ObjectSchema
47
- def merge_other_properties(properties)
48
- ObjectSchema.new(properties: self.properties.merge(properties))
63
+ def naming?
64
+ properties.is_a?(NamedProperties)
49
65
  end
50
66
 
51
- def resolve_name(stage)
67
+ def resolve_name(stage, user_scopes, defined_scopes)
68
+ raise ArgumentError, 'stage 不能为 nil' if stage.nil?
69
+
70
+ # 先合成外面传进来的 scope
52
71
  locked_scopes = (locked_options || {})[:scope] || []
53
- @schema_name_resolver.call(stage, locked_scopes)
72
+ user_scopes = (user_scopes + locked_scopes).uniq
73
+ scopes = user_scopes & defined_scopes
74
+
75
+ # 再根据 stage 和 scope 生成为当前的 Schema 生成一个合适的名称,要求保证唯一性
76
+ schema_name = properties.schema_name(stage)
77
+ schema_name += "__#{scopes.join('__')}" unless scopes.empty?
78
+ schema_name
54
79
  end
55
80
 
56
81
  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
82
+ user_options = TO_SCHEMA_DOC_CHECKER.check(user_options)
83
+ user_options = self.class.merge_user_options(user_options, locked_options) if locked_options
59
84
 
60
85
  schema = { type: 'object' }
61
86
  schema[:description] = options[:description] if options[:description]
@@ -80,7 +105,7 @@ module Meta
80
105
  @properties.filter(object_value, user_options)
81
106
  end
82
107
 
83
- def merge_user_options(user_options, locked_options)
108
+ def self.merge_user_options(user_options, locked_options)
84
109
  user_options.merge(locked_options) do |key, user_value, locked_value|
85
110
  if key == :scope
86
111
  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 的属性都会被过滤
@@ -65,6 +69,7 @@ module Meta
65
69
  end
66
70
  end
67
71
 
72
+ # user_options 包括 stage, scope, schema_docs_mapping, defined_scopes_mapping
68
73
  def to_swagger_doc(scope: [], stage: nil, **user_options)
69
74
  locked_scopes = scope
70
75
  properties = filter_by(stage: stage, user_scope: locked_scopes)
@@ -78,7 +83,7 @@ module Meta
78
83
  end
79
84
 
80
85
  # 程序中有些地方用到了这三个方法
81
- def_delegators :@properties, :empty?, :key?, :[]
86
+ def_delegators :@properties, :empty?, :key?, :[], :each
82
87
 
83
88
  def merge(properties)
84
89
  self.class.new(@properties.merge(properties.instance_eval { @properties }))
@@ -101,7 +106,7 @@ module Meta
101
106
  def resolve_property_value(object_value, name, property_schema)
102
107
  if property_schema.value?
103
108
  nil
104
- elsif object_value.is_a?(Hash) || object_value.is_a?(ObjectWrapper)
109
+ elsif object_value.is_a?(Hash) || object_value.is_a?(JsonObject)
105
110
  object_value.key?(name.to_s) ? object_value[name.to_s] : object_value[name.to_sym]
106
111
  else
107
112
  raise "不应该还有其他类型了,已经在类型转换中将其转换为 Meta::JsonSchema::ObjectWrapper 了"
@@ -5,31 +5,58 @@ 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
+ schema_name = object_schema.properties.schema_name(stage)
44
+ return defined_scopes_mapping[schema_name] if defined_scopes_mapping.key?(schema_name)
45
+
46
+ defined_scopes_mapping[schema_name] = []
47
+ defined_scopes = object_schema.properties.each.map do |name, property|
48
+ property.defined_scopes(stage: stage, defined_scopes_mapping: defined_scopes_mapping)
49
+ end.flatten.uniq.sort
50
+ defined_scopes_mapping[schema_name] = defined_scopes
51
+ defined_scopes
52
+ end
53
+
54
+ private
55
+
56
+ # # TODO: 这种带有组合方式的 Schema,让我联想到,每次 BaseSchema 新增一个方法都要在子 Schema 中加一遍,很烦!
57
+ # def defined_scopes
58
+ # schema.defined_scopes
59
+ # end
33
60
  end
34
61
  end
35
62
  end
@@ -1,39 +1,36 @@
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
+ scope_matcher.defined_scopes
20
21
  end
21
22
 
22
- STAGING_SCHEMA_OPTIONS = Utils::KeywordArgs::Builder.build do
23
- permit_extras true
23
+ private
24
24
 
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
- }
25
+ def unsupported_schema(user_scopes)
26
+ UnsupportedSchema.new(:scope, user_scopes)
31
27
  end
28
+
32
29
  def self.build_from_options(options, build_schema)
33
- options = STAGING_SCHEMA_OPTIONS.check(options)
34
- required_scope = options.delete(:on) || []
30
+ options = options.dup
31
+ scope_matcher_options = options.delete(:scope)
35
32
  schema = build_schema.call(options)
36
- schema = ScopingSchema.new(required_scope: required_scope, schema: schema) unless required_scope.empty?
33
+ schema = ScopingSchema.new(scope_matcher_options: scope_matcher_options, schema: schema) if scope_matcher_options
37
34
  schema
38
35
  end
39
36
  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
 
@@ -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.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.0.9
4
+ version: 0.1.0
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: 2023-08-05 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