meta-api 0.0.8 → 0.1.0

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 (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/lib/meta/application/execution.rb +19 -49
  4. data/lib/meta/application/metadata.rb +68 -15
  5. data/lib/meta/application/responses.rb +32 -0
  6. data/lib/meta/application/route.rb +4 -26
  7. data/lib/meta/config.rb +2 -3
  8. data/lib/meta/entity.rb +2 -3
  9. data/lib/meta/json_schema/builders/object_schema_builder.rb +45 -40
  10. data/lib/meta/json_schema/builders/schema_builder_tool.rb +2 -5
  11. data/lib/meta/json_schema/schemas/array_schema.rb +24 -14
  12. data/lib/meta/json_schema/schemas/base_schema.rb +107 -37
  13. data/lib/meta/json_schema/schemas/named_properties.rb +37 -0
  14. data/lib/meta/json_schema/schemas/object_schema.rb +58 -20
  15. data/lib/meta/json_schema/schemas/properties.rb +27 -76
  16. data/lib/meta/json_schema/schemas/ref_schema.rb +36 -9
  17. data/lib/meta/json_schema/schemas/scoping_schema.rb +38 -0
  18. data/lib/meta/json_schema/schemas/staging_schema.rb +59 -0
  19. data/lib/meta/json_schema/schemas/unsupported_schema.rb +22 -0
  20. data/lib/meta/json_schema/support/errors.rb +3 -0
  21. data/lib/meta/json_schema/support/json_object.rb +37 -0
  22. data/lib/meta/json_schema/support/schema_options.rb +0 -9
  23. data/lib/meta/json_schema/support/scope_matcher.rb +29 -0
  24. data/lib/meta/json_schema/support/type_converter.rb +3 -24
  25. data/lib/meta/route_dsl/meta_builder.rb +2 -2
  26. data/lib/meta/swagger_doc.rb +2 -25
  27. data/lib/meta/utils/kwargs/builder.rb +5 -3
  28. data/lib/meta/utils/route_dsl_builders.rb +2 -1
  29. data/meta-api.gemspec +6 -5
  30. metadata +75 -111
  31. data/.autoenv.zsh +0 -1
  32. data/.gitignore +0 -7
  33. data/.rubocop.yml +0 -28
  34. data/Gemfile +0 -19
  35. data/Gemfile.lock +0 -68
  36. data/README.md +0 -166
  37. data/Rakefile +0 -3
  38. data/docs/Rails.md +0 -61
  39. data/docs//345/220/215/347/247/260/347/224/261/346/235/245.md +0 -7
  40. data/docs//345/246/202/344/275/225/350/264/241/347/214/256.md +0 -10
  41. data/docs//346/225/231/347/250/213.md +0 -1708
  42. data/docs//347/264/242/345/274/225.md +0 -183
  43. data/examples/lobster.rb +0 -71
  44. data/examples/rack_app/README.md +0 -3
  45. data/examples/rack_app/config.ru +0 -6
  46. data/examples/rack_app/hello.rb +0 -6
  47. data/examples/rack_app/timing.rb +0 -15
  48. data/examples/rails_app/.gitattributes +0 -5
  49. data/examples/rails_app/.gitignore +0 -23
  50. data/examples/rails_app/.rspec +0 -1
  51. data/examples/rails_app/.ruby-version +0 -1
  52. data/examples/rails_app/Gemfile +0 -29
  53. data/examples/rails_app/Gemfile.lock +0 -190
  54. data/examples/rails_app/README.md +0 -11
  55. data/examples/rails_app/Rakefile +0 -6
  56. data/examples/rails_app/app/controllers/application_controller.rb +0 -7
  57. data/examples/rails_app/app/controllers/concerns/.keep +0 -0
  58. data/examples/rails_app/app/controllers/data_controller.rb +0 -63
  59. data/examples/rails_app/app/controllers/swagger_controller.rb +0 -13
  60. data/examples/rails_app/app/models/concerns/.keep +0 -0
  61. data/examples/rails_app/bin/rails +0 -4
  62. data/examples/rails_app/bin/rake +0 -4
  63. data/examples/rails_app/bin/setup +0 -25
  64. data/examples/rails_app/config/application.rb +0 -39
  65. data/examples/rails_app/config/boot.rb +0 -3
  66. data/examples/rails_app/config/credentials.yml.enc +0 -1
  67. data/examples/rails_app/config/environment.rb +0 -5
  68. data/examples/rails_app/config/environments/development.rb +0 -51
  69. data/examples/rails_app/config/environments/production.rb +0 -65
  70. data/examples/rails_app/config/environments/test.rb +0 -50
  71. data/examples/rails_app/config/initializers/cors.rb +0 -16
  72. data/examples/rails_app/config/initializers/filter_parameter_logging.rb +0 -8
  73. data/examples/rails_app/config/initializers/inflections.rb +0 -16
  74. data/examples/rails_app/config/initializers/meta_rails_plugin.rb +0 -3
  75. data/examples/rails_app/config/locales/en.yml +0 -33
  76. data/examples/rails_app/config/puma.rb +0 -43
  77. data/examples/rails_app/config/routes.rb +0 -13
  78. data/examples/rails_app/config.ru +0 -6
  79. data/examples/rails_app/lib/tasks/.keep +0 -0
  80. data/examples/rails_app/log/.keep +0 -0
  81. data/examples/rails_app/public/robots.txt +0 -1
  82. data/examples/rails_app/spec/data_controller_spec.rb +0 -60
  83. data/examples/rails_app/spec/rails_helper.rb +0 -55
  84. data/examples/rails_app/spec/spec_helper.rb +0 -94
  85. data/examples/rails_app/spec/swagger_controller_spec.rb +0 -13
  86. data/examples/rails_app/tmp/.keep +0 -0
  87. data/examples/rails_app/tmp/pids/.keep +0 -0
@@ -5,11 +5,35 @@ require_relative '../support/schema_options'
5
5
 
6
6
  module Meta
7
7
  module JsonSchema
8
+ # 表示一个基本类型的 Schema,或继承自该类表示一个拥有其他扩展能力的 Schema.
9
+ #
10
+ # 该类包含了通用 JsonSchema 思维的基本逻辑,比如 stage 和 scope. 其他 Schema 类应当主动地继
11
+ # 承自该类,这样就会自动获得 stage 和 scope 的能力。
12
+ #
13
+ # 该类剩余的逻辑是提供一个 `options` 属性,用于描述该 Schema. 因此,直接实例化该类可以用于表示
14
+ # 基本类型,而继承该类可以用于表示还有内部递归处理的对象和数组类型。这时,应当在子类的构造函数中调
15
+ # 用父类的构造方法,以便初始化 `options` 属性。并且在子类中重写 `filter_internal` 方法,实现
16
+ # 内部递归处理的逻辑。这种模式的案例主要是 `ObjectSchema` 和 `ArraySchema`.
17
+ #
18
+ # 如果是组合模式,也应当继承自该类,以便获得 stage 和 scope 的能力。但是,组合模式的 `options`
19
+ # 调用是非法的,因此不应当在构造函数中调用父类的构造方法。此时 options 为 nil,这样用到 options
20
+ # 的地方都会抛出异常(NoMethodError: undefined method `[]' for nil:NilClass)。这种模式
21
+ # 的案例很多,包括 StagingSchema、RefSchema 等。
8
22
  class BaseSchema
9
23
  OPTIONS_CHECKER = Utils::KeywordArgs::Builder.build do
10
24
  key :type, :items, :description, :presenter, :value, :default, :properties, :convert
11
25
  key :validate, :required, :format, :allowable
12
- key :param, :render
26
+ key :before, :after
27
+ key :if
28
+ end
29
+
30
+ USER_OPTIONS_CHECKER = Utils::KeywordArgs::Builder.build do
31
+ key :execution, :object_value, :type_conversion, :validation, :user_data
32
+ key :stage, validator: ->(value) { raise ArgumentError, "stage 只能取值为 :param 或 :render" unless [:param, :render].include?(value) }
33
+
34
+ # 以下是 ObjectSchema 需要的选项
35
+ # extra_properties 只能取值为 :ignore、:raise_error
36
+ key :discard_missing, :extra_properties, :exclude, :scope
13
37
  end
14
38
 
15
39
  # `options` 包含了转换器、验证器、文档、选项。
@@ -22,25 +46,22 @@ module Meta
22
46
  attr_reader :options
23
47
 
24
48
  def initialize(options = {})
49
+ raise ArgumentError, 'options 必须是 Hash 类型' unless options.is_a?(Hash)
25
50
  options = OPTIONS_CHECKER.check(options)
26
51
  raise '不允许 BaseSchema 直接接受 array 类型,必须通过继承使用 ArraySchema' if options[:type] == 'array' && self.class == BaseSchema
27
52
 
28
53
  @options = SchemaOptions.normalize(options).freeze
29
54
  end
30
55
 
31
- USER_OPTIONS_CHECKER = Utils::KeywordArgs::Builder.build do
32
- key :execution, :object_value, :type_conversion, :validation, :user_data
33
- key :stage, validator: ->(value) { raise ArgumentError, "stage 只能取值为 :param 或 :render" unless [:param, :render].include?(value) }
34
-
35
- # 以下是 ObjectSchema 需要的选项
36
- # extra_properties 只能取值为 :ignore、:raise_error
37
- key :discard_missing, :extra_properties, :exclude, :scope
56
+ def filter?
57
+ true
38
58
  end
39
59
 
40
60
  def filter(value, user_options = {})
41
61
  user_options = USER_OPTIONS_CHECKER.check(user_options)
42
62
 
43
- value = resolve_value(user_options) if options[:value]
63
+ value = value_callback(user_options) if options[:value]
64
+ value = before_callback(value, user_options) if options[:before]
44
65
  value = JsonSchema::Presenters.present(options[:presenter], value) if options[:presenter]
45
66
  value = resolve_default_value(options[:default]) if value.nil? && options.key?(:default)
46
67
  value = options[:convert].call(value) if options[:convert]
@@ -59,35 +80,55 @@ module Meta
59
80
  # 第二步,做校验。
60
81
  validate!(value, options) unless user_options[:validation] == false
61
82
 
83
+ # 第三步,如果存在内部属性,递归调用。
84
+ value = filter_internal(value, user_options) unless value.nil?
85
+
86
+ # 最后,返回 value
87
+ value = after_callback(value, user_options) if options[:after]
62
88
  value
63
89
  end
64
90
 
65
- def value?
66
- options[:value] != nil
91
+ # 返回能够处理 scope 和 stage 的 schema(可以是 self),否则应返回 UnsupportedStageSchema 或 nil.
92
+ def find_schema(scope:, stage:)
93
+ staged(stage)&.scoped(scope)
67
94
  end
68
95
 
69
- def resolve_value(user_options)
70
- value_proc = options[:value]
71
- if value_proc.lambda?
72
- value_proc_params = []
73
- value_proc_params << user_options[:object_value] if value_proc.arity >= 1
74
- value_proc_params << user_options[:user_data] if value_proc.arity >= 2
75
- else
76
- value_proc_params = [user_options[:object_value], user_options[:user_data]]
77
- end
96
+ # 返回能够处理 stage 的 schema(可以是 self),否则返回 UnsupportedStageSchema.
97
+ def staged(stage)
98
+ self
99
+ end
78
100
 
79
- if user_options[:execution]
80
- user_options[:execution].instance_exec(*value_proc_params, &value_proc)
101
+ # 返回能够处理 scope 的 schema(可以是 self),否则返回 nil.
102
+ def scoped(scope)
103
+ self
104
+ end
105
+
106
+ def defined_scopes(stage:, defined_scopes_mapping:)
107
+ []
108
+ end
109
+
110
+ # 执行 if: 选项,返回 true 或 false
111
+ def if?(user_options)
112
+ return true if options[:if].nil?
113
+
114
+ execution = user_options[:execution]
115
+ if execution
116
+ execution.instance_exec(&options[:if])
81
117
  else
82
- value_proc.call(*value_proc_params)
118
+ options[:if]&.call
83
119
  end
84
120
  end
85
121
 
122
+ def value?
123
+ options[:value] != nil
124
+ end
125
+
86
126
  # 生成 Swagger 文档的 schema 格式。
87
127
  #
88
128
  # 选项:
89
129
  # - stage: 传递 :param 或 :render
90
- # - schemas: 用于保存已经生成的 Schema
130
+ # - schema_docs_mapping: 用于保存已经生成的 Schema
131
+ # - defined_scopes_mapping: 用于缓存已经定义的 scopes
91
132
  # - presenter: 兼容 Grape 框架的实体类
92
133
  def to_schema_doc(**user_options)
93
134
  return Presenters.to_schema_doc(options[:presenter], options) if options[:presenter]
@@ -106,22 +147,51 @@ module Meta
106
147
 
107
148
  private
108
149
 
109
- def validate!(value, stage_options)
110
- stage_options.each do |key, option|
111
- validator = JsonSchema::Validators[key]
112
- validator&.call(value, option, stage_options)
113
- end
150
+ def validate!(value, stage_options)
151
+ stage_options.each do |key, option|
152
+ validator = JsonSchema::Validators[key]
153
+ validator&.call(value, option, stage_options)
114
154
  end
155
+ end
115
156
 
116
- def resolve_default_value(default_resolver)
117
- if default_resolver.respond_to?(:call)
118
- default_resolver.call
119
- elsif default_resolver.respond_to?(:dup)
120
- default_resolver.dup
121
- else
122
- default_resolver
123
- end
157
+ def resolve_value(value, user_options, value_proc, arguments)
158
+ if value_proc.lambda?
159
+ value_proc_params = arguments[0...value_proc.arity]
160
+ else
161
+ value_proc_params = arguments
162
+ end
163
+ if user_options[:execution]
164
+ user_options[:execution].instance_exec(*value_proc_params, &value_proc)
165
+ else
166
+ value_proc.call(*value_proc_params)
167
+ end
168
+ end
169
+
170
+ def resolve_default_value(default_resolver)
171
+ if default_resolver.respond_to?(:call)
172
+ default_resolver.call
173
+ elsif default_resolver.respond_to?(:dup)
174
+ default_resolver.dup
175
+ else
176
+ default_resolver
124
177
  end
178
+ end
179
+
180
+ def filter_internal(value, user_options)
181
+ value
182
+ end
183
+
184
+ def value_callback(user_options)
185
+ resolve_value(nil, user_options, options[:value], [user_options[:object_value], user_options[:user_data]])
186
+ end
187
+
188
+ def before_callback(value, user_options)
189
+ resolve_value(value, user_options, options[:before], [value, user_options[:object_value], user_options[:user_data]])
190
+ end
191
+
192
+ def after_callback(value, user_options)
193
+ resolve_value(value, user_options, options[:after], [value, user_options[:object_value], user_options[:user_data]])
194
+ end
125
195
  end
126
196
  end
127
197
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+ require_relative 'properties'
5
+
6
+ module Meta
7
+ module JsonSchema
8
+ class NamedProperties < Properties
9
+ attr_reader :schema_base_name
10
+
11
+ def initialize(properties, schema_base_name)
12
+ super(properties)
13
+
14
+ raise TypeError, "schema_name 必须是一个 String,当前是:#{schema_base_name.class}" unless schema_base_name.is_a?(String)
15
+ raise ArgumentError, 'schema_name 不能为 nil 或空字符串' if schema_base_name.nil? || schema_base_name.empty?
16
+
17
+ # 修正 base_name,确保其不包含 Entity 后缀
18
+ schema_base_name = schema_base_name.delete_suffix('Entity') if schema_base_name&.end_with?('Entity')
19
+ @schema_base_name = schema_base_name
20
+ end
21
+
22
+ def schema_name(stage)
23
+ if stage == :render
24
+ "#{schema_base_name}Entity"
25
+ elsif stage == :param
26
+ "#{schema_base_name}Params"
27
+ else
28
+ raise ArgumentError, "stage 必须是 :render 或 :param,当前是:#{stage}"
29
+ end
30
+ end
31
+
32
+ def merge(other_properties)
33
+ raise UnsupportedError, 'NamedProperties 不支持 merge 操作'
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,69 +1,91 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../../utils/kwargs/check'
4
+ require_relative 'named_properties'
4
5
 
5
6
  module Meta
6
7
  module JsonSchema
7
8
  class ObjectSchema < BaseSchema
8
- attr_reader :properties, :locked_options
9
+ attr_reader :properties
10
+ # 只有 ObjectSchema 对象才有 locked_options,因为 locked_options 多是用来锁定属性的行为的,包括:
11
+ # scope:、discard_missing:、exclude: 等
12
+ attr_reader :locked_options
9
13
 
14
+ # stage 和 scope 选项在两个 CHECKER 下都用到了
10
15
  USER_OPTIONS_CHECKER = Utils::KeywordArgs::Builder.build do
11
- permit_extras true
12
-
16
+ key :stage
13
17
  key :scope, normalizer: ->(value) {
14
18
  raise ArgumentError, 'scope 选项不可传递 nil' if value.nil?
15
19
  value = [value] unless value.is_a?(Array)
16
20
  value
17
21
  }
22
+ key :discard_missing, :exclude, :extra_properties, :type_conversion, :validation
23
+ key :execution, :user_data, :object_value
18
24
  end
25
+ TO_SCHEMA_DOC_CHECKER = Utils::KeywordArgs::Builder.build do
26
+ key :stage
27
+ key :scope, normalizer: ->(value) {
28
+ raise ArgumentError, 'scope 选项不可传递 nil' if value.nil?
29
+ value = [value] unless value.is_a?(Array)
30
+ value
31
+ }
32
+ key :schema_docs_mapping, :defined_scopes_mapping
33
+ end
34
+
35
+ def initialize(properties:, options: {}, locked_options: {})
36
+ raise ArgumentError, 'properties 必须是 Properties 实例' unless properties.is_a?(Properties)
19
37
 
20
- def initialize(properties: nil, options: {}, locked_options: {}, schema_name_resolver: proc { nil })
21
38
  super(options)
22
39
 
23
40
  @properties = properties || Properties.new({}) # property 包含 stage,stage 包含 scope、schema
24
41
  @properties = Properties.new(@properties) if @properties.is_a?(Hash)
25
42
  @locked_options = USER_OPTIONS_CHECKER.check(locked_options || {})
26
- @schema_name_resolver = schema_name_resolver || proc { nil }
27
43
  end
28
44
 
29
45
  # 复制一个新的 ObjectSchema,只有 options 不同
30
46
  def dup(options)
47
+ raise UnsupportedError, 'dup 不应该再执行了'
48
+
31
49
  self.class.new(
32
50
  properties: properties,
33
51
  options: options,
34
52
  locked_options: locked_options,
35
- schema_name_resolver: @schema_name_resolver
36
53
  )
37
54
  end
38
55
 
39
56
  def filter(object_value, user_options = {})
40
57
  # 合并 user_options
41
- user_options = user_options.merge(locked_options) if locked_options
42
58
  user_options = USER_OPTIONS_CHECKER.check(user_options)
43
-
44
- object_value = super(object_value, user_options)
45
-
46
- return nil if object_value.nil?
47
- @properties.filter(object_value, user_options)
59
+ user_options = self.class.merge_user_options(user_options, locked_options) if locked_options
60
+ super
48
61
  end
49
62
 
50
- # 合并其他的属性,并返回一个新的 ObjectSchema
51
- def merge_other_properties(properties)
52
- ObjectSchema.new(properties: self.properties.merge(properties))
63
+ def naming?
64
+ properties.is_a?(NamedProperties)
53
65
  end
54
66
 
55
- def resolve_name(stage)
67
+ def resolve_name(stage, user_scopes, defined_scopes)
68
+ raise ArgumentError, 'stage 不能为 nil' if stage.nil?
69
+
70
+ # 先合成外面传进来的 scope
56
71
  locked_scopes = (locked_options || {})[:scope] || []
57
- @schema_name_resolver.call(stage, locked_scopes)
72
+ user_scopes = (user_scopes + locked_scopes).uniq
73
+ scopes = user_scopes & defined_scopes
74
+
75
+ # 再根据 stage 和 scope 生成为当前的 Schema 生成一个合适的名称,要求保证唯一性
76
+ schema_name = properties.schema_name(stage)
77
+ schema_name += "__#{scopes.join('__')}" unless scopes.empty?
78
+ schema_name
58
79
  end
59
80
 
60
- def to_schema_doc(stage: nil, **user_options)
61
- locked_scopes = (locked_options || {})[:scope] || []
81
+ def to_schema_doc(user_options = {})
82
+ user_options = TO_SCHEMA_DOC_CHECKER.check(user_options)
83
+ user_options = self.class.merge_user_options(user_options, locked_options) if locked_options
62
84
 
63
85
  schema = { type: 'object' }
64
86
  schema[:description] = options[:description] if options[:description]
65
87
 
66
- properties, required_keys = @properties.to_swagger_doc(stage: stage, locked_scopes: locked_scopes, **user_options)
88
+ properties, required_keys = @properties.to_swagger_doc(**user_options)
67
89
  schema[:properties] = properties unless properties.empty?
68
90
  schema[:required] = required_keys unless required_keys.empty?
69
91
  schema
@@ -76,6 +98,22 @@ module Meta
76
98
  def locked_exclude
77
99
  locked_options && locked_options[:exclude]
78
100
  end
101
+
102
+ private
103
+
104
+ def filter_internal(object_value, user_options)
105
+ @properties.filter(object_value, user_options)
106
+ end
107
+
108
+ def self.merge_user_options(user_options, locked_options)
109
+ user_options.merge(locked_options) do |key, user_value, locked_value|
110
+ if key == :scope
111
+ user_value + locked_value
112
+ else
113
+ locked_value
114
+ end
115
+ end
116
+ end
79
117
  end
80
118
  end
81
119
  end
@@ -5,85 +5,33 @@ require 'forwardable'
5
5
  module Meta
6
6
  module JsonSchema
7
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
8
  extend Forwardable
68
9
 
69
10
  def initialize(properties)
70
11
  @properties = properties
71
12
  end
72
13
 
14
+ # user_options 包括 stage, scope, extra_properties, discard_missing, exclude、execution、user_data
73
15
  def filter(object_value, user_options = {})
16
+ # 首先,要将 object_value 转化为 ObjectWrapper
17
+ object_value = JsonObject.wrap(object_value)
18
+
74
19
  # 第一步,根据 user_options[:scope] 需要过滤一些字段
75
20
  stage = user_options[:stage]
76
21
  # 传递一个数字;因为 scope 不能包含数字,这里传递一个数字,使得凡是配置 scope 的属性都会被过滤
77
22
  user_scope = user_options[:scope] || [0]
78
23
  exclude = user_options.delete(:exclude) # 这里删除 exclude 选项,不要传递给下一层
79
24
  properties = filter_by(stage: stage, user_scope: user_scope)
80
- filtered_properties = properties.filter do |name, property|
25
+ filtered_properties = properties.filter do |name, property_schema|
81
26
  # 通过 discard_missing 过滤
82
27
  next false if user_options[:discard_missing] && !object_value.key?(name.to_s)
83
28
 
84
29
  # 通过 locked_exclude 选项过滤
85
30
  next false if exclude && exclude.include?(name)
86
31
 
32
+ # 通过 if 选项过滤
33
+ next false unless property_schema.if?(user_options)
34
+
87
35
  # 默认返回 true
88
36
  next true
89
37
  end
@@ -91,12 +39,14 @@ module Meta
91
39
  # 第二步,递归过滤每一个属性
92
40
  object = {}
93
41
  errors = {}
42
+ cause = nil
94
43
  filtered_properties.each do |name, property_schema|
95
44
  value = resolve_property_value(object_value, name, property_schema)
96
45
 
97
46
  begin
98
47
  object[name] = property_schema.filter(value, **user_options, object_value: object_value)
99
48
  rescue JsonSchema::ValidationErrors => e
49
+ cause = e.cause || e if cause.nil? # 将第一次出现的错误作为 cause
100
50
  errors.merge! e.prepend_root(name).errors
101
51
  end
102
52
  end.to_h
@@ -108,54 +58,55 @@ module Meta
108
58
 
109
59
  if errors.empty?
110
60
  object
61
+ elsif cause
62
+ begin
63
+ raise cause
64
+ rescue
65
+ raise JsonSchema::ValidationErrors.new(errors)
66
+ end
111
67
  else
112
68
  raise JsonSchema::ValidationErrors.new(errors)
113
69
  end
114
70
  end
115
71
 
116
- def to_swagger_doc(locked_scopes:, stage:, **user_options)
72
+ # user_options 包括 stage, scope, schema_docs_mapping, defined_scopes_mapping
73
+ def to_swagger_doc(scope: [], stage: nil, **user_options)
74
+ locked_scopes = scope
117
75
  properties = filter_by(stage: stage, user_scope: locked_scopes)
118
76
  required_keys = properties.filter do |key, property_schema|
119
77
  property_schema.options[:required]
120
78
  end.keys
121
79
  properties = properties.transform_values do |property_schema |
122
- property_schema.to_schema_doc(stage: stage, **user_options)
80
+ property_schema.to_schema_doc(stage: stage, scope: scope, **user_options)
123
81
  end
124
82
  [properties, required_keys]
125
83
  end
126
84
 
127
85
  # 程序中有些地方用到了这三个方法
128
- def_delegators :@properties, :empty?, :key?, :[]
86
+ def_delegators :@properties, :empty?, :key?, :[], :each
129
87
 
130
88
  def merge(properties)
131
89
  self.class.new(@properties.merge(properties.instance_eval { @properties }))
132
90
  end
133
91
 
134
92
  def self.build_property(*args)
135
- StagingProperty.build(*args)
93
+ StagingSchema.build_from_options(*args)
136
94
  end
137
95
 
138
96
  private
139
97
 
140
98
  def filter_by(stage:, user_scope: false)
141
- properties = @properties.filter do |name, property|
142
- # 通过 stage 过滤。
143
- next false unless property.stage?(stage)
144
- property = property.stage(stage)
145
-
146
- # 通过 user_scope 过滤
147
- next true if property.scope == :all
148
- (user_scope - property.scope).empty? # user_scope 应被消耗殆尽
149
- end
150
- properties.transform_values do |property|
151
- property.stage(stage).schema
99
+ @properties.transform_values do |property|
100
+ property.find_schema(stage: stage, scope: user_scope)
101
+ end.filter do |name, schema|
102
+ schema.filter?
152
103
  end
153
104
  end
154
105
 
155
106
  def resolve_property_value(object_value, name, property_schema)
156
107
  if property_schema.value?
157
108
  nil
158
- elsif object_value.is_a?(Hash) || object_value.is_a?(ObjectWrapper)
109
+ elsif object_value.is_a?(Hash) || object_value.is_a?(JsonObject)
159
110
  object_value.key?(name.to_s) ? object_value[name.to_s] : object_value[name.to_sym]
160
111
  else
161
112
  raise "不应该还有其他类型了,已经在类型转换中将其转换为 Meta::JsonSchema::ObjectWrapper 了"
@@ -5,31 +5,58 @@ require_relative 'base_schema'
5
5
  module Meta
6
6
  module JsonSchema
7
7
  class RefSchema < BaseSchema
8
- attr_reader :schema
8
+ attr_reader :object_schema
9
+
10
+ def initialize(object_schema, options = {})
11
+ raise ArgumentError, 'object_schema 必须是一个 ObjectSchema' unless object_schema.is_a?(ObjectSchema)
9
12
 
10
- def initialize(schema, options = {})
11
13
  super(options)
12
- @schema = schema
14
+ @object_schema = object_schema
13
15
  end
14
16
 
15
17
  def filter(value, user_options = {})
16
18
  value = super
17
- schema.filter(value, user_options)
19
+ object_schema.filter(value, user_options)
18
20
  end
19
21
 
20
22
  def to_schema_doc(user_options)
21
- schema_name = schema.resolve_name(user_options[:stage])
23
+ raise '引用的 ObjectSchema 没有包含命名逻辑,无法生成文档' unless object_schema.naming?
22
24
 
23
- # 首先将 Schema 写进 schemas 选项中去
24
- schema_components = user_options[:schemas]
25
+ # 首先,要求出 defined_scopes
26
+ defined_scopes = self.defined_scopes(stage: user_options[:stage], defined_scopes_mapping: user_options[:defined_scopes_mapping])
27
+ # 然后,求出 schema_name
28
+ schema_name = object_schema.resolve_name(user_options[:stage], user_options[:scope], defined_scopes)
29
+ # 接着将 Schema 写进 schemas 选项中去
30
+ schema_components = user_options[:schema_docs_mapping] || {}
25
31
  unless schema_components.key?(schema_name)
26
32
  schema_components[schema_name] = nil # 首先设置 schemas 防止出现无限循环
27
- schema_components[schema_name] = schema.to_schema_doc(**user_options) # 原地修改 schemas,无妨
33
+ schema_components[schema_name] = object_schema.to_schema_doc(**user_options) # 原地修改 schemas,无妨
28
34
  end
29
35
 
30
- # 返回的是 $ref 结构
36
+ # 最后,返回这个 $ref 结构
31
37
  { '$ref': "#/components/schemas/#{schema_name}" }
32
38
  end
39
+
40
+ def defined_scopes(stage:, defined_scopes_mapping:)
41
+ defined_scopes_mapping ||= {}
42
+
43
+ schema_name = object_schema.properties.schema_name(stage)
44
+ return defined_scopes_mapping[schema_name] if defined_scopes_mapping.key?(schema_name)
45
+
46
+ defined_scopes_mapping[schema_name] = []
47
+ defined_scopes = object_schema.properties.each.map do |name, property|
48
+ property.defined_scopes(stage: stage, defined_scopes_mapping: defined_scopes_mapping)
49
+ end.flatten.uniq.sort
50
+ defined_scopes_mapping[schema_name] = defined_scopes
51
+ defined_scopes
52
+ end
53
+
54
+ private
55
+
56
+ # # TODO: 这种带有组合方式的 Schema,让我联想到,每次 BaseSchema 新增一个方法都要在子 Schema 中加一遍,很烦!
57
+ # def defined_scopes
58
+ # schema.defined_scopes
59
+ # end
33
60
  end
34
61
  end
35
62
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../support/scope_matcher'
4
+
5
+ module Meta
6
+ module JsonSchema
7
+ class ScopingSchema < BaseSchema
8
+ attr_reader :scope_matcher, :schema
9
+
10
+ def initialize(scope_matcher_options: , schema:)
11
+ @scope_matcher = ScopeMatcher.new(scope_matcher_options)
12
+ @schema = schema
13
+ end
14
+
15
+ def scoped(user_scopes)
16
+ @scope_matcher.match?(user_scopes) ? schema : unsupported_schema(user_scopes)
17
+ end
18
+
19
+ def defined_scopes(**kwargs)
20
+ scope_matcher.defined_scopes
21
+ end
22
+
23
+ private
24
+
25
+ def unsupported_schema(user_scopes)
26
+ UnsupportedSchema.new(:scope, user_scopes)
27
+ end
28
+
29
+ def self.build_from_options(options, build_schema)
30
+ options = options.dup
31
+ scope_matcher_options = options.delete(:scope)
32
+ schema = build_schema.call(options)
33
+ schema = ScopingSchema.new(scope_matcher_options: scope_matcher_options, schema: schema) if scope_matcher_options
34
+ schema
35
+ end
36
+ end
37
+ end
38
+ end