meta-api 0.0.4 → 0.0.6
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 +11 -0
- data/Gemfile.lock +2 -2
- data/README.md +32 -21
- data/config/locales/zh-CN.yml +11 -3
- data/docs//346/225/231/347/250/213.md +513 -171
- data/examples/rails_app/Gemfile.lock +1 -1
- data/lib/meta/application/execution.rb +27 -18
- data/lib/meta/application/metadata.rb +1 -1
- data/lib/meta/application/parameters.rb +17 -9
- data/lib/meta/application/route.rb +6 -8
- data/lib/meta/errors.rb +1 -1
- data/lib/meta/json_schema/builders/schema_builder_tool.rb +1 -1
- data/lib/meta/json_schema/schemas/base_schema.rb +2 -0
- data/lib/meta/json_schema/support/type_converter.rb +13 -11
- data/lib/meta/route_dsl/application_builder.rb +18 -10
- data/lib/meta/route_dsl/around_action_builder.rb +30 -3
- data/lib/meta/route_dsl/route_builder.rb +10 -4
- data/meta-api.gemspec +1 -1
- metadata +6 -6
@@ -100,22 +100,36 @@ module Meta
|
|
100
100
|
end
|
101
101
|
|
102
102
|
new_hash = entity_schema.filter(hash, **options, execution: self, stage: :render, validation: ::Meta.config.render_validation, type_conversion: ::Meta.config.render_type_conversion)
|
103
|
+
response.content_type = 'application/json' if response.content_type.nil?
|
103
104
|
response.body = [JSON.generate(new_hash)]
|
104
105
|
else
|
105
106
|
# 渲染多键值结点
|
106
|
-
|
107
|
+
errors = {}
|
108
|
+
final_value = {}
|
109
|
+
renders.each do |key, render_content|
|
107
110
|
raise Errors::RenderingError, "渲染的键名 `#{key}` 不存在,请检查实体定义以确认是否有拼写错误" unless entity_schema.properties.key?(key)
|
108
111
|
schema = entity_schema.properties[key].schema(:render)
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
+
final_value[key] = schema.filter(render_content[:value], **render_content[:options], execution: self, stage: :render)
|
113
|
+
rescue JsonSchema::ValidationErrors => e
|
114
|
+
# 错误信息再度绑定 key
|
115
|
+
errors.merge! e.errors.transform_keys! { |k| k.empty? ? key : "#{key}.#{k}" }
|
112
116
|
end.to_h
|
113
|
-
|
117
|
+
|
118
|
+
if errors.empty?
|
119
|
+
response.content_type = 'application/json' if response.content_type.nil?
|
120
|
+
response.body = [JSON.generate(final_value)]
|
121
|
+
else
|
122
|
+
raise Errors::RenderingInvalid.new(errors)
|
123
|
+
end
|
114
124
|
end
|
115
125
|
rescue JsonSchema::ValidationErrors => e
|
116
126
|
raise Errors::RenderingInvalid.new(e.errors)
|
117
127
|
end
|
118
128
|
|
129
|
+
def abort_execution!
|
130
|
+
raise Abort
|
131
|
+
end
|
132
|
+
|
119
133
|
private
|
120
134
|
|
121
135
|
def parse_raw_params
|
@@ -123,7 +137,7 @@ module Meta
|
|
123
137
|
if request_body.empty?
|
124
138
|
json = {}
|
125
139
|
elsif !request.content_type.start_with?('application/json')
|
126
|
-
raise Errors::UnsupportedContentType, "只接受 Content-Type 为 application/json
|
140
|
+
raise Errors::UnsupportedContentType, "只接受 Content-Type 为 application/json 的请求参数,当前格式:#{request.content_type}"
|
127
141
|
else
|
128
142
|
json = JSON.parse(request_body)
|
129
143
|
end
|
@@ -134,22 +148,18 @@ module Meta
|
|
134
148
|
end
|
135
149
|
|
136
150
|
def parse_request_body_for_replacing
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
raise Errors::ParameterInvalid.new(e.errors)
|
141
|
-
end
|
151
|
+
request_body_schema.filter(params(:raw), stage: :param)
|
152
|
+
rescue JsonSchema::ValidationErrors => e
|
153
|
+
raise Errors::ParameterInvalid.new(e.errors)
|
142
154
|
end
|
143
155
|
|
144
156
|
def parse_request_body_for_updating
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
raise Errors::ParameterInvalid.new(e.errors)
|
149
|
-
end
|
157
|
+
request_body_schema.filter(params(:raw), stage: :param, discard_missing: true)
|
158
|
+
rescue JsonSchema::ValidationErrors => e
|
159
|
+
raise Errors::ParameterInvalid.new(e.errors)
|
150
160
|
end
|
151
161
|
|
152
|
-
class Abort <
|
162
|
+
class Abort < Exception
|
153
163
|
end
|
154
164
|
|
155
165
|
# 使得能够处理 Execution 的类作为 Rack 中间件
|
@@ -162,7 +172,6 @@ module Meta
|
|
162
172
|
execute(execution, request.path)
|
163
173
|
|
164
174
|
response = execution.response
|
165
|
-
response.content_type = 'application/json' unless response.no_content?
|
166
175
|
response.to_a
|
167
176
|
end
|
168
177
|
end
|
@@ -11,15 +11,23 @@ module Meta
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def filter(request)
|
14
|
-
|
14
|
+
errors = {}
|
15
|
+
final_value = {}
|
16
|
+
|
17
|
+
parameters.each do |name, options|
|
15
18
|
schema = options[:schema]
|
16
19
|
value = if options[:in] == 'header'
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
[name
|
22
|
-
|
20
|
+
schema.filter(request.get_header('HTTP_' + name.to_s.upcase.gsub('-', '_')))
|
21
|
+
else
|
22
|
+
schema.filter(request.params[name.to_s])
|
23
|
+
end
|
24
|
+
final_value[name] = value
|
25
|
+
rescue JsonSchema::ValidationError => e
|
26
|
+
errors[name] = e.message
|
27
|
+
end
|
28
|
+
raise Errors::ParameterInvalid.new(errors) unless errors.empty?
|
29
|
+
|
30
|
+
final_value
|
23
31
|
end
|
24
32
|
|
25
33
|
def to_swagger_doc
|
@@ -28,11 +36,11 @@ module Meta
|
|
28
36
|
{
|
29
37
|
name: name,
|
30
38
|
in: options[:in],
|
31
|
-
required: property_options[:required]
|
39
|
+
required: property_options.key?(:required) ? property_options[:required] : false,
|
32
40
|
description: property_options[:description] || '',
|
33
41
|
schema: {
|
34
42
|
type: property_options[:type]
|
35
|
-
}
|
43
|
+
}.compact
|
36
44
|
}.compact
|
37
45
|
end unless parameters.empty?
|
38
46
|
end
|
@@ -20,16 +20,14 @@ module Meta
|
|
20
20
|
def execute(execution, remaining_path)
|
21
21
|
path_matching.merge_path_params(remaining_path, execution.request)
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
execution.parse_request_body(@meta[:request_body]) if @meta[:request_body]
|
23
|
+
execution.parse_parameters(@meta[:parameters]) if @meta[:parameters]
|
24
|
+
execution.parse_request_body(@meta[:request_body]) if @meta[:request_body]
|
26
25
|
|
27
|
-
|
26
|
+
action.execute(execution) if action
|
28
27
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
end
|
28
|
+
render_entity(execution) if @meta[:responses]
|
29
|
+
rescue Execution::Abort
|
30
|
+
nil
|
33
31
|
end
|
34
32
|
|
35
33
|
def match?(execution, remaining_path)
|
data/lib/meta/errors.rb
CHANGED
@@ -23,6 +23,8 @@ module Meta
|
|
23
23
|
|
24
24
|
def initialize(options = {})
|
25
25
|
options = OPTIONS_CHECKER.check(options)
|
26
|
+
raise '不允许 BaseSchema 直接接受 array 类型,必须通过继承使用 ArraySchema' if options[:type] == 'array' && self.class == BaseSchema
|
27
|
+
|
26
28
|
@options = SchemaOptions.normalize(options)
|
27
29
|
end
|
28
30
|
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'bigdecimal'
|
4
|
+
|
3
5
|
module Meta
|
4
6
|
module JsonSchema
|
5
7
|
class ObjectWrapper
|
@@ -29,7 +31,7 @@ module Meta
|
|
29
31
|
@definity_types = {
|
30
32
|
'boolean' => [TrueClass, FalseClass],
|
31
33
|
'integer' => [Integer],
|
32
|
-
'number' => [Integer, Float],
|
34
|
+
'number' => [Integer, Float, BigDecimal],
|
33
35
|
'string' => [String],
|
34
36
|
'array' => [Array],
|
35
37
|
'object' => [Hash, ObjectWrapper]
|
@@ -39,7 +41,7 @@ module Meta
|
|
39
41
|
@boolean_converters = {
|
40
42
|
[String] => lambda do |value|
|
41
43
|
unless %w[true True TRUE false False FALSE].include?(value)
|
42
|
-
raise TypeConvertError,
|
44
|
+
raise TypeConvertError, I18n.t(:'json_schema.errors.type_convert.basic', target_type: 'boolean', value: value)
|
43
45
|
end
|
44
46
|
|
45
47
|
value.downcase == 'true'
|
@@ -50,14 +52,14 @@ module Meta
|
|
50
52
|
[String] => lambda do |value|
|
51
53
|
# 允许的格式:+34、-34、34、34.0 等
|
52
54
|
unless value =~ /^[+-]?\d+(\.0+)?$/
|
53
|
-
raise TypeConvertError,
|
55
|
+
raise TypeConvertError, I18n.t(:'json_schema.errors.type_convert.basic', target_type: 'integer', value: value)
|
54
56
|
end
|
55
57
|
|
56
58
|
value.to_i
|
57
59
|
end,
|
58
|
-
[Float] => lambda do |value|
|
60
|
+
[Float, BigDecimal] => lambda do |value|
|
59
61
|
unless value.to_i == value
|
60
|
-
raise TypeConvertError,
|
62
|
+
raise TypeConvertError, I18n.t(:'json_schema.errors.type_convert.basic', target_type: 'integer', value: value)
|
61
63
|
end
|
62
64
|
|
63
65
|
value.to_i
|
@@ -67,7 +69,7 @@ module Meta
|
|
67
69
|
@number_converters = {
|
68
70
|
[String] => lambda do |value|
|
69
71
|
unless value =~ /^[+-]?\d+(\.\d+)?$/
|
70
|
-
raise TypeConvertError,
|
72
|
+
raise TypeConvertError, I18n.t(:'json_schema.errors.type_convert.basic', target_type: 'number', value: value)
|
71
73
|
end
|
72
74
|
|
73
75
|
float = value.to_f
|
@@ -84,7 +86,7 @@ module Meta
|
|
84
86
|
@array_converters = {
|
85
87
|
[Object] => lambda do |value|
|
86
88
|
unless value.respond_to?(:to_a)
|
87
|
-
raise TypeConvertError,
|
89
|
+
raise TypeConvertError, I18n.t(:'json_schema.errors.type_convert.array')
|
88
90
|
end
|
89
91
|
|
90
92
|
value.to_a
|
@@ -94,9 +96,9 @@ module Meta
|
|
94
96
|
@object_converters = {
|
95
97
|
[Object] => lambda do |value|
|
96
98
|
if [TrueClass, FalseClass, Integer, Float, String].any? { |ruby_type| value.is_a?(ruby_type) }
|
97
|
-
raise TypeConvertError,
|
99
|
+
raise TypeConvertError, I18n.t(:'json_schema.errors.type_convert.object', value: value, real_type: I18n.t(:'json_schema.type_names.basic'))
|
98
100
|
elsif value.is_a?(Array)
|
99
|
-
raise TypeConvertError,
|
101
|
+
raise TypeConvertError, I18n.t(:'json_schema.errors.type_convert.object', value: value, real_type: I18n.t(:'json_schema.type_names.array'))
|
100
102
|
end
|
101
103
|
|
102
104
|
ObjectWrapper.new(value)
|
@@ -106,7 +108,7 @@ module Meta
|
|
106
108
|
class << self
|
107
109
|
def convert_value(value, target_type)
|
108
110
|
return nil if value.nil?
|
109
|
-
raise JsonSchema::TypeConvertError,
|
111
|
+
raise JsonSchema::TypeConvertError, I18n.t(:'json_schema.errors.type_convert.unknown') unless @definity_types.keys.include?(target_type)
|
110
112
|
return value if match_definity_types?(value, target_type)
|
111
113
|
|
112
114
|
convert_to_definity_type(value, target_type)
|
@@ -126,7 +128,7 @@ module Meta
|
|
126
128
|
return converter.call(value)
|
127
129
|
end
|
128
130
|
end
|
129
|
-
raise TypeConvertError,
|
131
|
+
raise TypeConvertError, I18n.t(:'json_schema.errors.type_convert.basic', target_type: target_type, value: value)
|
130
132
|
end
|
131
133
|
end
|
132
134
|
end
|
@@ -10,7 +10,7 @@ module Meta
|
|
10
10
|
|
11
11
|
def initialize(prefix = nil, &block)
|
12
12
|
@mod_prefix = prefix
|
13
|
-
@callbacks =
|
13
|
+
@callbacks = []
|
14
14
|
@error_guards = []
|
15
15
|
@meta_builder = MetaBuilder.new
|
16
16
|
@mod_builders = []
|
@@ -19,7 +19,7 @@ module Meta
|
|
19
19
|
instance_exec &block if block_given?
|
20
20
|
end
|
21
21
|
|
22
|
-
def build(parent_path: '', meta: {}, callbacks:
|
22
|
+
def build(parent_path: '', meta: {}, callbacks: [])
|
23
23
|
# 合并 meta 时不仅仅是覆盖,比如 parameters 参数需要合并
|
24
24
|
meta2 = (meta || {}).merge(@meta_builder.build)
|
25
25
|
if meta[:parameters] && meta2[:parameters]
|
@@ -27,11 +27,10 @@ module Meta
|
|
27
27
|
end
|
28
28
|
|
29
29
|
# 构建子模块
|
30
|
-
callbacks
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
}
|
30
|
+
# 合并父级传递过来的 callbacks,将 before 和 around 放在前面,after 放在后面
|
31
|
+
parent_before = callbacks.filter { |cb| cb[:lifecycle] == :before || cb[:lifecycle] == :around }
|
32
|
+
parent_after = callbacks.filter { |cb| cb[:lifecycle] == :after }
|
33
|
+
callbacks = parent_before + @callbacks + parent_after
|
35
34
|
mods = @mod_builders.map { |builder| builder.build(parent_path: Utils::Path.join(parent_path, @mod_prefix), meta: meta2, callbacks: callbacks) }
|
36
35
|
|
37
36
|
Application.new(
|
@@ -66,15 +65,24 @@ module Meta
|
|
66
65
|
|
67
66
|
# 定义模块内的公共逻辑
|
68
67
|
def before(&block)
|
69
|
-
@callbacks
|
68
|
+
@callbacks << {
|
69
|
+
lifecycle: :before,
|
70
|
+
proc: block
|
71
|
+
}
|
70
72
|
end
|
71
73
|
|
72
74
|
def after(&block)
|
73
|
-
@callbacks
|
75
|
+
@callbacks << {
|
76
|
+
lifecycle: :after,
|
77
|
+
proc: block
|
78
|
+
}
|
74
79
|
end
|
75
80
|
|
76
81
|
def around(&block)
|
77
|
-
@callbacks
|
82
|
+
@callbacks << {
|
83
|
+
lifecycle: :around,
|
84
|
+
proc: block
|
85
|
+
}
|
78
86
|
end
|
79
87
|
|
80
88
|
def rescue_error(error_class, &block)
|
@@ -1,26 +1,53 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# 洋葱圈模型的构建器。
|
4
|
+
#
|
5
|
+
# 因为应用的底层仅使用洋葱圈模型,所以在 DSL 层面,我们需要将 before、after、around 等序列和当前 action 共同构建洋葱圈模型。
|
6
|
+
# 在这个类中,仅仅列出了 before、around、after 三个方法。
|
7
|
+
# 因此第一步,当前执行的 action 会用 before 逻辑代替。
|
8
|
+
# 其次,有必要声明 before、after、around 的执行顺序。before 和 after 的顺序关系容易理解,重点是要关注 around 的执行顺序。
|
9
|
+
# around 的前半部分与 before 的关系:按照声明的顺序执行。
|
10
|
+
# around 与 after 的关系:after 序列会在之前执行,然后是 around 序列的后半部分。
|
11
|
+
|
3
12
|
require_relative '../application/linked_action'
|
4
13
|
|
5
14
|
module Meta
|
6
15
|
module RouteDSL
|
7
16
|
class AroundActionBuilder
|
8
17
|
def initialize
|
9
|
-
@
|
18
|
+
@before = []
|
19
|
+
@after = []
|
10
20
|
end
|
11
21
|
|
12
22
|
def around(&block)
|
13
|
-
@
|
23
|
+
@before << block
|
24
|
+
end
|
25
|
+
|
26
|
+
def before(&block)
|
27
|
+
@before << Proc.new do |next_action|
|
28
|
+
self.instance_exec(&block)
|
29
|
+
next_action.execute(self) if next_action
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def after(&block)
|
34
|
+
# 在洋葱圈模型中,先声明的 after 逻辑会在最后执行,因此为了保证 after 逻辑的执行顺序
|
35
|
+
@after.unshift(Proc.new do |next_action|
|
36
|
+
next_action.execute(self) if next_action
|
37
|
+
self.instance_exec(&block)
|
38
|
+
end)
|
14
39
|
end
|
15
40
|
|
16
41
|
def build
|
17
42
|
# 从后向前构建
|
18
|
-
@
|
43
|
+
(@before + @after).reverse.reduce(nil) do |following, p|
|
19
44
|
LinkedAction.new(p, following)
|
20
45
|
end
|
21
46
|
end
|
22
47
|
|
23
48
|
# 使用 before、after、around 系列和当前 action 共同构建洋葱圈模型。
|
49
|
+
# Note: 该方法可能被废弃!
|
50
|
+
#
|
24
51
|
# 构建成功后,执行顺序是:
|
25
52
|
#
|
26
53
|
# - before 序列
|
@@ -47,10 +47,16 @@ module Meta
|
|
47
47
|
end
|
48
48
|
|
49
49
|
# 构建洋葱圈模型的 LinkedAction
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
50
|
+
# 合并父级传递过来的 callbacks,将 before 和 around 放在前面,after 放在后面
|
51
|
+
parent_before = callbacks.filter { |cb| cb[:lifecycle] == :before || cb[:lifecycle] == :around }
|
52
|
+
parent_after = callbacks.filter { |cb| cb[:lifecycle] == :after }
|
53
|
+
callbacks = parent_before + [{ lifecycle: :before, proc: @action_builder&.build }] + parent_after
|
54
|
+
# 使用 AroundActionBuilder 构建洋葱圈模型
|
55
|
+
around_action_builder = AroundActionBuilder.new
|
56
|
+
callbacks.each do |cb|
|
57
|
+
around_action_builder.send(cb[:lifecycle], &cb[:proc]) if cb[:proc]
|
58
|
+
end
|
59
|
+
action = around_action_builder.build
|
54
60
|
|
55
61
|
Route.new(
|
56
62
|
path: @path,
|
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.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- yetrun
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-05-26 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: 一个 Web API 框架,该框架采用定义元信息的方式编写 API,并同步生成 API 文档
|
14
14
|
email:
|
@@ -130,7 +130,7 @@ metadata:
|
|
130
130
|
allowed_push_host: https://rubygems.org
|
131
131
|
homepage_uri: https://github.com/yetrun/web-frame
|
132
132
|
source_code_uri: https://github.com/yetrun/web-frame.git
|
133
|
-
post_install_message:
|
133
|
+
post_install_message:
|
134
134
|
rdoc_options: []
|
135
135
|
require_paths:
|
136
136
|
- lib
|
@@ -145,8 +145,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
145
145
|
- !ruby/object:Gem::Version
|
146
146
|
version: '0'
|
147
147
|
requirements: []
|
148
|
-
rubygems_version: 3.3.
|
149
|
-
signing_key:
|
148
|
+
rubygems_version: 3.3.26
|
149
|
+
signing_key:
|
150
150
|
specification_version: 4
|
151
151
|
summary: 一个 Web API 框架
|
152
152
|
test_files: []
|