meta-api 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bc6f6c3dddfd1e683cae7147be60bd7ead57e46b097700fc1db4113d12f08965
4
- data.tar.gz: 476caee521969debd84614433ab41be89fbe7d1e5b074f46a6c8df40ca2d2c43
3
+ metadata.gz: a3fc58de696caf4cf3abade846de9af53bb6491bda575b361461aa67f07d2fb9
4
+ data.tar.gz: 68d00c2d43086af73a10bbe6bfc103a5ee8611751b01ad2b9c811edf9197438a
5
5
  SHA512:
6
- metadata.gz: 3e213a983004b7fed1d15c2e0b41c9746584d259af6f969c631669b7efbb63f5573ee67b61ca5cb95888738925ca525e2ae4045502a7998c57c96432c12e3fd7
7
- data.tar.gz: b575cf5145a3693ceb14296178c25c1ab1f8bf23e2a7dac92e9cc131a82dd79649d101f066c716069a5cb99114c138842b7bde89734790f217095fd459bf8fb4
6
+ metadata.gz: 18cf46619c9f286fe01fbc3b941a6ca5a5e380740ad94e4d6827040cd9c6a014f6249eb13bc609a9f56fe2c15da6e044250f610b0eafdf6202aa9b6a16a0f15b
7
+ data.tar.gz: edc1532a86fe8776f061cfd1f550392ba280a4e693437b6d6260bfcfce8f0a33d7ae66e6f95250fb8530751307751a53857c0fd2a32a57ec9f8764bf6a9037da
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # 更新日志
2
2
 
3
+ ## 0.0.5(2023 年 4 月 18 日)
4
+
5
+ 1. 调整了 `around` 钩子的执行顺序,它与 `before` 钩子共同定义顺序。
6
+ 2. 修复了若干 bug.
7
+
3
8
  ## 0.0.4(2023 年 4 月 18 日)
4
9
 
5
10
  1. `Application` 添加 `.around` 宏。
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- meta-api (0.0.4)
4
+ meta-api (0.0.5)
5
5
 
6
6
  GEM
7
7
  remote: https://gems.ruby-china.com/
@@ -63,4 +63,4 @@ DEPENDENCIES
63
63
  rspec
64
64
 
65
65
  BUNDLED WITH
66
- 2.3.17
66
+ 2.3.7
data/README.md CHANGED
@@ -17,7 +17,7 @@ $ git clone https://github.com/yetrun/web-frame-example.git
17
17
  在 Gemfile 中添加:
18
18
 
19
19
  ```ruby
20
- gem 'meta-api', '~> 0.0.3' # Meta 框架处于快速开发阶段,引入时应尽量固定版本
20
+ gem 'meta-api', '~> 0.0.5' # Meta 框架处于快速开发阶段,引入时应尽量固定版本
21
21
  ```
22
22
 
23
23
  然后在 Ruby 代码中引用:
@@ -1,6 +1,14 @@
1
1
  zh-CN:
2
2
  json_schema:
3
+ type_names:
4
+ basic: "基本类型"
5
+ array: "数组类型"
3
6
  errors:
4
- required: '未提供'
5
- format: '格式不正确'
6
- allowable: '不在允许的值范围内'
7
+ required: "未提供"
8
+ format: "格式不正确"
9
+ allowable: "不在允许的值范围内"
10
+ type_convert:
11
+ basic: "类型转化失败,期望得到一个 `%{target_type}` 类型,但值 `%{value}` 无法转化"
12
+ array: "转化为数组类型时期望对象拥有 `to_a` 方法"
13
+ object: "类型转化失败,期望得到一个 `object` 类型,但值 `%{value}` 是一个%{real_type}"
14
+ unknown: "未知的目标类型 `%{target_type}`"
@@ -147,14 +147,14 @@ end
147
147
  ```ruby
148
148
  class DemoApp < Meta::Application
149
149
  before { puts 1 }
150
- before { puts 2 }
151
150
  around { |next_action|
152
- puts 3
151
+ puts 2
153
152
  next_action.execute(self)
154
- puts 5
153
+ puts 7
155
154
  }
155
+ before { puts 3 }
156
+ after { puts 5 }
156
157
  after { puts 6 }
157
- after { puts 7 }
158
158
 
159
159
  get '/request' do
160
160
  puts 4
@@ -164,11 +164,10 @@ end
164
164
 
165
165
  所有钩子的执行顺序是:
166
166
 
167
- 1. 首先执行 `before` 钩子,按照定义的顺序执行。
168
- 2. 然后执行 `around` 钩子的前半部分,按照定义的顺序执行。
169
- 3. 然后执行路由方法。
170
- 4. 然后执行 `around` 钩子的后半部分,按照定义的顺序执行。
171
- 5. 最后执行 `after` 钩子,按照定义的顺序执行。
167
+ 1. `before` 钩子和 `around` 钩子的前半部分,按照定义的顺序执行
168
+ 2. 然后执行路由方法。
169
+ 3. 然后执行 `after` 钩子,按照定义的顺序执行。
170
+ 4. 最后执行 `around` 钩子的后半部分,按照定义的逆序执行。
172
171
 
173
172
  ### 异常拦截
174
173
 
@@ -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/
@@ -123,7 +123,7 @@ module Meta
123
123
  if request_body.empty?
124
124
  json = {}
125
125
  elsif !request.content_type.start_with?('application/json')
126
- raise Errors::UnsupportedContentType, "只接受 Content-Type 为 application/json 的请求参数"
126
+ raise Errors::UnsupportedContentType, "只接受 Content-Type 为 application/json 的请求参数,当前格式:#{request.content_type}"
127
127
  else
128
128
  json = JSON.parse(request_body)
129
129
  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 = {})
@@ -28,11 +28,11 @@ module Meta
28
28
  {
29
29
  name: name,
30
30
  in: options[:in],
31
- required: property_options[:required] || nil,
31
+ required: property_options.key?(:required) ? property_options[:required] : false,
32
32
  description: property_options[:description] || '',
33
33
  schema: {
34
34
  type: property_options[:type]
35
- }
35
+ }.compact
36
36
  }.compact
37
37
  end unless parameters.empty?
38
38
  end
@@ -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_description.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_description.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.5"
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.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - yetrun
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-04-18 00:00:00.000000000 Z
11
+ date: 2023-04-27 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: 一个 Web API 框架,该框架采用定义元信息的方式编写 API,并同步生成 API 文档
14
14
  email: