grape-oas 1.0.2 → 1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +92 -75
- data/README.md +25 -1
- data/lib/grape_oas/api_model/parameter.rb +5 -2
- data/lib/grape_oas/api_model_builders/concerns/type_resolver.rb +5 -2
- data/lib/grape_oas/api_model_builders/request.rb +125 -9
- data/lib/grape_oas/api_model_builders/request_params.rb +6 -5
- data/lib/grape_oas/api_model_builders/request_params_support/param_schema_builder.rb +59 -8
- data/lib/grape_oas/api_model_builders/request_params_support/schema_enhancer.rb +77 -2
- data/lib/grape_oas/api_model_builders/response.rb +63 -6
- data/lib/grape_oas/api_model_builders/response_parsers/http_codes_parser.rb +114 -10
- data/lib/grape_oas/constants.rb +32 -19
- data/lib/grape_oas/exporter/oas2_schema.rb +0 -3
- data/lib/grape_oas/exporter/oas3/parameter.rb +2 -0
- data/lib/grape_oas/exporter/oas3_schema.rb +0 -3
- data/lib/grape_oas/introspectors/dry_introspector.rb +19 -10
- data/lib/grape_oas/introspectors/dry_introspector_support/argument_extractor.rb +57 -6
- data/lib/grape_oas/introspectors/dry_introspector_support/inheritance_handler.rb +17 -5
- data/lib/grape_oas/introspectors/dry_introspector_support/predicate_handler.rb +38 -12
- data/lib/grape_oas/introspectors/dry_introspector_support/rule_index.rb +196 -0
- data/lib/grape_oas/introspectors/dry_introspector_support/type_schema_builder.rb +89 -17
- data/lib/grape_oas/introspectors/dry_introspector_support/type_unwrapper.rb +19 -0
- data/lib/grape_oas/introspectors/entity_introspector.rb +0 -8
- data/lib/grape_oas/introspectors/entity_introspector_support/exposure_processor.rb +6 -3
- data/lib/grape_oas/version.rb +1 -1
- data/lib/grape_oas.rb +1 -1
- metadata +3 -2
|
@@ -59,7 +59,6 @@ module GrapeOAS
|
|
|
59
59
|
when :email? then constraints.format = "email"
|
|
60
60
|
when :date? then constraints.format = "date"
|
|
61
61
|
when :time?, :date_time? then constraints.format = "date-time"
|
|
62
|
-
when :bool?, :boolean? then constraints.type_predicate ||= :boolean
|
|
63
62
|
when :type? then constraints.type_predicate = ArgumentExtractor.extract_literal(args.first)
|
|
64
63
|
when :odd? then constraints.parity = :odd
|
|
65
64
|
when :even? then constraints.parity = :even
|
|
@@ -68,7 +67,31 @@ module GrapeOAS
|
|
|
68
67
|
|
|
69
68
|
def apply_enum_from_list(args)
|
|
70
69
|
vals = ArgumentExtractor.extract_list(args.first)
|
|
71
|
-
|
|
70
|
+
if vals
|
|
71
|
+
constraints.enum = vals
|
|
72
|
+
else
|
|
73
|
+
# For numeric ranges, extract min/max instead of enum
|
|
74
|
+
apply_min_max_from_range(args)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def apply_min_max_from_range(args)
|
|
79
|
+
rng = ArgumentExtractor.extract_range(args.first)
|
|
80
|
+
return unless rng
|
|
81
|
+
# Only apply min/max for numeric ranges; non-numeric ranges that can't
|
|
82
|
+
# be enumerated should be silently ignored rather than producing invalid schema
|
|
83
|
+
return if rng.begin && !rng.begin.is_a?(Numeric)
|
|
84
|
+
return if rng.end && !rng.end.is_a?(Numeric)
|
|
85
|
+
|
|
86
|
+
apply_range_constraints(rng)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def apply_range_constraints(rng)
|
|
90
|
+
return unless rng
|
|
91
|
+
|
|
92
|
+
constraints.minimum = rng.begin if rng.begin
|
|
93
|
+
constraints.maximum = rng.end if rng.end
|
|
94
|
+
constraints.exclusive_maximum = rng.exclude_end? if rng.end
|
|
72
95
|
end
|
|
73
96
|
|
|
74
97
|
def apply_excluded_from_list(args)
|
|
@@ -97,19 +120,22 @@ module GrapeOAS
|
|
|
97
120
|
end
|
|
98
121
|
|
|
99
122
|
def handle_size(name, args)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
123
|
+
rng = ArgumentExtractor.extract_range(args.first)
|
|
124
|
+
|
|
125
|
+
if rng
|
|
126
|
+
constraints.min_size = rng.begin if rng.begin
|
|
127
|
+
constraints.max_size = rng.max if rng.end
|
|
128
|
+
else
|
|
129
|
+
min_val = ArgumentExtractor.extract_numeric(args[0])
|
|
130
|
+
max_val = ArgumentExtractor.extract_numeric(args[1]) if name == :size?
|
|
131
|
+
constraints.min_size = min_val if min_val
|
|
132
|
+
constraints.max_size = max_val if max_val
|
|
133
|
+
end
|
|
104
134
|
end
|
|
105
135
|
|
|
106
136
|
def handle_range(args)
|
|
107
|
-
rng =
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
constraints.minimum = rng.begin if rng.begin
|
|
111
|
-
constraints.maximum = rng.end if rng.end
|
|
112
|
-
constraints.exclusive_maximum = rng.exclude_end?
|
|
137
|
+
rng = ArgumentExtractor.extract_range(args.first)
|
|
138
|
+
apply_range_constraints(rng)
|
|
113
139
|
end
|
|
114
140
|
|
|
115
141
|
def handle_multiple_of(args)
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "ast_walker"
|
|
4
|
+
require_relative "constraint_extractor"
|
|
5
|
+
require_relative "constraint_merger"
|
|
6
|
+
|
|
7
|
+
module GrapeOAS
|
|
8
|
+
module Introspectors
|
|
9
|
+
module DryIntrospectorSupport
|
|
10
|
+
# Builds path-aware constraint and required field indexes from dry-schema AST
|
|
11
|
+
class RuleIndex
|
|
12
|
+
def initialize(contract_schema)
|
|
13
|
+
@walker = AstWalker.new(ConstraintExtractor::ConstraintSet)
|
|
14
|
+
@merger = ConstraintMerger
|
|
15
|
+
@constraints_by_path = {}
|
|
16
|
+
@required_by_object_path = Hash.new { |h, k| h[k] = {} }
|
|
17
|
+
|
|
18
|
+
build_indexes(contract_schema)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.build(contract_schema)
|
|
22
|
+
new(contract_schema).to_a
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def to_a
|
|
26
|
+
[@constraints_by_path, @required_by_object_path.transform_values(&:keys)]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def build_indexes(contract_schema)
|
|
32
|
+
rules = contract_schema.respond_to?(:rules) ? contract_schema.rules : {}
|
|
33
|
+
rules.each_value do |rule|
|
|
34
|
+
ast = rule.respond_to?(:to_ast) ? rule.to_ast : rule
|
|
35
|
+
collect_constraints(ast, [])
|
|
36
|
+
collect_required(ast, [], in_implication_condition: false)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def collect_constraints(ast, path)
|
|
41
|
+
return unless ast.is_a?(Array)
|
|
42
|
+
|
|
43
|
+
case ast[0]
|
|
44
|
+
when :key
|
|
45
|
+
key_name, value_ast = parse_key_node(ast)
|
|
46
|
+
return unless key_name && value_ast.is_a?(Array)
|
|
47
|
+
|
|
48
|
+
new_path = path + [key_name]
|
|
49
|
+
apply_node_constraints(value_ast, new_path)
|
|
50
|
+
collect_constraints(value_ast, new_path)
|
|
51
|
+
|
|
52
|
+
when :each
|
|
53
|
+
child = ast[1]
|
|
54
|
+
return unless child.is_a?(Array)
|
|
55
|
+
|
|
56
|
+
item_path = path + ["[]"]
|
|
57
|
+
|
|
58
|
+
# Index constraints that apply to the item schema itself
|
|
59
|
+
apply_node_constraints(child, item_path)
|
|
60
|
+
|
|
61
|
+
# Recurse so nested keys inside the item get their own paths
|
|
62
|
+
collect_constraints(child, item_path)
|
|
63
|
+
else
|
|
64
|
+
ast.each { |child| collect_constraints(child, path) if child.is_a?(Array) }
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def collect_required(ast, object_path, in_implication_condition:)
|
|
69
|
+
return unless ast.is_a?(Array)
|
|
70
|
+
|
|
71
|
+
case ast[0]
|
|
72
|
+
when :implication
|
|
73
|
+
left, right = ast[1].is_a?(Array) ? ast[1] : [nil, nil]
|
|
74
|
+
collect_required(left, object_path, in_implication_condition: true) if left
|
|
75
|
+
collect_required(right, object_path, in_implication_condition: false) if right
|
|
76
|
+
|
|
77
|
+
when :predicate
|
|
78
|
+
mark_required_if_key_predicate(ast[1], object_path) unless in_implication_condition
|
|
79
|
+
|
|
80
|
+
when :key, :each
|
|
81
|
+
if ast[0] == :key
|
|
82
|
+
key_name, value_ast = parse_key_node(ast)
|
|
83
|
+
if key_name && value_ast.is_a?(Array)
|
|
84
|
+
collect_required(value_ast, object_path + [key_name],
|
|
85
|
+
in_implication_condition: in_implication_condition,)
|
|
86
|
+
end
|
|
87
|
+
elsif ast[1] # :each
|
|
88
|
+
collect_required(ast[1], object_path + ["[]"],
|
|
89
|
+
in_implication_condition: in_implication_condition,)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
else
|
|
93
|
+
ast.each do |child|
|
|
94
|
+
collect_required(child, object_path, in_implication_condition: in_implication_condition) if child.is_a?(Array)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def parse_key_node(ast)
|
|
100
|
+
info = ast[1]
|
|
101
|
+
return [nil, nil] unless info.is_a?(Array) && info.any?
|
|
102
|
+
|
|
103
|
+
key_name = info[0]
|
|
104
|
+
value_ast = info[1] || info[-1]
|
|
105
|
+
[key_name&.to_s, value_ast]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def apply_node_constraints(value_ast, path)
|
|
109
|
+
pruned = prune_nested_validations(value_ast)
|
|
110
|
+
return unless pruned
|
|
111
|
+
|
|
112
|
+
constraints = @walker.walk(pruned)
|
|
113
|
+
constraints.required = nil if constraints.respond_to?(:required=)
|
|
114
|
+
|
|
115
|
+
path_key = path.join("/")
|
|
116
|
+
if @constraints_by_path.key?(path_key)
|
|
117
|
+
@merger.merge(@constraints_by_path[path_key], constraints)
|
|
118
|
+
else
|
|
119
|
+
@constraints_by_path[path_key] = constraints
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def prune_nested_validations(ast)
|
|
124
|
+
return ast unless ast.is_a?(Array)
|
|
125
|
+
|
|
126
|
+
tag = ast[0]
|
|
127
|
+
return ast unless tag.is_a?(Symbol)
|
|
128
|
+
|
|
129
|
+
case tag
|
|
130
|
+
when :each, :key
|
|
131
|
+
nil
|
|
132
|
+
|
|
133
|
+
when :set
|
|
134
|
+
children, wrapped = extract_children(ast)
|
|
135
|
+
pruned = children.filter_map { |c| c.is_a?(Array) ? prune_nested_validations(c) : c }
|
|
136
|
+
return nil if pruned.empty?
|
|
137
|
+
|
|
138
|
+
# rewrite set -> and, preserve wrapper style
|
|
139
|
+
wrapped ? [:and, pruned] : [:and, *pruned]
|
|
140
|
+
|
|
141
|
+
when :and, :or, :rule
|
|
142
|
+
children, wrapped = extract_children(ast)
|
|
143
|
+
pruned = children.filter_map { |c| c.is_a?(Array) ? prune_nested_validations(c) : c }
|
|
144
|
+
return nil if pruned.empty?
|
|
145
|
+
|
|
146
|
+
wrapped ? [tag, pruned] : [tag, *pruned]
|
|
147
|
+
|
|
148
|
+
when :implication
|
|
149
|
+
pair = ast[1]
|
|
150
|
+
return ast unless pair.is_a?(Array) && pair.size >= 2
|
|
151
|
+
|
|
152
|
+
left = pair[0].is_a?(Array) ? prune_nested_validations(pair[0]) : pair[0]
|
|
153
|
+
right = pair[1].is_a?(Array) ? prune_nested_validations(pair[1]) : pair[1]
|
|
154
|
+
[:implication, [left, right]]
|
|
155
|
+
|
|
156
|
+
when :not
|
|
157
|
+
child = ast[1]
|
|
158
|
+
child = prune_nested_validations(child) if child.is_a?(Array)
|
|
159
|
+
[:not, child]
|
|
160
|
+
|
|
161
|
+
else
|
|
162
|
+
ast
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def extract_children(ast)
|
|
167
|
+
# handles both shapes:
|
|
168
|
+
# [:and, [node1, node2]]
|
|
169
|
+
# [:and, node1, node2]
|
|
170
|
+
payload = ast[1]
|
|
171
|
+
|
|
172
|
+
if payload.is_a?(Array) && !payload.empty? && payload.all? { |x| x.is_a?(Array) && x[0].is_a?(Symbol) }
|
|
173
|
+
[payload, true] # wrapped list
|
|
174
|
+
else
|
|
175
|
+
[ast[1..], false] # splatted
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def mark_required_if_key_predicate(pred, object_path)
|
|
180
|
+
return unless pred.is_a?(Array) && pred[0] == :key?
|
|
181
|
+
|
|
182
|
+
name = extract_key_name(pred)
|
|
183
|
+
@required_by_object_path[object_path.join("/")][name] = true if name
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def extract_key_name(pred_node)
|
|
187
|
+
args = pred_node[1]
|
|
188
|
+
return nil unless args.is_a?(Array)
|
|
189
|
+
|
|
190
|
+
name_pair = args.find { |x| x.is_a?(Array) && x[0] == :name }
|
|
191
|
+
name_pair&.dig(1)&.to_s
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
@@ -11,7 +11,39 @@ module GrapeOAS
|
|
|
11
11
|
ConstraintSet = ConstraintExtractor::ConstraintSet
|
|
12
12
|
|
|
13
13
|
def initialize
|
|
14
|
-
|
|
14
|
+
@path_stack = []
|
|
15
|
+
@constraints_by_path = nil
|
|
16
|
+
@required_by_object_path = nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def configure_path_aware_mode(constraints_by_path, required_by_object_path)
|
|
20
|
+
@path_stack = []
|
|
21
|
+
@constraints_by_path = constraints_by_path
|
|
22
|
+
@required_by_object_path = required_by_object_path
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def with_path(part)
|
|
26
|
+
@path_stack << part
|
|
27
|
+
yield
|
|
28
|
+
ensure
|
|
29
|
+
@path_stack.pop
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def current_object_path
|
|
33
|
+
@path_stack.join("/")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def constraints_for_current_path
|
|
37
|
+
return nil unless @constraints_by_path
|
|
38
|
+
|
|
39
|
+
@constraints_by_path[current_object_path]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def required_keys_for_current_object
|
|
43
|
+
return nil unless @required_by_object_path
|
|
44
|
+
|
|
45
|
+
# In path-aware mode we rely entirely on rule-index requiredness
|
|
46
|
+
@required_by_object_path[current_object_path] || []
|
|
15
47
|
end
|
|
16
48
|
|
|
17
49
|
# Builds a schema for a Dry type.
|
|
@@ -20,7 +52,7 @@ module GrapeOAS
|
|
|
20
52
|
# @param constraints [ConstraintSet, nil] extracted constraints
|
|
21
53
|
# @return [ApiModel::Schema] the built schema
|
|
22
54
|
def build_schema_for_type(dry_type, constraints = nil)
|
|
23
|
-
constraints ||= ConstraintSet.new(unhandled_predicates: [])
|
|
55
|
+
constraints ||= constraints_for_current_path || ConstraintSet.new(unhandled_predicates: [])
|
|
24
56
|
meta = dry_type.respond_to?(:meta) ? dry_type.meta : {}
|
|
25
57
|
|
|
26
58
|
# Check for Sum type first (TypeA | TypeB) -> anyOf
|
|
@@ -29,6 +61,10 @@ module GrapeOAS
|
|
|
29
61
|
# Check for Hash schema type (nested schemas like .hash(SomeSchema))
|
|
30
62
|
return build_hash_schema(dry_type) if hash_schema_type?(dry_type)
|
|
31
63
|
|
|
64
|
+
# Check for object schema (unwrapped hash with keys)
|
|
65
|
+
unwrapped = TypeUnwrapper.unwrap(dry_type)
|
|
66
|
+
return build_object_schema(unwrapped) if unwrapped.respond_to?(:keys)
|
|
67
|
+
|
|
32
68
|
primitive, member = TypeUnwrapper.derive_primitive_and_member(dry_type)
|
|
33
69
|
enum_vals = extract_enum_from_type(dry_type)
|
|
34
70
|
|
|
@@ -59,6 +95,36 @@ module GrapeOAS
|
|
|
59
95
|
|
|
60
96
|
private
|
|
61
97
|
|
|
98
|
+
def build_object_schema(unwrapped_schema_type)
|
|
99
|
+
schema = ApiModel::Schema.new(type: Constants::SchemaTypes::OBJECT)
|
|
100
|
+
required_keys = required_keys_for_current_object
|
|
101
|
+
|
|
102
|
+
# Dry::Types::Schema does not have each_key, so we disable the cop here
|
|
103
|
+
unwrapped_schema_type.keys.each do |key| # rubocop:disable Style/HashEachMethods
|
|
104
|
+
key_name = key.respond_to?(:name) ? key.name.to_s : key.to_s
|
|
105
|
+
key_type = key.respond_to?(:type) ? key.type : nil
|
|
106
|
+
|
|
107
|
+
prop_schema = nil
|
|
108
|
+
with_path(key_name) do
|
|
109
|
+
prop_schema = if key_type
|
|
110
|
+
build_schema_for_type(key_type, constraints_for_current_path)
|
|
111
|
+
else
|
|
112
|
+
default_string_schema
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
is_required = if required_keys
|
|
117
|
+
required_keys.include?(key_name)
|
|
118
|
+
else
|
|
119
|
+
key.respond_to?(:required?) ? key.required? : false
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
schema.add_property(key_name, prop_schema, required: is_required)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
schema
|
|
126
|
+
end
|
|
127
|
+
|
|
62
128
|
def build_any_of_schema(sum_type)
|
|
63
129
|
types = TypeUnwrapper.extract_sum_types(sum_type)
|
|
64
130
|
|
|
@@ -86,29 +152,35 @@ module GrapeOAS
|
|
|
86
152
|
end
|
|
87
153
|
|
|
88
154
|
def build_hash_schema(dry_type)
|
|
89
|
-
schema = ApiModel::Schema.new(type: Constants::SchemaTypes::OBJECT)
|
|
90
155
|
unwrapped = TypeUnwrapper.unwrap(dry_type)
|
|
91
156
|
|
|
92
|
-
|
|
157
|
+
# Delegate to the same path-aware logic as regular object schemas.
|
|
158
|
+
# This ensures nested rule constraints (e.g. max_size?, gteq?, format?) are applied
|
|
159
|
+
# to properties inside `.hash do ... end` blocks.
|
|
160
|
+
return ApiModel::Schema.new(type: Constants::SchemaTypes::OBJECT) unless unwrapped.respond_to?(:keys)
|
|
93
161
|
|
|
94
|
-
|
|
95
|
-
schema_keys = unwrapped.keys
|
|
96
|
-
schema_keys.each do |key|
|
|
97
|
-
key_name = key.respond_to?(:name) ? key.name.to_s : key.to_s
|
|
98
|
-
key_type = key.respond_to?(:type) ? key.type : nil
|
|
99
|
-
|
|
100
|
-
prop_schema = key_type ? build_schema_for_type(key_type) : default_string_schema
|
|
101
|
-
req = key.respond_to?(:required?) ? key.required? : true
|
|
102
|
-
schema.add_property(key_name, prop_schema, required: req)
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
schema
|
|
162
|
+
build_object_schema(unwrapped)
|
|
106
163
|
end
|
|
107
164
|
|
|
108
165
|
def build_base_schema(primitive, member)
|
|
109
166
|
if primitive == Array
|
|
110
|
-
items_schema =
|
|
167
|
+
items_schema = nil
|
|
168
|
+
|
|
169
|
+
with_path("[]") do
|
|
170
|
+
if member
|
|
171
|
+
unwrapped = TypeUnwrapper.unwrap(member)
|
|
172
|
+
items_schema = if unwrapped.respond_to?(:keys)
|
|
173
|
+
build_object_schema(unwrapped)
|
|
174
|
+
else
|
|
175
|
+
build_schema_for_type(member, constraints_for_current_path)
|
|
176
|
+
end
|
|
177
|
+
else
|
|
178
|
+
items_schema = default_string_schema
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
111
182
|
ApiModel::Schema.new(type: Constants::SchemaTypes::ARRAY, items: items_schema)
|
|
183
|
+
|
|
112
184
|
else
|
|
113
185
|
build_schema_for_primitive(primitive)
|
|
114
186
|
end
|
|
@@ -24,6 +24,9 @@ module GrapeOAS
|
|
|
24
24
|
# @param dry_type [Dry::Types::Type] the type to analyze
|
|
25
25
|
# @return [Array(Class, Object)] tuple of [primitive_class, member_type_or_nil]
|
|
26
26
|
def derive_primitive_and_member(dry_type)
|
|
27
|
+
# Handle boolean Sum types (TrueClass | FalseClass)
|
|
28
|
+
return [TrueClass, nil] if boolean_sum_type?(dry_type)
|
|
29
|
+
|
|
27
30
|
core = unwrap(dry_type)
|
|
28
31
|
|
|
29
32
|
return [Array, core.type.member] if array_member_type?(core)
|
|
@@ -137,6 +140,22 @@ module GrapeOAS
|
|
|
137
140
|
core.primitive == Array
|
|
138
141
|
end
|
|
139
142
|
private_class_method :array_with_member?
|
|
143
|
+
|
|
144
|
+
def boolean_sum_type?(dry_type)
|
|
145
|
+
return false unless dry_type.respond_to?(:left) && dry_type.respond_to?(:right)
|
|
146
|
+
|
|
147
|
+
boolean_type?(dry_type.left) && boolean_type?(dry_type.right)
|
|
148
|
+
end
|
|
149
|
+
private_class_method :boolean_sum_type?
|
|
150
|
+
|
|
151
|
+
def boolean_type?(dry_type)
|
|
152
|
+
return false unless dry_type
|
|
153
|
+
|
|
154
|
+
return [TrueClass, FalseClass].include?(dry_type.primitive) if dry_type.respond_to?(:primitive)
|
|
155
|
+
|
|
156
|
+
false
|
|
157
|
+
end
|
|
158
|
+
private_class_method :boolean_type?
|
|
140
159
|
end
|
|
141
160
|
end
|
|
142
161
|
end
|
|
@@ -1,13 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "base"
|
|
4
|
-
require_relative "../api_model_builders/concerns/type_resolver"
|
|
5
|
-
require_relative "entity_introspector_support/cycle_tracker"
|
|
6
|
-
require_relative "entity_introspector_support/discriminator_handler"
|
|
7
|
-
require_relative "entity_introspector_support/inheritance_builder"
|
|
8
|
-
require_relative "entity_introspector_support/property_extractor"
|
|
9
|
-
require_relative "entity_introspector_support/exposure_processor"
|
|
10
|
-
|
|
11
3
|
module GrapeOAS
|
|
12
4
|
module Introspectors
|
|
13
5
|
# Introspector for Grape::Entity classes.
|
|
@@ -59,7 +59,7 @@ module GrapeOAS
|
|
|
59
59
|
# @return [ApiModel::Schema] the built schema
|
|
60
60
|
def schema_for_exposure(exposure, doc)
|
|
61
61
|
opts = exposure.instance_variable_get(:@options) || {}
|
|
62
|
-
type =
|
|
62
|
+
type = opts[:using] || doc[:type] || doc["type"]
|
|
63
63
|
|
|
64
64
|
schema = build_exposure_base_schema(type)
|
|
65
65
|
apply_exposure_properties(schema, doc)
|
|
@@ -243,7 +243,7 @@ module GrapeOAS
|
|
|
243
243
|
|
|
244
244
|
def resolve_entity_from_opts(exposure, doc)
|
|
245
245
|
opts = exposure.instance_variable_get(:@options) || {}
|
|
246
|
-
type =
|
|
246
|
+
type = opts[:using] || doc[:type] || doc["type"]
|
|
247
247
|
return type if defined?(Grape::Entity) && type.is_a?(Class) && type <= Grape::Entity
|
|
248
248
|
|
|
249
249
|
nil
|
|
@@ -253,7 +253,10 @@ module GrapeOAS
|
|
|
253
253
|
schema_type = Constants.primitive_type(type)
|
|
254
254
|
return nil unless schema_type
|
|
255
255
|
|
|
256
|
-
ApiModel::Schema.new(
|
|
256
|
+
ApiModel::Schema.new(
|
|
257
|
+
type: schema_type,
|
|
258
|
+
format: Constants.format_for_type(type),
|
|
259
|
+
)
|
|
257
260
|
end
|
|
258
261
|
end
|
|
259
262
|
end
|
data/lib/grape_oas/version.rb
CHANGED
data/lib/grape_oas.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: grape-oas
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrei Subbota
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-01-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: grape
|
|
@@ -114,6 +114,7 @@ files:
|
|
|
114
114
|
- lib/grape_oas/introspectors/dry_introspector_support/contract_resolver.rb
|
|
115
115
|
- lib/grape_oas/introspectors/dry_introspector_support/inheritance_handler.rb
|
|
116
116
|
- lib/grape_oas/introspectors/dry_introspector_support/predicate_handler.rb
|
|
117
|
+
- lib/grape_oas/introspectors/dry_introspector_support/rule_index.rb
|
|
117
118
|
- lib/grape_oas/introspectors/dry_introspector_support/type_schema_builder.rb
|
|
118
119
|
- lib/grape_oas/introspectors/dry_introspector_support/type_unwrapper.rb
|
|
119
120
|
- lib/grape_oas/introspectors/entity_introspector.rb
|