meta-api 0.0.1
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 +7 -0
- data/.autoenv.zsh +1 -0
- data/.gitignore +6 -0
- data/.rubocop.yml +28 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +66 -0
- data/LICENSE.txt +502 -0
- data/README.md +149 -0
- data/Rakefile +3 -0
- data/config/locales/zh-CN.yml +6 -0
- data/docs//345/220/215/347/247/260/347/224/261/346/235/245.md +7 -0
- data/docs//346/225/231/347/250/213.md +1199 -0
- data/docs//347/264/242/345/274/225.md +173 -0
- data/examples/lobster.rb +71 -0
- data/examples/rack_app/README.md +3 -0
- data/examples/rack_app/config.ru +6 -0
- data/examples/rack_app/hello.rb +6 -0
- data/examples/rack_app/timing.rb +15 -0
- data/lib/meta/api.rb +3 -0
- data/lib/meta/application/application.rb +63 -0
- data/lib/meta/application/execution.rb +178 -0
- data/lib/meta/application/meta.rb +71 -0
- data/lib/meta/application/path_matching_mod.rb +53 -0
- data/lib/meta/application/route.rb +58 -0
- data/lib/meta/application.rb +42 -0
- data/lib/meta/entity.rb +59 -0
- data/lib/meta/errors.rb +29 -0
- data/lib/meta/json_schema/builders/array_schema_builder.rb +29 -0
- data/lib/meta/json_schema/builders/object_schema_builder.rb +120 -0
- data/lib/meta/json_schema/builders/schema_builder_tool.rb +29 -0
- data/lib/meta/json_schema/schemas/array_schema.rb +40 -0
- data/lib/meta/json_schema/schemas/base_schema.rb +110 -0
- data/lib/meta/json_schema/schemas/object_schema.rb +161 -0
- data/lib/meta/json_schema/schemas.rb +12 -0
- data/lib/meta/json_schema/support/errors.rb +38 -0
- data/lib/meta/json_schema/support/presenters.rb +35 -0
- data/lib/meta/json_schema/support/schema_options.rb +55 -0
- data/lib/meta/json_schema/support/type_converter.rb +137 -0
- data/lib/meta/json_schema/support/validators.rb +54 -0
- data/lib/meta/load_i18n.rb +8 -0
- data/lib/meta/route_dsl/action_builder.rb +15 -0
- data/lib/meta/route_dsl/application_builder.rb +108 -0
- data/lib/meta/route_dsl/chain_builder.rb +48 -0
- data/lib/meta/route_dsl/helpers.rb +15 -0
- data/lib/meta/route_dsl/meta_builder.rb +57 -0
- data/lib/meta/route_dsl/parameters_builder.rb +24 -0
- data/lib/meta/route_dsl/route_builder.rb +85 -0
- data/lib/meta/route_dsl/uniformed_params_builder.rb +34 -0
- data/lib/meta/swagger_doc.rb +86 -0
- data/lib/meta/utils/path.rb +20 -0
- data/meta-api.gemspec +23 -0
- metadata +96 -0
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Meta
|
4
|
+
module JsonSchema
|
5
|
+
class ValidationErrors < StandardError
|
6
|
+
attr_reader :errors
|
7
|
+
|
8
|
+
def initialize(errors, message = nil)
|
9
|
+
raise ArgumentError, '参数 errors 应传递一个 Hash' unless errors.is_a?(Hash)
|
10
|
+
|
11
|
+
super(message)
|
12
|
+
@errors = errors
|
13
|
+
end
|
14
|
+
|
15
|
+
def prepend_root(root)
|
16
|
+
errors_prepend_root = errors.transform_keys do |name|
|
17
|
+
return name.to_s if root.empty?
|
18
|
+
|
19
|
+
path = name[0] == '[' ? "#{root}#{name}" : "#{root}.#{name}"
|
20
|
+
path = path[1..] if path[0] == '.'
|
21
|
+
path = path[0..-2] if path[-1] == '.'
|
22
|
+
path
|
23
|
+
end
|
24
|
+
ValidationErrors.new(errors_prepend_root)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class ValidationError < ValidationErrors
|
29
|
+
def initialize(message)
|
30
|
+
super('' => message)
|
31
|
+
end
|
32
|
+
|
33
|
+
def message
|
34
|
+
errors['']
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Meta
|
4
|
+
module JsonSchema
|
5
|
+
module Presenters
|
6
|
+
@presenter_handlers = []
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def register(presenter_handler)
|
10
|
+
@presenter_handlers << presenter_handler
|
11
|
+
end
|
12
|
+
|
13
|
+
def unregister(presenter_handler)
|
14
|
+
@presenter_handlers.delete(presenter_handler)
|
15
|
+
end
|
16
|
+
|
17
|
+
def present(presenter, value)
|
18
|
+
@presenter_handlers.each do |presenter_handler|
|
19
|
+
next unless presenter_handler.handle?(presenter)
|
20
|
+
|
21
|
+
return presenter_handler.present(presenter, value)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_schema_doc(presenter, other_options)
|
26
|
+
@presenter_handlers.each do |presenter_handler|
|
27
|
+
next unless presenter_handler.handle?(presenter)
|
28
|
+
|
29
|
+
return presenter_handler.to_schema_doc(presenter, other_options)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Meta
|
4
|
+
module JsonSchema
|
5
|
+
module SchemaOptions
|
6
|
+
@default_options = {
|
7
|
+
scope: [],
|
8
|
+
required: false
|
9
|
+
}
|
10
|
+
@allowable_options = (
|
11
|
+
%i[type description in value using default presenter convert scope items] +
|
12
|
+
@default_options.keys +
|
13
|
+
JsonSchema::Validators.keys
|
14
|
+
).uniq
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def normalize_to_param_and_render(options)
|
18
|
+
common_opts = (options || {}).dup
|
19
|
+
param_opts = common_opts.delete(:param)
|
20
|
+
render_opts = common_opts.delete(:render)
|
21
|
+
|
22
|
+
param_opts = merge_common_to_stage(common_opts, param_opts)
|
23
|
+
render_opts = merge_common_to_stage(common_opts, render_opts)
|
24
|
+
[param_opts, render_opts]
|
25
|
+
end
|
26
|
+
|
27
|
+
def merge_common_to_stage(common_opts, stage_opts)
|
28
|
+
stage_opts = {} if stage_opts.nil? || stage_opts == true
|
29
|
+
stage_opts = common_opts.merge(stage_opts) if stage_opts
|
30
|
+
stage_opts = normalize(stage_opts) if stage_opts
|
31
|
+
stage_opts
|
32
|
+
end
|
33
|
+
|
34
|
+
def normalize(options)
|
35
|
+
# 只要 options 中设置为 nil 的选项没有明确的意义,则下行代码是永远有效的
|
36
|
+
options = (@default_options.compact).merge(options.compact)
|
37
|
+
options[:scope] = [options[:scope]] unless options[:scope].is_a?(Array)
|
38
|
+
if options[:using]
|
39
|
+
if options[:type].nil?
|
40
|
+
options[:type] = 'object'
|
41
|
+
elsif options[:type] != 'object' && options[:type] != 'array'
|
42
|
+
raise "当使用 using 时,type 必须声明为 object 或 array"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# 处理 validators
|
47
|
+
unknown_validators = options.keys - @allowable_options
|
48
|
+
raise "未知的选项:#{unknown_validators.join(', ')}" unless unknown_validators.empty?
|
49
|
+
|
50
|
+
options
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Meta
|
4
|
+
module JsonSchema
|
5
|
+
class ObjectWrapper
|
6
|
+
def initialize(target)
|
7
|
+
@target = target
|
8
|
+
end
|
9
|
+
|
10
|
+
def __target__
|
11
|
+
@target
|
12
|
+
end
|
13
|
+
|
14
|
+
def key?(key)
|
15
|
+
@target.respond_to?(key)
|
16
|
+
end
|
17
|
+
|
18
|
+
def [](key)
|
19
|
+
@target.__send__(key)
|
20
|
+
end
|
21
|
+
|
22
|
+
def method_missing(method, *args)
|
23
|
+
@target.__send__(method, *args)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module TypeConverter
|
28
|
+
# 定义客户类型对应的 Ruby 类型
|
29
|
+
@definity_types = {
|
30
|
+
'boolean' => [TrueClass, FalseClass],
|
31
|
+
'integer' => [Integer],
|
32
|
+
'number' => [Integer, Float],
|
33
|
+
'string' => [String],
|
34
|
+
'array' => [Array],
|
35
|
+
'object' => [Hash, ObjectWrapper]
|
36
|
+
}
|
37
|
+
|
38
|
+
# 定义从 Ruby 类型转化为对应类型的逻辑
|
39
|
+
@boolean_converters = {
|
40
|
+
[String] => lambda do |value|
|
41
|
+
unless %w[true True TRUE false False FALSE].include?(value)
|
42
|
+
raise TypeConvertError, "类型转化失败,期望得到一个 `boolean` 类型,但值 `#{value}` 无法转化"
|
43
|
+
end
|
44
|
+
|
45
|
+
value.downcase == 'true'
|
46
|
+
end
|
47
|
+
}
|
48
|
+
|
49
|
+
@integer_converters = {
|
50
|
+
[String] => lambda do |value|
|
51
|
+
# 允许的格式:+34、-34、34、34.0 等
|
52
|
+
unless value =~ /^[+-]?\d+(\.0+)?$/
|
53
|
+
raise TypeConvertError, "类型转化失败,期望得到一个 `integer` 类型,但值 `#{value}` 无法转化"
|
54
|
+
end
|
55
|
+
|
56
|
+
value.to_i
|
57
|
+
end,
|
58
|
+
[Float] => lambda do |value|
|
59
|
+
unless value.to_i == value
|
60
|
+
raise TypeConvertError, "类型转化失败,期望得到一个 `integer` 类型,但值 `#{value}` 无法转化"
|
61
|
+
end
|
62
|
+
|
63
|
+
value.to_i
|
64
|
+
end
|
65
|
+
}
|
66
|
+
|
67
|
+
@number_converters = {
|
68
|
+
[String] => lambda do |value|
|
69
|
+
unless value =~ /^[+-]?\d+(\.\d+)?$/
|
70
|
+
raise TypeConvertError, "类型转化失败,期望得到一个 `number` 类型,但值 `#{value}` 无法转化"
|
71
|
+
end
|
72
|
+
|
73
|
+
float = value.to_f
|
74
|
+
float.to_i == float ? float.to_i : float
|
75
|
+
end
|
76
|
+
}
|
77
|
+
|
78
|
+
@string_converters = {
|
79
|
+
[Object] => lambda do |value|
|
80
|
+
value.to_s
|
81
|
+
end
|
82
|
+
}
|
83
|
+
|
84
|
+
@array_converters = {
|
85
|
+
[Object] => lambda do |value|
|
86
|
+
unless value.respond_to?(:to_a)
|
87
|
+
raise TypeConvertError, "转化为数组类型时期望对象拥有 `to_a` 方法"
|
88
|
+
end
|
89
|
+
|
90
|
+
value.to_a
|
91
|
+
end
|
92
|
+
}
|
93
|
+
|
94
|
+
@object_converters = {
|
95
|
+
[Object] => lambda do |value|
|
96
|
+
if [TrueClass, FalseClass, Integer, Float, String].any? { |ruby_type| value.is_a?(ruby_type) }
|
97
|
+
raise TypeConvertError, "类型转化失败,期望得到一个 `object` 类型,但值 `#{value}` 是一个基本类型"
|
98
|
+
elsif value.is_a?(Array)
|
99
|
+
raise TypeConvertError, "类型转化失败,期望得到一个 `object` 类型,但值 `#{value}` 是一个 `array` 类型"
|
100
|
+
end
|
101
|
+
|
102
|
+
ObjectWrapper.new(value)
|
103
|
+
end
|
104
|
+
}
|
105
|
+
|
106
|
+
class << self
|
107
|
+
def convert_value(value, target_type)
|
108
|
+
return nil if value.nil?
|
109
|
+
raise JsonSchema::TypeConvertError, "未知的目标类型 `#{target_type}`" unless @definity_types.keys.include?(target_type)
|
110
|
+
return value if match_definity_types?(value, target_type)
|
111
|
+
|
112
|
+
convert_to_definity_type(value, target_type)
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
def match_definity_types?(value, target_type)
|
118
|
+
ruby_types = @definity_types[target_type]
|
119
|
+
return ruby_types.any?{ |ruby_type| value.is_a?(ruby_type) }
|
120
|
+
end
|
121
|
+
|
122
|
+
def convert_to_definity_type(value, target_type)
|
123
|
+
converters = instance_variable_get(:"@#{target_type}_converters")
|
124
|
+
converters.each do |ruby_types, converter|
|
125
|
+
if ruby_types.any?{ |ruby_type| value.is_a?(ruby_type) }
|
126
|
+
return converter.call(value)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
raise TypeConvertError, "类型转化失败,期望得到一个 `#{target_type}` 类型,但值 `#{value}` 无法转化"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
class TypeConvertError < StandardError
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Meta
|
4
|
+
module JsonSchema
|
5
|
+
module Validators
|
6
|
+
@validators = {
|
7
|
+
validate: proc { |value, p|
|
8
|
+
next if value.nil?
|
9
|
+
p.call(value)
|
10
|
+
},
|
11
|
+
required: proc { |value, options, full_options|
|
12
|
+
next if options == false
|
13
|
+
|
14
|
+
full_options ||= {}
|
15
|
+
options = {} if options == true
|
16
|
+
raise JsonSchema::ValidationError, I18n.t(:'json_schema.errors.required') if value.nil?
|
17
|
+
|
18
|
+
if full_options[:type] == 'string' && (!options[:allow_empty]) && value.empty?
|
19
|
+
raise JsonSchema::ValidationError, I18n.t(:'json_schema.errors.required')
|
20
|
+
end
|
21
|
+
if full_options[:type] == 'array' && (options[:allow_empty] == false) && value.empty?
|
22
|
+
raise JsonSchema::ValidationError, I18n.t(:'json_schema.errors.required')
|
23
|
+
end
|
24
|
+
},
|
25
|
+
format: proc { |value, format|
|
26
|
+
next if value.nil?
|
27
|
+
raise JsonSchema::ValidationError, I18n.t(:'json_schema.errors.format') unless value =~ format
|
28
|
+
},
|
29
|
+
allowable: proc { |value, allowable_values|
|
30
|
+
next if value.nil?
|
31
|
+
raise JsonSchema::ValidationError, I18n.t(:'json_schema.errors.allowable') unless allowable_values.include?(value)
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
class << self
|
36
|
+
def [](key)
|
37
|
+
@validators[key]
|
38
|
+
end
|
39
|
+
|
40
|
+
def []=(key, validator)
|
41
|
+
@validators[key] = validator
|
42
|
+
end
|
43
|
+
|
44
|
+
def delete(key)
|
45
|
+
@validators.delete(key)
|
46
|
+
end
|
47
|
+
|
48
|
+
def keys
|
49
|
+
@validators.keys
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'route_builder'
|
4
|
+
require_relative 'meta_builder'
|
5
|
+
|
6
|
+
module Meta
|
7
|
+
module RouteDSL
|
8
|
+
class ApplicationBuilder
|
9
|
+
include MetaBuilder::Delegator
|
10
|
+
|
11
|
+
def initialize(prefix = nil, &block)
|
12
|
+
@mod_prefix = prefix
|
13
|
+
@before_callbacks = []
|
14
|
+
@after_callbacks = []
|
15
|
+
@error_guards = []
|
16
|
+
@meta_builder = MetaBuilder.new
|
17
|
+
@mod_builders = []
|
18
|
+
@shared_mods = []
|
19
|
+
|
20
|
+
instance_exec &block if block_given?
|
21
|
+
end
|
22
|
+
|
23
|
+
def build(parent_path: '', meta: {}, before_callbacks: [], after_callbacks: [])
|
24
|
+
# 合并 meta 时不仅仅是覆盖,比如 parameters 参数需要合并
|
25
|
+
meta2 = (meta || {}).merge(@meta_builder.build)
|
26
|
+
if meta[:parameters] && meta2[:parameters]
|
27
|
+
meta2[:parameters] = meta[:parameters].merge(meta2[:parameters])
|
28
|
+
end
|
29
|
+
|
30
|
+
# 构建子模块
|
31
|
+
before_callbacks += @before_callbacks
|
32
|
+
after_callbacks = @after_callbacks + after_callbacks
|
33
|
+
mods = @mod_builders.map { |builder| builder.build(parent_path: Utils::Path.join(parent_path, @mod_prefix), meta: meta2, before_callbacks: before_callbacks, after_callbacks: after_callbacks) }
|
34
|
+
|
35
|
+
Application.new(
|
36
|
+
prefix: @mod_prefix,
|
37
|
+
mods: mods,
|
38
|
+
shared_mods: @shared_mods,
|
39
|
+
error_guards: @error_guards
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
def shared(*mods, &block)
|
44
|
+
@shared_mods += mods
|
45
|
+
@shared_mods << Module.new(&block) if block_given?
|
46
|
+
end
|
47
|
+
|
48
|
+
# 定义路由块
|
49
|
+
def route(path, method = nil, &block)
|
50
|
+
route_builder = RouteDSL::RouteBuilder.new(path, method, &block)
|
51
|
+
@mod_builders << route_builder
|
52
|
+
route_builder
|
53
|
+
end
|
54
|
+
|
55
|
+
# 定义子模块
|
56
|
+
def namespace(path, &block)
|
57
|
+
@mod_builders << ApplicationBuilder.new(path, &block)
|
58
|
+
end
|
59
|
+
|
60
|
+
# 应用另一个模块
|
61
|
+
def apply(builder, tags: nil)
|
62
|
+
@mod_builders << BindingMeta.new(builder, tags ? { tags: tags } : {})
|
63
|
+
end
|
64
|
+
|
65
|
+
# 定义模块内的公共逻辑
|
66
|
+
def before(&block)
|
67
|
+
@before_callbacks << block
|
68
|
+
end
|
69
|
+
|
70
|
+
def after(&block)
|
71
|
+
@after_callbacks << block
|
72
|
+
end
|
73
|
+
|
74
|
+
def rescue_error(error_class, &block)
|
75
|
+
@error_guards << { error_class: error_class, caller: block }
|
76
|
+
end
|
77
|
+
|
78
|
+
# 定义应用到子模块的公共逻辑
|
79
|
+
def meta(&block)
|
80
|
+
@meta_builder.instance_exec &block
|
81
|
+
end
|
82
|
+
|
83
|
+
# 添加 get、post、put、patch、delete 路由方法
|
84
|
+
[:get, :post, :put, :patch, :delete].each do |method|
|
85
|
+
define_method(method) do |path = '', &block|
|
86
|
+
route(path, method, &block)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# 绑定 Meta,绑定的 Meta 会覆盖父级的 Meta,用于 Application.apply 方法
|
91
|
+
class BindingMeta
|
92
|
+
def initialize(builder, meta)
|
93
|
+
@builder = builder
|
94
|
+
@meta = meta
|
95
|
+
end
|
96
|
+
|
97
|
+
def build(parent_path: '', meta: {}, before_callbacks: [], after_callbacks: [])
|
98
|
+
# 合并 meta 时不仅仅是覆盖,比如 parameters 参数需要合并
|
99
|
+
meta2 = (meta || {}).merge(@meta)
|
100
|
+
if meta[:parameters] && meta2[:parameters]
|
101
|
+
meta2[:parameters] = meta[:parameters].merge(meta2[:parameters])
|
102
|
+
end
|
103
|
+
@builder.build(parent_path: parent_path, meta: meta2, before_callbacks: before_callbacks, after_callbacks: after_callbacks)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Meta
|
4
|
+
module RouteDSL
|
5
|
+
class ChainBuilder
|
6
|
+
def initialize
|
7
|
+
@blocks = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def build
|
11
|
+
blocks = @blocks
|
12
|
+
proc do
|
13
|
+
blocks.each { |b| instance_exec &b }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def do_any(&block)
|
18
|
+
@blocks << block
|
19
|
+
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def resource(&block)
|
24
|
+
do_any {
|
25
|
+
resource = instance_exec(&block)
|
26
|
+
|
27
|
+
raise Errors::ResourceNotFound if resource.nil?
|
28
|
+
|
29
|
+
# 为 execution 添加一个 resource 方法
|
30
|
+
define_singleton_method(:resource) { resource }
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
def authorize(&block)
|
35
|
+
do_any {
|
36
|
+
permitted = instance_eval(&block)
|
37
|
+
raise Errors::NotAuthorized unless permitted
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
def set_status(&block)
|
42
|
+
do_any {
|
43
|
+
response.status = instance_exec(&block)
|
44
|
+
}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,15 @@
|
|
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
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'parameters_builder'
|
4
|
+
require_relative 'uniformed_params_builder'
|
5
|
+
|
6
|
+
module Meta
|
7
|
+
module RouteDSL
|
8
|
+
class MetaBuilder
|
9
|
+
def initialize(&block)
|
10
|
+
@meta = {}
|
11
|
+
|
12
|
+
instance_exec &block if block_given?
|
13
|
+
end
|
14
|
+
|
15
|
+
def build
|
16
|
+
@meta
|
17
|
+
end
|
18
|
+
|
19
|
+
def parameters(&block)
|
20
|
+
@meta[:parameters] = ParametersBuilder.new(&block).build
|
21
|
+
end
|
22
|
+
|
23
|
+
def request_body(options = {}, &block)
|
24
|
+
@meta[:request_body] = JsonSchema::SchemaBuilderTool.build(options, &block)
|
25
|
+
end
|
26
|
+
|
27
|
+
# params 宏是一个遗留的宏,它在一个宏定义块内同时定义 parameters 和 request_body
|
28
|
+
def params(&block)
|
29
|
+
@meta[:parameters], @meta[:request_body] = UniformedParamsBuilder.new(&block).build
|
30
|
+
end
|
31
|
+
|
32
|
+
def status(code, *other_codes, &block)
|
33
|
+
codes = [code, *other_codes]
|
34
|
+
entity_schema = JsonSchema::SchemaBuilderTool.build(&block)
|
35
|
+
@meta[:responses] = @meta[:responses] || {}
|
36
|
+
codes.each { |code| @meta[:responses][code] = entity_schema }
|
37
|
+
end
|
38
|
+
|
39
|
+
[:tags, :title, :description].each do |method_name|
|
40
|
+
define_method(method_name) do |value|
|
41
|
+
@meta[method_name] = value
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# 别的模块引入该模块时可直接调用 MetaBuilder 的方法。
|
46
|
+
# 由于该模块是动态实现的,其务必要在 MetaBuilder 类构建完毕后再声明,故而放在文件的最后声明。
|
47
|
+
module Delegator
|
48
|
+
method_names = MetaBuilder.public_instance_methods(false) - ['build']
|
49
|
+
method_names.each do |method_name|
|
50
|
+
define_method(method_name) do |*args, &block|
|
51
|
+
@meta_builder.send(method_name, *args, &block) and self
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Meta
|
4
|
+
module RouteDSL
|
5
|
+
class ParametersBuilder
|
6
|
+
def initialize(&block)
|
7
|
+
@parameters = {}
|
8
|
+
|
9
|
+
instance_exec &block if block_given?
|
10
|
+
end
|
11
|
+
|
12
|
+
def param(name, options)
|
13
|
+
options = options.dup
|
14
|
+
op_in = options.delete(:in) || 'query'
|
15
|
+
|
16
|
+
@parameters[name] = { in: op_in, schema: JsonSchema::BaseSchema.new(options) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def build
|
20
|
+
@parameters
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require_relative '../entity'
|
5
|
+
require_relative '../application/route'
|
6
|
+
require_relative 'helpers'
|
7
|
+
require_relative 'chain_builder'
|
8
|
+
require_relative 'action_builder'
|
9
|
+
require_relative 'meta_builder'
|
10
|
+
|
11
|
+
module Meta
|
12
|
+
module RouteDSL
|
13
|
+
class RouteBuilder
|
14
|
+
include MetaBuilder::Delegator
|
15
|
+
|
16
|
+
alias :if_status :status
|
17
|
+
|
18
|
+
def initialize(path = '', method = :all, &block)
|
19
|
+
@path = path || ''
|
20
|
+
@method = method || :all
|
21
|
+
@action_builder = nil
|
22
|
+
@meta_builder = MetaBuilder.new
|
23
|
+
|
24
|
+
instance_exec &block if block_given?
|
25
|
+
end
|
26
|
+
|
27
|
+
def build(parent_path: '', meta: {}, before_callbacks: [], after_callbacks: [])
|
28
|
+
# 合并 meta 时不仅仅是覆盖,比如 parameters 参数需要合并
|
29
|
+
meta2 = (meta || {}).merge(@meta_builder.build)
|
30
|
+
if meta[:parameters] && meta2[:parameters]
|
31
|
+
meta2[:parameters] = meta[:parameters].merge(meta2[:parameters])
|
32
|
+
end
|
33
|
+
|
34
|
+
# 合并 parameters 参数
|
35
|
+
meta2[:parameters] ||= {}
|
36
|
+
path_params = Utils::Path.join(parent_path, @path).split('/')
|
37
|
+
.filter { |part| part =~ /[:*].+/ }
|
38
|
+
.map { |part| part[1..-1].to_sym }
|
39
|
+
path_params.each do |name|
|
40
|
+
unless meta2[:parameters].key?(name)
|
41
|
+
meta2[:parameters][name] = {
|
42
|
+
in: 'path',
|
43
|
+
schema: JsonSchema::BaseSchema.new(required: true)
|
44
|
+
}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# 将 before_callbacks、action、after_callbacks 合并为 actions
|
49
|
+
action = @action_builder&.build
|
50
|
+
actions = before_callbacks + (action ? [action] : []) + after_callbacks
|
51
|
+
|
52
|
+
Route.new(
|
53
|
+
path: @path,
|
54
|
+
method: @method,
|
55
|
+
meta: meta2,
|
56
|
+
actions: actions
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
def chain
|
61
|
+
@action_builder || @action_builder = ChainBuilder.new
|
62
|
+
end
|
63
|
+
|
64
|
+
def action(&block)
|
65
|
+
@action_builder = ActionBuilder.new(&block)
|
66
|
+
end
|
67
|
+
|
68
|
+
# 将 chain 的方法转交给 ChainBuilder
|
69
|
+
[:do_any, :resource, :authorize, :set_status].each do |method_name|
|
70
|
+
define_method(method_name) do |&block|
|
71
|
+
chain.send(method_name, &block)
|
72
|
+
self
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def clone_meta(meta)
|
79
|
+
meta = meta.clone
|
80
|
+
meta[:responses] = meta[:responses].clone if meta[:responses]
|
81
|
+
meta
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|