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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.autoenv.zsh +1 -0
  3. data/.gitignore +6 -0
  4. data/.rubocop.yml +28 -0
  5. data/Gemfile +18 -0
  6. data/Gemfile.lock +66 -0
  7. data/LICENSE.txt +502 -0
  8. data/README.md +149 -0
  9. data/Rakefile +3 -0
  10. data/config/locales/zh-CN.yml +6 -0
  11. data/docs//345/220/215/347/247/260/347/224/261/346/235/245.md +7 -0
  12. data/docs//346/225/231/347/250/213.md +1199 -0
  13. data/docs//347/264/242/345/274/225.md +173 -0
  14. data/examples/lobster.rb +71 -0
  15. data/examples/rack_app/README.md +3 -0
  16. data/examples/rack_app/config.ru +6 -0
  17. data/examples/rack_app/hello.rb +6 -0
  18. data/examples/rack_app/timing.rb +15 -0
  19. data/lib/meta/api.rb +3 -0
  20. data/lib/meta/application/application.rb +63 -0
  21. data/lib/meta/application/execution.rb +178 -0
  22. data/lib/meta/application/meta.rb +71 -0
  23. data/lib/meta/application/path_matching_mod.rb +53 -0
  24. data/lib/meta/application/route.rb +58 -0
  25. data/lib/meta/application.rb +42 -0
  26. data/lib/meta/entity.rb +59 -0
  27. data/lib/meta/errors.rb +29 -0
  28. data/lib/meta/json_schema/builders/array_schema_builder.rb +29 -0
  29. data/lib/meta/json_schema/builders/object_schema_builder.rb +120 -0
  30. data/lib/meta/json_schema/builders/schema_builder_tool.rb +29 -0
  31. data/lib/meta/json_schema/schemas/array_schema.rb +40 -0
  32. data/lib/meta/json_schema/schemas/base_schema.rb +110 -0
  33. data/lib/meta/json_schema/schemas/object_schema.rb +161 -0
  34. data/lib/meta/json_schema/schemas.rb +12 -0
  35. data/lib/meta/json_schema/support/errors.rb +38 -0
  36. data/lib/meta/json_schema/support/presenters.rb +35 -0
  37. data/lib/meta/json_schema/support/schema_options.rb +55 -0
  38. data/lib/meta/json_schema/support/type_converter.rb +137 -0
  39. data/lib/meta/json_schema/support/validators.rb +54 -0
  40. data/lib/meta/load_i18n.rb +8 -0
  41. data/lib/meta/route_dsl/action_builder.rb +15 -0
  42. data/lib/meta/route_dsl/application_builder.rb +108 -0
  43. data/lib/meta/route_dsl/chain_builder.rb +48 -0
  44. data/lib/meta/route_dsl/helpers.rb +15 -0
  45. data/lib/meta/route_dsl/meta_builder.rb +57 -0
  46. data/lib/meta/route_dsl/parameters_builder.rb +24 -0
  47. data/lib/meta/route_dsl/route_builder.rb +85 -0
  48. data/lib/meta/route_dsl/uniformed_params_builder.rb +34 -0
  49. data/lib/meta/swagger_doc.rb +86 -0
  50. data/lib/meta/utils/path.rb +20 -0
  51. data/meta-api.gemspec +23 -0
  52. metadata +96 -0
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'execution'
4
+ require_relative 'path_matching_mod'
5
+ require_relative 'meta'
6
+
7
+ module Meta
8
+ class Route
9
+ include PathMatchingMod.new(path_method: :path, matching_mode: :full)
10
+
11
+ attr_reader :path, :method, :meta, :actions
12
+
13
+ def initialize(path: '', method: :all, meta: {}, actions: [])
14
+ @path = Utils::Path.normalize_path(path)
15
+ @method = method
16
+ @meta = Meta.new(meta)
17
+ @actions = actions
18
+ end
19
+
20
+ def execute(execution, remaining_path)
21
+ path_matching.merge_path_params(remaining_path, execution.request)
22
+
23
+ # 依次执行这个环境
24
+ begin
25
+ execution.parse_parameters(@meta[:parameters]) if @meta[:parameters]
26
+ execution.parse_request_body(@meta[:request_body]) if @meta[:request_body]
27
+
28
+ actions.each { |b| execution.instance_eval(&b) }
29
+
30
+ render_entity(execution) if @meta[:responses]
31
+ rescue Execution::Abort
32
+ execution
33
+ end
34
+ end
35
+
36
+ def match?(execution, remaining_path)
37
+ request = execution.request
38
+ remaining_path = '' if remaining_path == '/'
39
+ method = request.request_method
40
+
41
+ return false unless path_matching.match?(remaining_path)
42
+ return false unless @method == :all || @method.to_s.upcase == method
43
+ return true
44
+ end
45
+
46
+ private
47
+
48
+ def render_entity(execution)
49
+ responses = @meta[:responses]
50
+ status = execution.response.status
51
+ codes = responses.keys
52
+ return unless codes.include?(status)
53
+
54
+ entity_schema = responses[status]
55
+ execution.render_entity(entity_schema) if entity_schema
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'route_dsl/route_builder'
4
+ require_relative 'route_dsl/application_builder'
5
+ require_relative 'application/application'
6
+
7
+ # 结构组织如下:
8
+ # - lib/application.rb: 模块实例
9
+ # - route_dsl/application_builder.rb: DSL 语法的 Builder
10
+ # - application.rb(本类): 综合以上两个类q的方法到一个类当中
11
+ module Meta
12
+ class Application
13
+ class << self
14
+ extend Forwardable
15
+
16
+ include Execution::MakeToRackMiddleware
17
+
18
+ attr_reader :builder
19
+
20
+ def inherited(mod)
21
+ super
22
+
23
+ mod.instance_variable_set(:@builder, RouteDSL::ApplicationBuilder.new)
24
+ end
25
+
26
+ # 读取应用的元信息
27
+ def_delegators :app, :prefix, :routes, :applications, :execute, :to_swagger_doc
28
+
29
+ # DSL 调用委托给内部 Builder
30
+ builder_methods = (RouteDSL::ApplicationBuilder.public_instance_methods(false) - ['build'])
31
+ def_delegators :builder, *builder_methods
32
+
33
+ def app
34
+ @app || @app = builder.build
35
+ end
36
+
37
+ def build(**args)
38
+ @app = builder.build(**args)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+ require_relative 'errors'
5
+ require_relative 'json_schema/schemas'
6
+
7
+ # Meta::Entity 是 ObjectSchemaBuilder 的一个类封装,它不应有自己的逻辑
8
+ module Meta
9
+ class Entity
10
+ class << self
11
+ extend Forwardable
12
+
13
+ attr_reader :schema_builder
14
+
15
+ def inherited(base)
16
+ base.instance_eval do
17
+ @schema_builder = JsonSchema::ObjectSchemaBuilder.new
18
+ @schema_builder.schema_name(proc { |locked_scope, stage|
19
+ generate_schema_name(locked_scope, stage)
20
+ })
21
+ end
22
+ end
23
+
24
+ def_delegators :schema_builder, :property, :param, :expose, :use, :lock, :locked, :schema_name, :to_schema
25
+
26
+ def method_missing(method, *args)
27
+ if method =~ /^lock_(\w+)$/
28
+ schema_builder.send(method, *args)
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def generate_schema_name(stage, locked_scopes)
37
+ # 匿名类不考虑自动生成名称
38
+ return nil unless self.name
39
+
40
+ schema_name = self.name.gsub('::', '_')
41
+ schema_name = schema_name.delete_suffix('Entity') unless schema_name == 'Entity'
42
+
43
+ # 先考虑 stage
44
+ case stage
45
+ when :param
46
+ schema_name += 'Params'
47
+ when :render
48
+ schema_name += 'Entity'
49
+ end
50
+
51
+ # 再考虑 locked_scope
52
+ scope_suffix = locked_scopes.join('_')
53
+ schema_name = "#{schema_name}_#{scope_suffix}" unless scope_suffix.empty?
54
+
55
+ schema_name
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'json_schema/schemas'
4
+
5
+ module Meta
6
+ module Errors
7
+ class NoMatchingRoute < StandardError; end
8
+
9
+ class ParameterInvalid < JsonSchema::ValidationErrors
10
+ def initialize(errors)
11
+ super(errors, "参数异常:#{errors}")
12
+ end
13
+ end
14
+
15
+ class RenderingInvalid < JsonSchema::ValidationErrors
16
+ def initialize(errors)
17
+ super(errors, "渲染实体异常:#{errors}")
18
+ end
19
+ end
20
+
21
+ class RenderingError < StandardError; end
22
+
23
+ class NotAuthorized < StandardError; end
24
+
25
+ class ResourceNotFound < StandardError; end
26
+
27
+ class UnsupportedContentType < StandardError; end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Meta
4
+ module JsonSchema
5
+ class ArraySchemaBuilder
6
+ def initialize(options, &block)
7
+ raise 'type 选项必须是 array' if !options[:type].nil? && options[:type] != 'array'
8
+
9
+ options = options.merge(type: 'array')
10
+ @options = options
11
+
12
+ items_options = options.delete(:items) || {}
13
+ if object_property?(items_options, block)
14
+ @items = ObjectSchemaBuilder.new(items_options, &block).to_schema
15
+ else
16
+ @items = BaseSchema.new(items_options)
17
+ end
18
+ end
19
+
20
+ def to_schema
21
+ ArraySchema.new(@items, @options)
22
+ end
23
+
24
+ def object_property?(options, block)
25
+ (options && !options[:properties].nil?) || !block.nil?
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Meta
4
+ module JsonSchema
5
+ class ObjectSchemaBuilder
6
+ def initialize(options = {}, &)
7
+ raise 'type 选项必须是 object' if !options[:type].nil? && options[:type] != 'object'
8
+
9
+ @properties = {}
10
+ @required = []
11
+ @validations = {}
12
+
13
+ options = options.merge(type: 'object')
14
+ properties = options.delete(:properties)
15
+ @options = options
16
+
17
+ properties&.each do |name, property_options|
18
+ property name, property_options
19
+ end
20
+
21
+ instance_exec(&) if block_given?
22
+ end
23
+
24
+ # 设置 schema_name.
25
+ #
26
+ # 一、可以传递一个块,该块会接收 locked_scope 参数,需要返回一个带有 param 和 render 键的 Hash.
27
+ # 二、可以传递一个 Hash,它包含 param 和 render 键。
28
+ # 三、可以传递一个字符串。
29
+ def schema_name(schema_name_resolver)
30
+ if schema_name_resolver.is_a?(Proc)
31
+ @schema_name_resolver = schema_name_resolver
32
+ elsif schema_name_resolver.is_a?(Hash)
33
+ @schema_name_resolver = proc { |stage, locked_scopes| schema_name_resolver[stage] }
34
+ elsif schema_name_resolver.is_a?(String)
35
+ @schema_name_resolver = proc { |stage, locked_scopes| schema_name_resolver }
36
+ elsif schema_name_resolver.nil?
37
+ @schema_name_resolver = proc { nil }
38
+ else
39
+ raise TypeError, "schema_name_resolver 必须是一个 Proc、Hash 或 String,当前是:#{schema_name_resolver.class}"
40
+ end
41
+ end
42
+
43
+ def property(name, options = {}, &block)
44
+ name = name.to_sym
45
+ options = options.dup
46
+
47
+ # 能且仅能 ObjectSchemaBuilder 内能使用 using 选项
48
+ block = options[:using] unless block_given?
49
+ if block.nil? || block.is_a?(Proc)
50
+ @properties[name] = SchemaBuilderTool.build(options, &block)
51
+ elsif block.respond_to?(:to_schema)
52
+ schema = block.to_schema
53
+ if options[:type] == 'array'
54
+ @properties[name] = ArraySchema.new(schema, options)
55
+ else
56
+ @properties[name] = schema.dup(options)
57
+ end
58
+ else
59
+ raise "非法的参数。应传递代码块,或通过 using 选项传递 Proc、ObjectScope 或接受 `to_schema` 方法的对象。当前传递:#{block}"
60
+ end
61
+ end
62
+
63
+ alias expose property
64
+ alias param property
65
+
66
+ # 能且仅能 ObjectSchemaBuilder 内能使用 use 方法
67
+ def use(proc)
68
+ proc = proc.to_proc if proc.respond_to?(:to_proc)
69
+ instance_exec(&proc)
70
+ end
71
+
72
+ def to_schema(locked_options = nil)
73
+ ObjectSchema.new(properties: @properties, object_validations: @validations, options: @options, locked_options: locked_options, schema_name_resolver: @schema_name_resolver)
74
+ end
75
+
76
+ def lock(key, value)
77
+ locked(key => value)
78
+ end
79
+
80
+ def locked(options)
81
+ Locked.new(self, options)
82
+ end
83
+
84
+ private
85
+
86
+ def apply_array_scope?(options, block)
87
+ options[:type] == 'array' && (options[:items] || block)
88
+ end
89
+
90
+ def apply_object_scope?(options, block)
91
+ (options[:type] == 'object' || block) && (options[:properties] || block)
92
+ end
93
+
94
+ def method_missing(method, *args)
95
+ if method =~ /^lock_(\w+)$/
96
+ key = Regexp.last_match(1)
97
+ lock(key.to_sym, *args)
98
+ else
99
+ super
100
+ end
101
+ end
102
+
103
+ class Locked
104
+ attr_reader :builder, :locked_options
105
+
106
+ def initialize(builder, locked_options)
107
+ @builder = builder
108
+ @locked_options = locked_options
109
+ end
110
+
111
+ # 当调用 Entity.locked 方法后,生成 schema 的方法会掉到这里面来。
112
+ # 在生成 schema 时,locked_options 会覆盖;当生成 schema 文档时,由于缺失 schema_name 的
113
+ # 信息,故而 schema_name 相关的影响就消失不见了。
114
+ def to_schema
115
+ builder.to_schema(locked_options)
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Meta
4
+ module JsonSchema
5
+ class SchemaBuilderTool
6
+ class << self
7
+ def build(options = {}, &block)
8
+ if apply_array_schema?(options, block)
9
+ ArraySchemaBuilder.new(options, &block).to_schema
10
+ elsif apply_object_schema?(options, block)
11
+ ObjectSchemaBuilder.new(options, &block).to_schema
12
+ else
13
+ BaseSchema.new(options)
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def apply_array_schema?(options, block)
20
+ options[:type] == 'array' && (options[:items] || block)
21
+ end
22
+
23
+ def apply_object_schema?(options, block)
24
+ (options[:type] == 'object' || options[:type].nil?) && (options[:properties] || block)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Meta
4
+ module JsonSchema
5
+ class ArraySchema < BaseSchema
6
+ attr_reader :items
7
+
8
+ def initialize(items, options = {})
9
+ super(options)
10
+
11
+ @items = items
12
+ end
13
+
14
+ def filter(array_value, options = {})
15
+ array_value = super(array_value, options)
16
+ return nil if array_value.nil?
17
+ raise ValidationError.new('参数应该传递一个数组') unless array_value.respond_to?(:each_with_index)
18
+
19
+ array_value.each_with_index.map do |item, index|
20
+ begin
21
+ @items.filter(item, **options)
22
+ rescue ValidationErrors => e
23
+ raise e.prepend_root("[#{index}]")
24
+ end
25
+ end
26
+ end
27
+
28
+ def to_schema_doc(user_options = {})
29
+ stage_options = user_options[:stage] == :param ? @param_options : @render_options
30
+
31
+ schema = {
32
+ type: 'array',
33
+ items: @items ? @items.to_schema_doc(user_options) : {}
34
+ }
35
+ schema[:description] = stage_options[:description] if stage_options[:description]
36
+ schema
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../support/schema_options'
4
+
5
+ module Meta
6
+ module JsonSchema
7
+ class BaseSchema
8
+ # `options` 包含了转换器、验证器、文档、选项。
9
+ #
10
+ # 由于本对象可继承,基于不同的继承可分别表示基本类型、对象和数组,所以该属
11
+ # 性可用在不同类型的对象上。需要时刻留意的是,无论是用在哪种类型的对象内,
12
+ # `options` 属性都是描述该对象的本身,而不是深层的属性。
13
+ #
14
+ # 较常出现错误的是数组,`options` 是描述数组的,而不是描述数组内部元素的。
15
+ attr_reader :param_options, :render_options
16
+
17
+ # 传递 path 参数主要是为了渲染 Parameter 文档时需要
18
+ def initialize(options = {})
19
+ @param_options, @render_options = SchemaOptions.normalize_to_param_and_render(options)
20
+ end
21
+
22
+ def options(stage, key = nil)
23
+ stage_options = case stage
24
+ when :param
25
+ param_options
26
+ when :render
27
+ render_options
28
+ when nil
29
+ merged_options
30
+ else
31
+ raise "非法的 stage 参数,它只允许取值 :param、:render 或 nil,却收到 #{stage.inspect}"
32
+ end
33
+ if key
34
+ stage_options ? stage_options[key] : nil
35
+ else
36
+ stage_options
37
+ end
38
+ end
39
+
40
+ # 将 params 和 render 的选项合并
41
+ def merged_options
42
+ (param_options || {}).merge(render_options || {})
43
+ end
44
+
45
+ def filter(value, user_options = {})
46
+ stage_options = options(user_options[:stage])
47
+
48
+ value = resolve_value(user_options) if stage_options[:value]
49
+ value = JsonSchema::Presenters.present(stage_options[:presenter], value) if stage_options[:presenter]
50
+ value = stage_options[:default] if value.nil? && stage_options.key?(:default)
51
+ value = stage_options[:convert].call(value) if stage_options[:convert]
52
+
53
+ # 这一步转换值。需要注意的是,对象也可能被转换,因为并没有深层次的结构被声明。
54
+ type = stage_options[:type]
55
+ unless type.nil? || value.nil?
56
+ begin
57
+ value = JsonSchema::TypeConverter.convert_value(value, type)
58
+ rescue JsonSchema::TypeConvertError => e
59
+ raise JsonSchema::ValidationError.new(e.message)
60
+ end
61
+ end
62
+
63
+ validate!(value, stage_options)
64
+
65
+ value
66
+ end
67
+
68
+ def value?(stage)
69
+ options(stage, :value) != nil
70
+ end
71
+
72
+ def resolve_value(user_options)
73
+ value_proc = options(user_options[:stage], :value)
74
+ value_proc_params = (value_proc.lambda? && value_proc.arity == 0) ? [] : [user_options[:object_value]]
75
+
76
+ if user_options[:execution]
77
+ user_options[:execution].instance_exec(*value_proc_params, &value_proc)
78
+ else
79
+ value_proc.call(*value_proc_params)
80
+ end
81
+ end
82
+
83
+ def to_schema_doc(user_options = {})
84
+ stage_options = options(user_options[:stage])
85
+
86
+ return Presenters.to_schema_doc(stage_options[:presenter], stage_options) if stage_options[:presenter]
87
+
88
+ schema = {}
89
+ schema[:type] = stage_options[:type] if stage_options[:type]
90
+ schema[:description] = stage_options[:description] if stage_options[:description]
91
+ schema[:enum] = stage_options[:allowable] if stage_options[:allowable]
92
+
93
+ schema
94
+ end
95
+
96
+ def to_schema
97
+ self
98
+ end
99
+
100
+ private
101
+
102
+ def validate!(value, stage_options)
103
+ stage_options.each do |key, option|
104
+ validator = JsonSchema::Validators[key]
105
+ validator&.call(value, option, stage_options)
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Meta
4
+ module JsonSchema
5
+ class ObjectSchema < BaseSchema
6
+ attr_reader :properties, :object_validations, :locked_options
7
+
8
+ def initialize(properties: {}, object_validations: {}, options: {}, locked_options: {}, schema_name_resolver: proc { nil })
9
+ super(options)
10
+
11
+ @properties = properties || {}
12
+ @object_validations = object_validations || {}
13
+ @locked_options = locked_options || {}
14
+ @schema_name_resolver = schema_name_resolver || proc { nil }
15
+ end
16
+
17
+ # 复制一个新的 ObjectSchema,只有 options 不同
18
+ def dup(options)
19
+ self.class.new(
20
+ properties: properties,
21
+ object_validations: object_validations,
22
+ options: options,
23
+ locked_options: locked_options,
24
+ schema_name_resolver: @schema_name_resolver
25
+ )
26
+ end
27
+
28
+ def filter(object_value, user_options = {})
29
+ # 合并 user_options
30
+ user_options = user_options.merge(locked_options) if locked_options
31
+
32
+ object_value = super(object_value, user_options)
33
+ return nil if object_value.nil?
34
+
35
+ # 第一步,根据 user_options[:scope] 需要过滤一些字段
36
+ # user_options[:scope] 应是一个数组
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
77
+ end
78
+
79
+ # 合并其他的属性,并返回一个新的 ObjectSchema
80
+ def merge(properties)
81
+ ObjectSchema.new(properties: self.properties.merge(properties))
82
+ end
83
+
84
+ # 生成 Swagger 文档的 schema 格式。
85
+ #
86
+ # 选项:
87
+ # - stage: 传递 :param 或 :render
88
+ # - schemas: 用于保存已经生成的 Schema
89
+ # - to_ref: 是否生成 $ref 格式,默认为“是”
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: 标准化选项的工作进行得怎样?
98
+ 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
+
133
+ schema = { type: 'object' }
134
+ schema[:description] = stage_options[:description] if stage_options[:description]
135
+ schema[:properties] = properties unless properties.empty?
136
+ schema[:required] = required_keys unless required_keys.empty?
137
+ schema
138
+ end
139
+
140
+ def locked_scope
141
+ locked_options && locked_options[:scope]
142
+ end
143
+
144
+ def locked_exclude
145
+ locked_options && locked_options[:exclude]
146
+ 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
+ end
160
+ end
161
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'support/errors'
4
+ require_relative 'support/type_converter'
5
+ require_relative 'support/validators'
6
+ require_relative 'support/presenters'
7
+ require_relative 'schemas/base_schema'
8
+ require_relative 'schemas/object_schema'
9
+ require_relative 'schemas/array_schema'
10
+ require_relative 'builders/object_schema_builder'
11
+ require_relative 'builders/array_schema_builder'
12
+ require_relative 'builders/schema_builder_tool'