meta-api 0.0.5 → 0.0.7

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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -1
  3. data/Gemfile.lock +1 -1
  4. data/README.md +32 -21
  5. data/config/locales/zh-CN.yml +1 -1
  6. data/docs//346/225/231/347/250/213.md +566 -175
  7. data/docs//347/264/242/345/274/225.md +10 -0
  8. data/examples/rails_app/Gemfile.lock +1 -1
  9. data/lib/meta/application/execution.rb +48 -24
  10. data/lib/meta/application/linked_action.rb +7 -9
  11. data/lib/meta/application/metadata.rb +5 -3
  12. data/lib/meta/application/parameters.rb +16 -8
  13. data/lib/meta/application/route.rb +24 -14
  14. data/lib/meta/config.rb +8 -3
  15. data/lib/meta/errors.rb +1 -1
  16. data/lib/meta/json_schema/builders/schema_builder_tool.rb +8 -2
  17. data/lib/meta/json_schema/schemas/base_schema.rb +19 -5
  18. data/lib/meta/json_schema/schemas/object_schema.rb +1 -1
  19. data/lib/meta/json_schema/schemas/properties.rb +11 -2
  20. data/lib/meta/json_schema/support/type_converter.rb +2 -2
  21. data/lib/meta/json_schema/support/validators.rb +1 -1
  22. data/lib/meta/route_dsl/application_builder.rb +18 -25
  23. data/lib/meta/route_dsl/around_action_builder.rb +37 -27
  24. data/lib/meta/route_dsl/meta_builder.rb +16 -9
  25. data/lib/meta/route_dsl/parameters_builder.rb +29 -6
  26. data/lib/meta/route_dsl/route_builder.rb +10 -44
  27. data/lib/meta/route_dsl/uniformed_params_builder.rb +27 -9
  28. data/lib/meta/swagger_doc.rb +2 -2
  29. data/lib/meta/utils/kwargs/builder.rb +4 -2
  30. data/lib/meta/utils/kwargs/checker.rb +36 -0
  31. data/lib/meta/utils/path.rb +8 -2
  32. data/lib/meta/utils/route_dsl_builders.rb +30 -0
  33. data/meta-api.gemspec +1 -1
  34. metadata +5 -4
  35. data/lib/meta/route_dsl/helpers.rb +0 -15
@@ -6,32 +6,39 @@ require_relative 'uniformed_params_builder'
6
6
  module Meta
7
7
  module RouteDSL
8
8
  class MetaBuilder
9
- def initialize(&block)
9
+ def initialize(route_full_path:, route_method: :all, &block)
10
+ @route_full_path = route_full_path
11
+ @method = route_method
10
12
  @meta = {}
13
+ @parameters_builder = ParametersBuilder.new(route_full_path: route_full_path, route_method: route_method) # 默认给一个空的参数构建器,它只会处理 path 参数
11
14
 
12
15
  instance_exec &block if block_given?
13
16
  end
14
17
 
15
18
  def build
16
- @meta
19
+ meta = @meta
20
+ if @meta[:parameters].nil? && @route_full_path =~ /[:*].+/
21
+ meta[:parameters] = ParametersBuilder.new(route_full_path: @route_full_path, route_method: @method).build
22
+ end
23
+ meta
17
24
  end
18
25
 
19
26
  def parameters(&block)
20
- @meta[:parameters] = ParametersBuilder.new(&block).build
27
+ @meta[:parameters] = ParametersBuilder.new(route_full_path: @route_full_path, route_method: @method, &block).build
21
28
  end
22
29
 
23
30
  def request_body(options = {}, &block)
24
- @meta[:request_body] = JsonSchema::SchemaBuilderTool.build(options, &block)
31
+ @meta[:request_body] = JsonSchema::SchemaBuilderTool.build(options, &block).to_schema
25
32
  end
26
33
 
27
34
  # params 宏是一个遗留的宏,它在一个宏定义块内同时定义 parameters 和 request_body
28
35
  def params(&block)
29
- @meta[:parameters], @meta[:request_body] = UniformedParamsBuilder.new(&block).build
36
+ @meta[:parameters], @meta[:request_body] = UniformedParamsBuilder.new(route_full_path: @route_full_path, route_method: @method, &block).build
30
37
  end
31
38
 
32
- def status(code, *other_codes, &block)
39
+ def status(code, *other_codes, **options, &block)
33
40
  codes = [code, *other_codes]
34
- entity_schema = JsonSchema::SchemaBuilderTool.build(&block)
41
+ entity_schema = JsonSchema::SchemaBuilderTool.build(options, &block)
35
42
  @meta[:responses] = @meta[:responses] || {}
36
43
  codes.each { |code| @meta[:responses][code] = entity_schema }
37
44
  end
@@ -47,8 +54,8 @@ module Meta
47
54
  module Delegator
48
55
  method_names = MetaBuilder.public_instance_methods(false) - ['build']
49
56
  method_names.each do |method_name|
50
- define_method(method_name) do |*args, &block|
51
- @meta_builder.send(method_name, *args, &block) and self
57
+ define_method(method_name) do |*args, **kwargs, &block|
58
+ @meta_builder.send(method_name, *args, **kwargs, &block) and self
52
59
  end
53
60
  end
54
61
  end
@@ -1,24 +1,47 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative '../application/parameters'
4
+ require_relative '../utils/kwargs/checker'
3
5
 
4
6
  module Meta
5
7
  module RouteDSL
6
8
  class ParametersBuilder
7
- def initialize(&block)
8
- @parameters = {}
9
+ def initialize(route_full_path:, route_method:, &block)
10
+ @route_full_path = route_full_path || ''
11
+ @route_method = route_method
12
+ @parameter_options = {}
9
13
 
10
14
  instance_exec &block if block_given?
11
15
  end
12
16
 
13
- def param(name, options)
17
+ def param(name, options = {})
18
+ # 修正 path 参数的选项
14
19
  options = options.dup
15
- op_in = options.delete(:in) || 'query'
20
+ if path_param_names.include?(name) # path 参数
21
+ options = Utils::KeywordArgs::Checker.fix!(options, in: 'path', required: true)
22
+ else
23
+ options = Utils::KeywordArgs::Checker.merge_defaults!(options, in: 'query')
24
+ end
16
25
 
17
- @parameters[name] = { in: op_in, schema: JsonSchema::BaseSchema.new(options) }
26
+ in_op = options.delete(:in)
27
+ @parameter_options[name] = { in: in_op, schema: JsonSchema::BaseSchema.new(options) }
18
28
  end
19
29
 
20
30
  def build
21
- Parameters.new(@parameters)
31
+ # 补充未声明的 path 参数
32
+ (path_param_names - @parameter_options.keys).each do |name|
33
+ @parameter_options[name] = { in: 'path', schema: JsonSchema::BaseSchema.new(required: true) }
34
+ end
35
+
36
+ Parameters.new(@parameter_options)
37
+ end
38
+
39
+ private
40
+
41
+ def path_param_names
42
+ @_path_param_names ||= @route_full_path.split('/')
43
+ .filter { |part| part =~ /[:*].+/ }
44
+ .map { |part| part[1..-1].to_sym }
22
45
  end
23
46
  end
24
47
  end
@@ -3,7 +3,6 @@
3
3
  require 'json'
4
4
  require_relative '../entity'
5
5
  require_relative '../application/route'
6
- require_relative 'helpers'
7
6
  require_relative 'chain_builder'
8
7
  require_relative 'action_builder'
9
8
  require_relative 'meta_builder'
@@ -16,52 +15,27 @@ module Meta
16
15
 
17
16
  alias :if_status :status
18
17
 
19
- def initialize(path = '', method = :all, &block)
18
+ # 这里的 path 局部的路径,也就是由 route 宏命令定义的路径
19
+ def initialize(path, method = :all, parent_path: '',&block)
20
+ route_full_path = Utils::Path.join(parent_path, path)
21
+
20
22
  @path = path || ''
21
23
  @method = method || :all
22
24
  @action_builder = nil
23
- @meta_builder = MetaBuilder.new
25
+ @meta_builder = MetaBuilder.new(route_full_path: route_full_path, route_method: method)
24
26
 
25
27
  instance_exec &block if block_given?
26
28
  end
27
29
 
28
- def build(parent_path: '', meta: {}, callbacks: {})
29
- # 合并 meta 时不仅仅是覆盖,比如 parameters 参数需要合并
30
- meta2 = (meta || {}).merge(@meta_builder.build)
31
- if meta[:parameters] && meta2[:parameters]
32
- meta2[:parameters] = meta[:parameters].merge(meta2[:parameters])
33
- end
34
-
35
- # 合并 parameters 参数
36
- meta2[:parameters] ||= {}
37
- path_params = Utils::Path.join(parent_path, @path).split('/')
38
- .filter { |part| part =~ /[:*].+/ }
39
- .map { |part| part[1..-1].to_sym }
40
- path_params.each do |name|
41
- unless meta2[:parameters].key?(name)
42
- meta2[:parameters][name] = {
43
- in: 'path',
44
- schema: JsonSchema::BaseSchema.new(required: true)
45
- }
46
- end
47
- end
48
-
49
- # 构建洋葱圈模型的 LinkedAction
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
30
+ def build(meta_options: {}, callbacks: {})
31
+ meta_options = Utils::RouteDSLBuilders.merge_meta_options(meta_options, @meta_builder.build)
32
+ callbacks = Utils::RouteDSLBuilders.merge_callbacks(callbacks, [{ lifecycle: :before, proc: @action_builder&.build }])
33
+ action = AroundActionBuilder.build_from_callbacks(callbacks: callbacks)
60
34
 
61
35
  Route.new(
62
36
  path: @path,
63
37
  method: @method,
64
- meta: meta2,
38
+ meta: meta_options,
65
39
  action: action
66
40
  )
67
41
  end
@@ -81,14 +55,6 @@ module Meta
81
55
  self
82
56
  end
83
57
  end
84
-
85
- private
86
-
87
- def clone_meta(meta)
88
- meta = meta.clone
89
- meta[:responses] = meta[:responses].clone if meta[:responses]
90
- meta
91
- end
92
58
  end
93
59
  end
94
60
  end
@@ -3,31 +3,49 @@
3
3
  module Meta
4
4
  module RouteDSL
5
5
  class UniformedParamsBuilder
6
- def initialize(&block)
7
- @parameters = {}
8
- @request_body_builder = JsonSchema::ObjectSchemaBuilder.new
6
+ def initialize(route_full_path:, route_method:, &block)
7
+ @route_full_path = route_full_path
8
+ @route_method = route_method
9
+ @parameters_builder = ParametersBuilder.new(route_full_path: @route_full_path, route_method: @route_method)
10
+
11
+ @parameter_options = {}
9
12
 
10
13
  instance_exec &block if block_given?
11
14
  end
12
15
 
13
16
  def param(name, options = {}, &block)
14
- options = options.dup
15
- op_in = options.delete(:in) || 'body'
17
+ options = (options || {}).dup
18
+ in_op = options.delete(:in) || \
19
+ if path_param_names.include?(name)
20
+ 'path'
21
+ elsif @route_method == :get
22
+ 'query'
23
+ else
24
+ 'body'
25
+ end
16
26
 
17
- if op_in == 'body'
27
+ if in_op == 'body'
18
28
  property name, options, &block
19
29
  else
20
- @parameters[name] = { in: op_in, schema: JsonSchema::BaseSchema.new(options) }
30
+ @parameters_builder.param name, options
21
31
  end
22
32
  end
23
33
 
24
34
  def property(name, options = {}, &block)
35
+ @request_body_builder ||= JsonSchema::ObjectSchemaBuilder.new
25
36
  @request_body_builder.property name, options, &block
26
37
  end
27
38
 
28
39
  def build
29
- request_body = @request_body_builder.to_schema
30
- [@parameters, request_body.properties.empty? ? nil : request_body]
40
+ [@parameters_builder.build, @request_body_builder&.to_schema]
41
+ end
42
+
43
+ private
44
+
45
+ def path_param_names
46
+ @_path_param_names ||= @route_full_path.split('/')
47
+ .filter { |part| part =~ /[:*].+/ }
48
+ .map { |part| part[1..-1].to_sym }
31
49
  end
32
50
  end
33
51
  end
@@ -47,13 +47,13 @@ module Meta
47
47
  # ]
48
48
  def get_paths_and_routes!(application, prefix = '', store_routes = [])
49
49
  if (application.is_a?(Class) && application < Application) || application.is_a?(Application)
50
- prefix = RouteDSL::Helpers.join_path(prefix, application.prefix)
50
+ prefix = Utils::Path.join(prefix, application.prefix)
51
51
  (application.routes + application.applications).each do |mod|
52
52
  get_paths_and_routes!(mod, prefix, store_routes)
53
53
  end
54
54
  elsif application.is_a?(Route)
55
55
  route = application
56
- route_path = route.path == :all ? prefix : RouteDSL::Helpers.join_path(prefix, route.path)
56
+ route_path = route.path == :all ? prefix : Utils::Path.join(prefix, route.path)
57
57
  store_routes << [route_path, route] unless route.method == :all
58
58
  else
59
59
  raise "Param application must be a Application instance, Application module or a Route instance, but it got a `#{application}`"
@@ -55,10 +55,11 @@ module Meta
55
55
  class Argument
56
56
  DEFAULT_TRANSFORMER = ->(value) { value }
57
57
 
58
- def initialize(name:, normalizer: DEFAULT_TRANSFORMER, alias_names: [])
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
- @normalizer = normalizer
61
+ @normalizer = default ? ->(value) { normalizer.call(value || default) } : normalizer
62
+ @validator = validator
62
63
  end
63
64
 
64
65
  def consume(final_args, args)
@@ -71,6 +72,7 @@ module Meta
71
72
  def consume_name(final_args, args, consumer_name)
72
73
  if args.key?(consumer_name)
73
74
  value = @normalizer.call(args.delete(consumer_name))
75
+ @validator.call(value) if @validator
74
76
  final_args[@key_name] = value
75
77
  true
76
78
  else
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Meta
4
+ module Utils
5
+ class KeywordArgs
6
+ module Checker
7
+ class << self
8
+ # 将 options 内的值修正为固定值,该方法会原地修改 options 选项。
9
+ # 如果 options 中的缺失相应的值,则使用 fixed_values 中的值补充;如果 options 中的值不等于 fixed_values 中对应的值,则抛出异常。
10
+ # 示例:
11
+ # (1)fix!({}, { a: 1, b: 2 }) # => { a: 1, b: 2 }
12
+ # (2)fix!({ a: 1 }, { a: 2 }) # raise error
13
+ def fix!(options, fixed_values)
14
+ fixed_values.each do |key, value|
15
+ if options.include?(key)
16
+ if options[key] != value
17
+ raise ArgumentError, "关键字参数 #{key} 的值不正确,必须为 #{value}"
18
+ end
19
+ else
20
+ options[key] = value
21
+ end
22
+ end
23
+ options
24
+ end
25
+
26
+ def merge_defaults!(options, defaults)
27
+ defaults.each do |key, value|
28
+ options[key] = value unless options[key]
29
+ end
30
+ options
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,9 +1,12 @@
1
1
  # frozen_string_literal: true
2
+ #
2
3
 
3
4
  module Meta
4
5
  module Utils
5
6
  class Path
6
7
  class << self
8
+ # 规范化 path 结构,确保 path 以 '/' 开头,不以 '/' 结尾。
9
+ # 仅有一个例外,如果 path 为 nil 或空字符串,则返回空字符串 ''.
7
10
  def normalize_path(path)
8
11
  path = '/' unless path
9
12
  path = '/' + path unless path.start_with?('/')
@@ -11,8 +14,11 @@ module Meta
11
14
  path
12
15
  end
13
16
 
14
- def join(p1, p2)
15
- normalize_path(normalize_path(p1) + '/' + normalize_path(p2))
17
+ # 合并两个 path. 有且只有一个例外,如果 p1 p2 其中之一为 '/',则返回另一个。
18
+ def join(*parts)
19
+ parts = parts.map { |p| (p || '').delete_prefix('/').delete_suffix('/') }
20
+ parts = parts.reject { |p| p.nil? || p.empty? }
21
+ '/' + parts.join('/')
16
22
  end
17
23
  end
18
24
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Meta
4
+ module Utils
5
+ class RouteDSLBuilders
6
+ class << self
7
+ def merge_meta_options(options1, options2)
8
+ final_options = (options1 || {}).merge(options2 || {})
9
+ if options1[:parameters] && options2[:parameters]
10
+ final_options[:parameters] = options1[:parameters].merge(options2[:parameters])
11
+ end
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].merge_other_properties(options2[:request_body].properties)
14
+ end
15
+ if options1[:responses] && options2[:responses]
16
+ final_options[:responses] = options1[:responses].merge(options2[:responses])
17
+ end
18
+ final_options
19
+ end
20
+
21
+ def merge_callbacks(parent_callbacks, current_callbacks)
22
+ # 合并父级传递过来的 callbacks,将 before 和 around 放在前面,after 放在后面
23
+ parent_before = parent_callbacks.filter { |cb| cb[:lifecycle] == :before || cb[:lifecycle] == :around }
24
+ parent_after = parent_callbacks.filter { |cb| cb[:lifecycle] == :after }
25
+ parent_before + current_callbacks + parent_after
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
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.5"
3
+ spec.version = "0.0.7"
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.5
4
+ version: 0.0.7
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-27 00:00:00.000000000 Z
11
+ date: 2023-07-14 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: 一个 Web API 框架,该框架采用定义元信息的方式编写 API,并同步生成 API 文档
14
14
  email:
@@ -113,7 +113,6 @@ files:
113
113
  - lib/meta/route_dsl/application_builder.rb
114
114
  - lib/meta/route_dsl/around_action_builder.rb
115
115
  - lib/meta/route_dsl/chain_builder.rb
116
- - lib/meta/route_dsl/helpers.rb
117
116
  - lib/meta/route_dsl/meta_builder.rb
118
117
  - lib/meta/route_dsl/parameters_builder.rb
119
118
  - lib/meta/route_dsl/route_builder.rb
@@ -121,7 +120,9 @@ files:
121
120
  - lib/meta/swagger_doc.rb
122
121
  - lib/meta/utils/kwargs/builder.rb
123
122
  - lib/meta/utils/kwargs/check.rb
123
+ - lib/meta/utils/kwargs/checker.rb
124
124
  - lib/meta/utils/path.rb
125
+ - lib/meta/utils/route_dsl_builders.rb
125
126
  - meta-api.gemspec
126
127
  homepage: https://github.com/yetrun/web-frame
127
128
  licenses:
@@ -145,7 +146,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
145
146
  - !ruby/object:Gem::Version
146
147
  version: '0'
147
148
  requirements: []
148
- rubygems_version: 3.3.7
149
+ rubygems_version: 3.4.15
149
150
  signing_key:
150
151
  specification_version: 4
151
152
  summary: 一个 Web API 框架
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Meta
4
- module RouteDSL
5
- module Helpers
6
- class << self
7
- def join_path(*parts)
8
- parts = parts.map { |p| (p || '').delete_prefix('/').delete_suffix('/') }
9
- parts = parts.reject { |p| p.nil? || p.empty? }
10
- '/' + parts.join('/')
11
- end
12
- end
13
- end
14
- end
15
- end