meta-api 0.0.1 → 0.0.3
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/.gitignore +5 -4
- data/CHANGELOG.md +11 -0
- data/Gemfile.lock +1 -1
- data/README.md +14 -8
- data/docs/Rails.md +61 -0
- data/docs//345/246/202/344/275/225/350/264/241/347/214/256.md +10 -0
- data/docs//346/225/231/347/250/213.md +50 -20
- data/examples/rails_app/.gitattributes +5 -0
- data/examples/rails_app/.gitignore +23 -0
- data/examples/rails_app/.rspec +1 -0
- data/examples/rails_app/.ruby-version +1 -0
- data/examples/rails_app/Gemfile +29 -0
- data/examples/rails_app/Gemfile.lock +190 -0
- data/examples/rails_app/README.md +11 -0
- data/examples/rails_app/Rakefile +6 -0
- data/examples/rails_app/app/controllers/application_controller.rb +7 -0
- data/examples/rails_app/app/controllers/concerns/.keep +0 -0
- data/examples/rails_app/app/controllers/data_controller.rb +63 -0
- data/examples/rails_app/app/controllers/swagger_controller.rb +13 -0
- data/examples/rails_app/app/models/concerns/.keep +0 -0
- data/examples/rails_app/bin/rails +4 -0
- data/examples/rails_app/bin/rake +4 -0
- data/examples/rails_app/bin/setup +25 -0
- data/examples/rails_app/config/application.rb +39 -0
- data/examples/rails_app/config/boot.rb +3 -0
- data/examples/rails_app/config/credentials.yml.enc +1 -0
- data/examples/rails_app/config/environment.rb +5 -0
- data/examples/rails_app/config/environments/development.rb +51 -0
- data/examples/rails_app/config/environments/production.rb +65 -0
- data/examples/rails_app/config/environments/test.rb +50 -0
- data/examples/rails_app/config/initializers/cors.rb +16 -0
- data/examples/rails_app/config/initializers/filter_parameter_logging.rb +8 -0
- data/examples/rails_app/config/initializers/inflections.rb +16 -0
- data/examples/rails_app/config/initializers/meta_rails_plugin.rb +3 -0
- data/examples/rails_app/config/locales/en.yml +33 -0
- data/examples/rails_app/config/puma.rb +43 -0
- data/examples/rails_app/config/routes.rb +13 -0
- data/examples/rails_app/config.ru +6 -0
- data/examples/rails_app/lib/tasks/.keep +0 -0
- data/examples/rails_app/log/.keep +0 -0
- data/examples/rails_app/public/robots.txt +1 -0
- data/examples/rails_app/spec/data_controller_spec.rb +60 -0
- data/examples/rails_app/spec/rails_helper.rb +55 -0
- data/examples/rails_app/spec/spec_helper.rb +94 -0
- data/examples/rails_app/spec/swagger_controller_spec.rb +13 -0
- data/examples/rails_app/tmp/.keep +0 -0
- data/examples/rails_app/tmp/pids/.keep +0 -0
- data/lib/meta/api.rb +1 -0
- data/lib/meta/application/execution.rb +5 -13
- data/lib/meta/application/{meta.rb → metadata.rb} +5 -15
- data/lib/meta/application/parameters.rb +47 -0
- data/lib/meta/application/route.rb +2 -2
- data/lib/meta/config.rb +17 -0
- data/lib/meta/entity.rb +0 -1
- data/lib/meta/json_schema/builders/array_schema_builder.rb +11 -10
- data/lib/meta/json_schema/builders/dynamic_schema_builder.rb +23 -0
- data/lib/meta/json_schema/builders/object_schema_builder.rb +4 -18
- data/lib/meta/json_schema/builders/ref_schema_builder.rb +19 -0
- data/lib/meta/json_schema/builders/schema_builder_tool.rb +26 -1
- data/lib/meta/json_schema/schemas/array_schema.rb +3 -3
- data/lib/meta/json_schema/schemas/base_schema.rb +32 -42
- data/lib/meta/json_schema/schemas/dynamic_schema.rb +29 -0
- data/lib/meta/json_schema/schemas/object_schema.rb +29 -109
- data/lib/meta/json_schema/schemas/properties.rb +157 -0
- data/lib/meta/json_schema/schemas/ref_schema.rb +35 -0
- data/lib/meta/json_schema/schemas.rb +0 -2
- data/lib/meta/json_schema/support/schema_options.rb +22 -11
- data/lib/meta/json_schema.rb +2 -0
- data/lib/meta/rails.rb +98 -0
- data/lib/meta/route_dsl/parameters_builder.rb +2 -1
- data/lib/meta/swagger_doc.rb +5 -2
- data/lib/meta/utils/kwargs/builder.rb +115 -0
- data/lib/meta/utils/kwargs/check.rb +91 -0
- data/meta-api.gemspec +1 -1
- metadata +61 -7
@@ -1,16 +1,28 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative '../../utils/kwargs/check'
|
4
|
+
|
3
5
|
module Meta
|
4
6
|
module JsonSchema
|
5
7
|
class ObjectSchema < BaseSchema
|
6
|
-
attr_reader :properties, :
|
8
|
+
attr_reader :properties, :locked_options
|
9
|
+
|
10
|
+
USER_OPTIONS_CHECKER = Utils::KeywordArgs::Builder.build do
|
11
|
+
permit_extras true
|
12
|
+
|
13
|
+
key :scope, normalizer: ->(value) {
|
14
|
+
raise ArgumentError, 'scope 选项不可传递 nil' if value.nil?
|
15
|
+
value = [value] unless value.is_a?(Array)
|
16
|
+
value
|
17
|
+
}
|
18
|
+
end
|
7
19
|
|
8
|
-
def initialize(properties:
|
20
|
+
def initialize(properties: nil, options: {}, locked_options: {}, schema_name_resolver: proc { nil })
|
9
21
|
super(options)
|
10
22
|
|
11
|
-
@properties = properties || {}
|
12
|
-
@
|
13
|
-
@locked_options = locked_options || {}
|
23
|
+
@properties = properties || Properties.new({}) # property 包含 stage,stage 包含 scope、schema
|
24
|
+
@properties = Properties.new(@properties) if @properties.is_a?(Hash)
|
25
|
+
@locked_options = USER_OPTIONS_CHECKER.check(locked_options || {})
|
14
26
|
@schema_name_resolver = schema_name_resolver || proc { nil }
|
15
27
|
end
|
16
28
|
|
@@ -18,7 +30,6 @@ module Meta
|
|
18
30
|
def dup(options)
|
19
31
|
self.class.new(
|
20
32
|
properties: properties,
|
21
|
-
object_validations: object_validations,
|
22
33
|
options: options,
|
23
34
|
locked_options: locked_options,
|
24
35
|
schema_name_resolver: @schema_name_resolver
|
@@ -28,52 +39,12 @@ module Meta
|
|
28
39
|
def filter(object_value, user_options = {})
|
29
40
|
# 合并 user_options
|
30
41
|
user_options = user_options.merge(locked_options) if locked_options
|
42
|
+
user_options = USER_OPTIONS_CHECKER.check(user_options)
|
31
43
|
|
32
44
|
object_value = super(object_value, user_options)
|
33
|
-
return nil if object_value.nil?
|
34
45
|
|
35
|
-
|
36
|
-
|
37
|
-
user_scope = user_options[:scope] || []
|
38
|
-
user_scope = [user_scope] unless user_scope.is_a?(Array)
|
39
|
-
stage = user_options[:stage]
|
40
|
-
exclude = user_options.delete(:exclude) # 这里删除 exclude 选项
|
41
|
-
filtered_properties = @properties.filter do |name, property_schema|
|
42
|
-
# 通过 discard_missing 过滤
|
43
|
-
next false if user_options[:discard_missing] && !object_value.key?(name.to_s)
|
44
|
-
|
45
|
-
# 通过 stage 过滤。
|
46
|
-
property_schema_options = property_schema.options(stage)
|
47
|
-
next false unless property_schema_options
|
48
|
-
|
49
|
-
# 通过 locked_exclude 选项过滤
|
50
|
-
next false if exclude && exclude.include?(name)
|
51
|
-
|
52
|
-
# 通过 scope 过滤
|
53
|
-
scope_option = property_schema_options[:scope]
|
54
|
-
next true if scope_option.empty?
|
55
|
-
next false if user_scope.empty?
|
56
|
-
(user_scope - scope_option).empty? # user_scope 应被消耗殆尽
|
57
|
-
end
|
58
|
-
|
59
|
-
# 第二步,递归过滤每一个属性
|
60
|
-
object = {}
|
61
|
-
errors = {}
|
62
|
-
filtered_properties.each do |name, property_schema|
|
63
|
-
value = resolve_property_value(object_value, name, property_schema, stage)
|
64
|
-
|
65
|
-
begin
|
66
|
-
object[name] = property_schema.filter(value, **user_options, object_value: object_value)
|
67
|
-
rescue JsonSchema::ValidationErrors => e
|
68
|
-
errors.merge! e.prepend_root(name).errors
|
69
|
-
end
|
70
|
-
end.to_h
|
71
|
-
|
72
|
-
if errors.empty?
|
73
|
-
object
|
74
|
-
else
|
75
|
-
raise JsonSchema::ValidationErrors.new(errors)
|
76
|
-
end
|
46
|
+
return nil if object_value.nil?
|
47
|
+
@properties.filter(object_value, user_options)
|
77
48
|
end
|
78
49
|
|
79
50
|
# 合并其他的属性,并返回一个新的 ObjectSchema
|
@@ -81,57 +52,18 @@ module Meta
|
|
81
52
|
ObjectSchema.new(properties: self.properties.merge(properties))
|
82
53
|
end
|
83
54
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
#
|
91
|
-
# 提示:
|
92
|
-
# > 每个 ObjectSchema 拥有一个 @schema_name_resolver 实例变量,如果由它解析出来的名称不为 nil,
|
93
|
-
# > 则该 Schema 生成文档时会使用 $ref 格式。除非 to_ref 选项设置为 false.
|
94
|
-
#
|
95
|
-
def to_schema_doc(user_options)
|
96
|
-
stage = user_options[:stage]
|
97
|
-
# HACK: 标准化选项的工作进行得怎样?
|
55
|
+
def resolve_name(stage)
|
56
|
+
locked_scopes = (locked_options || {})[:scope] || []
|
57
|
+
@schema_name_resolver.call(stage, locked_scopes)
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_schema_doc(stage: nil, **user_options)
|
98
61
|
locked_scopes = (locked_options || {})[:scope] || []
|
99
|
-
locked_scopes = [locked_scopes] unless locked_scope.nil? && locked_scopes.is_a?(Array)
|
100
|
-
schema_name = @schema_name_resolver.call(stage, locked_scopes)
|
101
|
-
if schema_name && user_options[:to_ref] != false
|
102
|
-
# 首先将 Schema 写进 schemas 选项中去
|
103
|
-
schemas = user_options[:schemas]
|
104
|
-
unless schemas.key?(schema_name)
|
105
|
-
schemas[schema_name] = nil # 首先设置 schemas 防止出现无限循环
|
106
|
-
schemas[schema_name] = to_schema_doc(**user_options, to_ref: false) # 原地修改 schemas,无妨
|
107
|
-
end
|
108
|
-
|
109
|
-
return { '$ref': "#/components/schemas/#{schema_name}" }
|
110
|
-
end
|
111
|
-
|
112
|
-
stage_options = options(stage)
|
113
|
-
properties = @properties.filter do |name, property_schema|
|
114
|
-
# 根据 stage 过滤
|
115
|
-
next false if stage.nil?
|
116
|
-
next false if stage == :param && !property_schema.options(:param)
|
117
|
-
next false if stage == :render && !property_schema.options(:render)
|
118
|
-
|
119
|
-
# 根据 locked_scope 过滤
|
120
|
-
next true if locked_scopes.empty? # locked_scope 未提供时不过滤
|
121
|
-
property_scope = property_schema.options(stage, :scope)
|
122
|
-
property_scope = [property_scope] unless property_scope.is_a?(Array)
|
123
|
-
next true if property_scope.empty?
|
124
|
-
(locked_scopes - property_scope).empty? # user_scope 应被消耗殆尽
|
125
|
-
end
|
126
|
-
required_keys = properties.filter do |key, property_schema|
|
127
|
-
property_schema.options(stage, :required)
|
128
|
-
end.keys
|
129
|
-
properties = properties.transform_values do |property_schema|
|
130
|
-
property_schema.to_schema_doc(**user_options, to_ref: true)
|
131
|
-
end
|
132
62
|
|
133
63
|
schema = { type: 'object' }
|
134
|
-
schema[:description] =
|
64
|
+
schema[:description] = options[:description] if options[:description]
|
65
|
+
|
66
|
+
properties, required_keys = @properties.to_swagger_doc(stage: stage, locked_scopes: locked_scopes, **user_options)
|
135
67
|
schema[:properties] = properties unless properties.empty?
|
136
68
|
schema[:required] = required_keys unless required_keys.empty?
|
137
69
|
schema
|
@@ -144,18 +76,6 @@ module Meta
|
|
144
76
|
def locked_exclude
|
145
77
|
locked_options && locked_options[:exclude]
|
146
78
|
end
|
147
|
-
|
148
|
-
private
|
149
|
-
|
150
|
-
def resolve_property_value(object_value, name, property_schema, stage)
|
151
|
-
if property_schema.value?(stage)
|
152
|
-
nil
|
153
|
-
elsif object_value.is_a?(Hash) || object_value.is_a?(ObjectWrapper)
|
154
|
-
object_value.key?(name.to_s) ? object_value[name.to_s] : object_value[name.to_sym]
|
155
|
-
else
|
156
|
-
raise "不应该还有其他类型了,已经在类型转换中将其转换为 Meta::JsonSchema::ObjectWrapper 了"
|
157
|
-
end
|
158
|
-
end
|
159
79
|
end
|
160
80
|
end
|
161
81
|
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module Meta
|
6
|
+
module JsonSchema
|
7
|
+
class Properties
|
8
|
+
class StagingProperty
|
9
|
+
def initialize(param:, render:, none:)
|
10
|
+
@param_stage = param
|
11
|
+
@render_stage = render
|
12
|
+
@none_stage = none
|
13
|
+
end
|
14
|
+
|
15
|
+
def stage(stage = nil)
|
16
|
+
case stage
|
17
|
+
when :param
|
18
|
+
@param_stage
|
19
|
+
when :render
|
20
|
+
@render_stage
|
21
|
+
else
|
22
|
+
@none_stage
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def stage?(stage)
|
27
|
+
stage(stage) != nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def schema(stage = nil)
|
31
|
+
stage(stage).schema
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.build(options, build_schema)
|
35
|
+
param_opts, render_opts, common_opts = SchemaOptions.divide_to_param_and_render(options)
|
36
|
+
|
37
|
+
StagingProperty.new(
|
38
|
+
param: options[:param] === false ? nil : ScopingProperty.build(param_opts, build_schema),
|
39
|
+
render: options[:render] === false ? nil : ScopingProperty.build(render_opts, build_schema),
|
40
|
+
none: ScopingProperty.build(common_opts, build_schema)
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class ScopingProperty
|
46
|
+
attr_reader :scope, :schema
|
47
|
+
|
48
|
+
def initialize(scope: :all, schema:)
|
49
|
+
scope = :all if scope.nil?
|
50
|
+
scope = [scope] unless scope.is_a?(Array) || scope == :all
|
51
|
+
if scope.is_a?(Array) && scope.any? { |s| s.is_a?(Integer) }
|
52
|
+
raise ArgumentError, 'scope 选项内不可传递数字'
|
53
|
+
end
|
54
|
+
@scope = scope
|
55
|
+
|
56
|
+
@schema = schema
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.build(options, build_schema)
|
60
|
+
options = options.dup
|
61
|
+
scope = options.delete(:scope)
|
62
|
+
schema = build_schema.call(options)
|
63
|
+
ScopingProperty.new(scope: scope, schema: schema)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
extend Forwardable
|
68
|
+
|
69
|
+
def initialize(properties)
|
70
|
+
@properties = properties
|
71
|
+
end
|
72
|
+
|
73
|
+
def filter(object_value, user_options = {})
|
74
|
+
# 第一步,根据 user_options[:scope] 需要过滤一些字段
|
75
|
+
stage = user_options[:stage]
|
76
|
+
# 传递一个数字;因为 scope 不能包含数字,这里传递一个数字,使得凡是配置 scope 的属性都会被过滤
|
77
|
+
user_scope = user_options[:scope] || [0]
|
78
|
+
exclude = user_options.delete(:exclude) # 这里删除 exclude 选项,不要传递给下一层
|
79
|
+
properties = filter_by(stage: stage, user_scope: user_scope)
|
80
|
+
filtered_properties = properties.filter do |name, property|
|
81
|
+
# 通过 discard_missing 过滤
|
82
|
+
next false if user_options[:discard_missing] && !object_value.key?(name.to_s)
|
83
|
+
|
84
|
+
# 通过 locked_exclude 选项过滤
|
85
|
+
next false if exclude && exclude.include?(name)
|
86
|
+
|
87
|
+
# 默认返回 true
|
88
|
+
next true
|
89
|
+
end
|
90
|
+
|
91
|
+
# 第二步,递归过滤每一个属性
|
92
|
+
object = {}
|
93
|
+
errors = {}
|
94
|
+
filtered_properties.each do |name, property_schema|
|
95
|
+
value = resolve_property_value(object_value, name, property_schema, stage)
|
96
|
+
|
97
|
+
begin
|
98
|
+
object[name] = property_schema.filter(value, **user_options, object_value: object_value)
|
99
|
+
rescue JsonSchema::ValidationErrors => e
|
100
|
+
errors.merge! e.prepend_root(name).errors
|
101
|
+
end
|
102
|
+
end.to_h
|
103
|
+
|
104
|
+
if errors.empty?
|
105
|
+
object
|
106
|
+
else
|
107
|
+
raise JsonSchema::ValidationErrors.new(errors)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def to_swagger_doc(locked_scopes:, stage:, **user_options)
|
112
|
+
properties = filter_by(stage: stage, user_scope: locked_scopes)
|
113
|
+
required_keys = properties.filter do |key, property_schema|
|
114
|
+
property_schema.options[:required]
|
115
|
+
end.keys
|
116
|
+
properties = properties.transform_values do |property_schema |
|
117
|
+
property_schema.to_schema_doc(stage: stage, **user_options)
|
118
|
+
end
|
119
|
+
[properties, required_keys]
|
120
|
+
end
|
121
|
+
|
122
|
+
# 程序中有些地方用到了这三个方法
|
123
|
+
def_delegators :@properties, :empty?, :key?, :[]
|
124
|
+
|
125
|
+
def self.build_property(*args)
|
126
|
+
StagingProperty.build(*args)
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def filter_by(stage:, user_scope: false)
|
132
|
+
properties = @properties.filter do |name, property|
|
133
|
+
# 通过 stage 过滤。
|
134
|
+
next false unless property.stage?(stage)
|
135
|
+
property = property.stage(stage)
|
136
|
+
|
137
|
+
# 通过 user_scope 过滤
|
138
|
+
next true if property.scope == :all
|
139
|
+
(user_scope - property.scope).empty? # user_scope 应被消耗殆尽
|
140
|
+
end
|
141
|
+
properties.transform_values do |property|
|
142
|
+
property.stage(stage).schema
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def resolve_property_value(object_value, name, property_schema, stage)
|
147
|
+
if property_schema.value?
|
148
|
+
nil
|
149
|
+
elsif object_value.is_a?(Hash) || object_value.is_a?(ObjectWrapper)
|
150
|
+
object_value.key?(name.to_s) ? object_value[name.to_s] : object_value[name.to_sym]
|
151
|
+
else
|
152
|
+
raise "不应该还有其他类型了,已经在类型转换中将其转换为 Meta::JsonSchema::ObjectWrapper 了"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base_schema'
|
4
|
+
|
5
|
+
module Meta
|
6
|
+
module JsonSchema
|
7
|
+
class RefSchema < BaseSchema
|
8
|
+
attr_reader :schema
|
9
|
+
|
10
|
+
def initialize(schema, options = {})
|
11
|
+
super(options)
|
12
|
+
@schema = schema
|
13
|
+
end
|
14
|
+
|
15
|
+
def filter(value, user_options = {})
|
16
|
+
value = super
|
17
|
+
schema.filter(value, user_options)
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_schema_doc(user_options)
|
21
|
+
schema_name = schema.resolve_name(user_options[:stage])
|
22
|
+
|
23
|
+
# 首先将 Schema 写进 schemas 选项中去
|
24
|
+
schema_components = user_options[:schemas]
|
25
|
+
unless schema_components.key?(schema_name)
|
26
|
+
schema_components[schema_name] = nil # 首先设置 schemas 防止出现无限循环
|
27
|
+
schema_components[schema_name] = schema.to_schema_doc(**user_options) # 原地修改 schemas,无妨
|
28
|
+
end
|
29
|
+
|
30
|
+
# 返回的是 $ref 结构
|
31
|
+
{ '$ref': "#/components/schemas/#{schema_name}" }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -7,6 +7,4 @@ require_relative 'support/presenters'
|
|
7
7
|
require_relative 'schemas/base_schema'
|
8
8
|
require_relative 'schemas/object_schema'
|
9
9
|
require_relative 'schemas/array_schema'
|
10
|
-
require_relative 'builders/object_schema_builder'
|
11
|
-
require_relative 'builders/array_schema_builder'
|
12
10
|
require_relative 'builders/schema_builder_tool'
|
@@ -1,40 +1,43 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative '../../utils/kwargs/builder'
|
4
|
+
|
3
5
|
module Meta
|
4
6
|
module JsonSchema
|
5
7
|
module SchemaOptions
|
8
|
+
OPTIONS_CHECKER = Utils::KeywordArgs::Builder.build do
|
9
|
+
key :type, :items, :description, :presenter, :value, :format, :required, :default, :validate, :allowable, :properties, :convert
|
10
|
+
key :using, normalizer: ->(value) { value.is_a?(Proc) ? { resolve: value } : value }
|
11
|
+
key :param
|
12
|
+
key :render
|
13
|
+
end
|
14
|
+
|
6
15
|
@default_options = {
|
7
16
|
scope: [],
|
8
17
|
required: false
|
9
18
|
}
|
10
19
|
@allowable_options = (
|
11
|
-
%i[type description in value using default presenter convert scope items] +
|
20
|
+
%i[type description in value using default presenter convert scope items using] +
|
12
21
|
@default_options.keys +
|
13
22
|
JsonSchema::Validators.keys
|
14
23
|
).uniq
|
15
24
|
|
16
25
|
class << self
|
17
|
-
def
|
26
|
+
def divide_to_param_and_render(options)
|
18
27
|
common_opts = (options || {}).dup
|
19
28
|
param_opts = common_opts.delete(:param)
|
20
29
|
render_opts = common_opts.delete(:render)
|
21
30
|
|
22
31
|
param_opts = merge_common_to_stage(common_opts, param_opts)
|
23
32
|
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
|
33
|
+
[param_opts, render_opts, common_opts]
|
32
34
|
end
|
33
35
|
|
34
36
|
def normalize(options)
|
37
|
+
options = OPTIONS_CHECKER.check(options)
|
38
|
+
|
35
39
|
# 只要 options 中设置为 nil 的选项没有明确的意义,则下行代码是永远有效的
|
36
40
|
options = (@default_options.compact).merge(options.compact)
|
37
|
-
options[:scope] = [options[:scope]] unless options[:scope].is_a?(Array)
|
38
41
|
if options[:using]
|
39
42
|
if options[:type].nil?
|
40
43
|
options[:type] = 'object'
|
@@ -49,6 +52,14 @@ module Meta
|
|
49
52
|
|
50
53
|
options
|
51
54
|
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def merge_common_to_stage(common_opts, stage_opts)
|
59
|
+
stage_opts = {} if stage_opts.nil? || stage_opts == true
|
60
|
+
stage_opts = common_opts.merge(stage_opts) if stage_opts
|
61
|
+
stage_opts
|
62
|
+
end
|
52
63
|
end
|
53
64
|
end
|
54
65
|
end
|
data/lib/meta/rails.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
# 作为 Rails 插件
|
2
|
+
|
3
|
+
require_relative 'errors'
|
4
|
+
require_relative 'json_schema/schemas'
|
5
|
+
|
6
|
+
module Meta
|
7
|
+
module Rails
|
8
|
+
def self.setup
|
9
|
+
# 第一步,为 ActionController 添加一个新的 Renderer
|
10
|
+
ActionController::Renderers.add :json_on_schema do |obj, options|
|
11
|
+
options = options.dup
|
12
|
+
status = options.delete(:status) || 200
|
13
|
+
scope = options.delete(:scope) || :all
|
14
|
+
|
15
|
+
route_definitions = self.class.route_definitions
|
16
|
+
route_definition = route_definitions[[self.class, params[:action].to_sym]]
|
17
|
+
raise '未绑定 Route 定义' unless route_definition
|
18
|
+
|
19
|
+
meta_definition = route_definition.meta
|
20
|
+
raise '未提供 status 宏定义' unless meta_definition[:responses] && meta_definition[:responses][status]
|
21
|
+
|
22
|
+
render_schema = meta_definition[:responses][status]
|
23
|
+
str = render_schema.filter(obj, execution: self, stage: :render, scope: scope)
|
24
|
+
render json: str, **options
|
25
|
+
rescue JsonSchema::ValidationErrors => e
|
26
|
+
raise Errors::RenderingInvalid.new(e.errors)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module Plugin
|
31
|
+
def self.generate_swagger_doc(klass, **kwargs)
|
32
|
+
paths_and_routes = klass.route_definitions.values.map do |route_definition|
|
33
|
+
[route_definition.path, route_definition]
|
34
|
+
end
|
35
|
+
SwaggerDocUtil.generate_from_paths_and_routes(paths_and_routes, **kwargs)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.included(base)
|
39
|
+
# 已经被父类引入过,不再重复引入
|
40
|
+
return if self.respond_to?(:route_definitions)
|
41
|
+
|
42
|
+
# 为 ActionController 引入宏命令,宏命令在子类中生效
|
43
|
+
base.extend ClassMethods
|
44
|
+
|
45
|
+
# 为 ActionController 定义一个 Route Definitions,子类实例可以通过 self.class.route_definitions 访问
|
46
|
+
route_definitions = {}
|
47
|
+
base.define_singleton_method(:route_definitions) { route_definitions }
|
48
|
+
|
49
|
+
# 定义一个方法,子类在定义方法后,将当前的路由定义应用到该方法上
|
50
|
+
base.define_singleton_method(:apply_route_definition) do |klass, method_name|
|
51
|
+
if @current_route_builder
|
52
|
+
self.route_definitions[[klass, method_name]] = @current_route_builder.build
|
53
|
+
@current_route_builder = nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
# 触发 apply_route_definition 方法
|
57
|
+
base.define_singleton_method(:method_added) do |name|
|
58
|
+
apply_route_definition(self, name)
|
59
|
+
end
|
60
|
+
|
61
|
+
# 为 ActionController 定义一个方法,用于获取过滤后的参数
|
62
|
+
attr_accessor :params_on_schema
|
63
|
+
|
64
|
+
# 为 ActionController 定义一个 before_action,用于过滤参数
|
65
|
+
base.before_action do
|
66
|
+
route_definitions = self.class.route_definitions
|
67
|
+
route_definition = route_definitions[[self.class, params[:action].to_sym]]
|
68
|
+
next unless route_definition
|
69
|
+
|
70
|
+
meta_definition = route_definition.meta
|
71
|
+
next if meta_definition[:parameters].empty? && meta_definition[:request_body].nil?
|
72
|
+
|
73
|
+
raw_params = self.params.to_unsafe_h
|
74
|
+
final_params = {}
|
75
|
+
|
76
|
+
if meta_definition[:parameters]
|
77
|
+
parameters_meta = meta_definition[:parameters]
|
78
|
+
final_params = parameters_meta.filter(request)
|
79
|
+
end
|
80
|
+
if meta_definition[:request_body]
|
81
|
+
params_schema = meta_definition[:request_body]
|
82
|
+
final_params.merge! params_schema.filter(raw_params, stage: :param)
|
83
|
+
end
|
84
|
+
|
85
|
+
self.params_on_schema = final_params
|
86
|
+
rescue JsonSchema::ValidationErrors => e
|
87
|
+
raise Errors::ParameterInvalid.new(e.errors)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
module ClassMethods
|
92
|
+
def route(path = '', method = :all, &block)
|
93
|
+
@current_route_builder = RouteDSL::RouteBuilder.new(path, method, &block)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
data/lib/meta/swagger_doc.rb
CHANGED
@@ -4,10 +4,13 @@ module Meta
|
|
4
4
|
module SwaggerDocUtil
|
5
5
|
class << self
|
6
6
|
def generate(application, info: {}, servers: [])
|
7
|
-
|
7
|
+
paths_and_routes = get_paths_and_routes!(application)
|
8
|
+
return generate_from_paths_and_routes(paths_and_routes, info: info, servers: servers)
|
9
|
+
end
|
8
10
|
|
11
|
+
def generate_from_paths_and_routes(paths_and_routes, info: {}, servers: [])
|
9
12
|
schemas = {}
|
10
|
-
paths =
|
13
|
+
paths = paths_and_routes.group_by { |path, route| path }.map { |path, routes| [path, routes.map { |item| item[1] }]}.map do |path, routes|
|
11
14
|
operations = routes.map do |route|
|
12
15
|
[route.method.downcase.to_sym, generate_operation_object(route, schemas)]
|
13
16
|
end.to_h
|