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 +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
|