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.
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: /home/run/workspace/personal/web-frame
3
3
  specs:
4
- meta-api (0.0.3)
4
+ meta-api (0.0.4)
5
5
 
6
6
  GEM
7
7
  remote: https://gems.ruby-china.com/
@@ -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
- new_hash = renders.map do |key, render_content|
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
- filtered = schema.filter(render_content[:value], **render_content[:options], execution: self, stage: :render)
111
- [key, filtered]
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
- response.body = [JSON.generate(new_hash)]
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
- begin
138
- request_body_schema.filter(params(:raw), stage: :param)
139
- rescue JsonSchema::ValidationErrors => e
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
- begin
146
- request_body_schema.filter(params(:raw), stage: :param, discard_missing: true)
147
- rescue JsonSchema::ValidationErrors => e
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 < StandardError
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
@@ -51,7 +51,7 @@ module Meta
51
51
  }.compact
52
52
  end unless responses.empty?
53
53
 
54
- operation_object
54
+ operation_object.compact
55
55
  end
56
56
 
57
57
  def self.new(meta = {})
@@ -11,15 +11,23 @@ module Meta
11
11
  end
12
12
 
13
13
  def filter(request)
14
- parameters.map do |name, options|
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
- schema.filter(request.get_header('HTTP_' + name.to_s.upcase.gsub('-', '_')))
18
- else
19
- schema.filter(request.params[name.to_s])
20
- end
21
- [name, value]
22
- end.to_h
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] || nil,
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
- begin
24
- execution.parse_parameters(@meta[:parameters]) if @meta[:parameters]
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
- action.execute(execution) if action
26
+ action.execute(execution) if action
28
27
 
29
- render_entity(execution) if @meta[:responses]
30
- rescue Execution::Abort
31
- execution
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
@@ -13,7 +13,7 @@ module Meta
13
13
  end
14
14
 
15
15
  class RenderingInvalid < JsonSchema::ValidationErrors
16
- def initialize(errors)
16
+ def initialize(errors = {})
17
17
  super(errors, "渲染实体异常:#{errors}")
18
18
  end
19
19
  end
@@ -34,7 +34,7 @@ module Meta
34
34
  private
35
35
 
36
36
  def apply_array_schema?(options, block)
37
- options[:type] == 'array' && (options[:items] || options[:ref] || options[:dynamic_ref] || block)
37
+ options[:type] == 'array'
38
38
  end
39
39
 
40
40
  def apply_object_schema?(options, block)
@@ -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, "类型转化失败,期望得到一个 `boolean` 类型,但值 `#{value}` 无法转化"
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, "类型转化失败,期望得到一个 `integer` 类型,但值 `#{value}` 无法转化"
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, "类型转化失败,期望得到一个 `integer` 类型,但值 `#{value}` 无法转化"
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, "类型转化失败,期望得到一个 `number` 类型,但值 `#{value}` 无法转化"
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, "转化为数组类型时期望对象拥有 `to_a` 方法"
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, "类型转化失败,期望得到一个 `object` 类型,但值 `#{value}` 是一个基本类型"
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, "类型转化失败,期望得到一个 `object` 类型,但值 `#{value}` 是一个 `array` 类型"
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, "未知的目标类型 `#{target_type}`" unless @definity_types.keys.include?(target_type)
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, "类型转化失败,期望得到一个 `#{target_type}` 类型,但值 `#{value}` 无法转化"
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 = { before: [], after: [], around: [] }
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 = { # 合并父级传递过来的 callbacks
31
- before: (callbacks[:before] || []) + @callbacks[:before],
32
- around: (callbacks[:around] || []) + @callbacks[:around],
33
- after: @callbacks[:after] + (callbacks[:after] || []),
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[:before] << block
68
+ @callbacks << {
69
+ lifecycle: :before,
70
+ proc: block
71
+ }
70
72
  end
71
73
 
72
74
  def after(&block)
73
- @callbacks[:after] << block
75
+ @callbacks << {
76
+ lifecycle: :after,
77
+ proc: block
78
+ }
74
79
  end
75
80
 
76
81
  def around(&block)
77
- @callbacks[:around] << block
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
- @around = []
18
+ @before = []
19
+ @after = []
10
20
  end
11
21
 
12
22
  def around(&block)
13
- @around << block
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
- @around.reverse.reduce(nil) do |following, p|
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
- action = AroundActionBuilder.build(
51
- action: @action_builder&.build,
52
- **callbacks
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
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "meta-api"
3
- spec.version = "0.0.4"
3
+ spec.version = "0.0.6"
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.4
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-04-18 00:00:00.000000000 Z
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.7
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: []