meta-api 0.0.9 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/lib/meta/application/metadata.rb +7 -14
- data/lib/meta/application/responses.rb +32 -0
- data/lib/meta/application/route.rb +1 -1
- data/lib/meta/config.rb +0 -1
- data/lib/meta/entity.rb +2 -3
- data/lib/meta/json_schema/builders/object_schema_builder.rb +45 -40
- data/lib/meta/json_schema/builders/schema_builder_tool.rb +2 -6
- data/lib/meta/json_schema/schemas/array_schema.rb +12 -1
- data/lib/meta/json_schema/schemas/base_schema.rb +6 -1
- data/lib/meta/json_schema/schemas/named_properties.rb +37 -0
- data/lib/meta/json_schema/schemas/object_schema.rb +40 -15
- data/lib/meta/json_schema/schemas/properties.rb +7 -2
- data/lib/meta/json_schema/schemas/ref_schema.rb +36 -9
- data/lib/meta/json_schema/schemas/scoping_schema.rb +16 -19
- data/lib/meta/json_schema/schemas/staging_schema.rb +10 -9
- data/lib/meta/json_schema/support/json_object.rb +37 -0
- data/lib/meta/json_schema/support/scope_matcher.rb +29 -0
- data/lib/meta/json_schema/support/type_converter.rb +3 -24
- data/lib/meta/swagger_doc.rb +1 -24
- data/lib/meta/utils/kwargs/builder.rb +5 -5
- data/lib/meta/utils/route_dsl_builders.rb +1 -1
- data/meta-api.gemspec +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bf3fb639df71a5353328e898711c9758c5e5fd3dd1760409ae823fbd0a7852e2
|
4
|
+
data.tar.gz: ea5dd728c3691fcf542d1085fd21d787d727c97b588652de3ffdd53b67512437
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
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,
|
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.
|
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
|
data/lib/meta/config.rb
CHANGED
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(
|
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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
60
|
-
|
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 :
|
90
|
+
attr_reader :object_schema_builder, :locked_options
|
91
91
|
|
92
|
-
|
93
|
-
|
94
|
-
@
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
# -
|
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
|
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
|
-
|
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
|
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
|
-
|
47
|
-
|
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
|
-
|
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 =
|
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?(
|
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 :
|
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
|
-
@
|
14
|
+
@object_schema = object_schema
|
13
15
|
end
|
14
16
|
|
15
17
|
def filter(value, user_options = {})
|
16
18
|
value = super
|
17
|
-
|
19
|
+
object_schema.filter(value, user_options)
|
18
20
|
end
|
19
21
|
|
20
22
|
def to_schema_doc(user_options)
|
21
|
-
|
23
|
+
raise '引用的 ObjectSchema 没有包含命名逻辑,无法生成文档' unless object_schema.naming?
|
22
24
|
|
23
|
-
#
|
24
|
-
|
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] =
|
33
|
+
schema_components[schema_name] = object_schema.to_schema_doc(**user_options) # 原地修改 schemas,无妨
|
28
34
|
end
|
29
35
|
|
30
|
-
#
|
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 :
|
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
|
-
|
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
|
-
|
16
|
+
@scope_matcher.match?(user_scopes) ? schema : unsupported_schema(user_scopes)
|
17
|
+
end
|
18
18
|
|
19
|
-
|
19
|
+
def defined_scopes(**kwargs)
|
20
|
+
scope_matcher.defined_scopes
|
20
21
|
end
|
21
22
|
|
22
|
-
|
23
|
-
permit_extras true
|
23
|
+
private
|
24
24
|
|
25
|
-
|
26
|
-
|
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 =
|
34
|
-
|
30
|
+
options = options.dup
|
31
|
+
scope_matcher_options = options.delete(:scope)
|
35
32
|
schema = build_schema.call(options)
|
36
|
-
schema = ScopingSchema.new(
|
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
|
12
|
+
attr_reader :param_schema, :render_schema
|
13
13
|
|
14
|
-
def initialize(param_schema:, render_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
|
-
|
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
|
-
|
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
|
-
|
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,
|
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
|
-
|
83
|
+
JsonObject.new(value)
|
105
84
|
end
|
106
85
|
}
|
107
86
|
|
data/lib/meta/swagger_doc.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
62
|
-
@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
|
68
|
+
return if consume_name(final_args, args, name)
|
69
69
|
end
|
70
|
-
|
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].
|
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
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
|
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-
|
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
|