grape-oas 1.2.0 → 1.4.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 +42 -0
- data/README.md +6 -0
- data/lib/grape_oas/api_model/api.rb +4 -0
- data/lib/grape_oas/api_model/schema.rb +18 -3
- data/lib/grape_oas/api_model_builders/concerns/content_type_resolver.rb +21 -0
- data/lib/grape_oas/api_model_builders/concerns/type_resolver.rb +3 -6
- data/lib/grape_oas/api_model_builders/request.rb +21 -12
- data/lib/grape_oas/api_model_builders/request_params_support/param_location_resolver.rb +7 -2
- data/lib/grape_oas/api_model_builders/request_params_support/param_schema_builder.rb +15 -1
- data/lib/grape_oas/api_model_builders/request_params_support/schema_enhancer.rb +34 -50
- data/lib/grape_oas/constants.rb +13 -0
- data/lib/grape_oas/doc_key_normalizer.rb +14 -0
- data/lib/grape_oas/exporter/oas2/parameter.rb +1 -0
- data/lib/grape_oas/exporter/oas2/schema.rb +53 -19
- data/lib/grape_oas/exporter/oas3/parameter.rb +5 -2
- data/lib/grape_oas/exporter/oas3/schema.rb +81 -46
- data/lib/grape_oas/introspectors/dry_introspector_support/argument_extractor.rb +2 -31
- data/lib/grape_oas/introspectors/dry_introspector_support/constraint_extractor.rb +10 -19
- data/lib/grape_oas/introspectors/dry_introspector_support/predicate_handler.rb +2 -10
- data/lib/grape_oas/introspectors/entity_introspector.rb +5 -1
- data/lib/grape_oas/introspectors/entity_introspector_support/discriminator_handler.rb +2 -25
- data/lib/grape_oas/introspectors/entity_introspector_support/exposure_processor.rb +139 -136
- data/lib/grape_oas/introspectors/entity_introspector_support/inheritance_builder.rb +6 -30
- data/lib/grape_oas/introspectors/entity_introspector_support/nesting_merger.rb +104 -0
- data/lib/grape_oas/introspectors/entity_introspector_support/property_extractor.rb +2 -1
- data/lib/grape_oas/introspectors/entity_introspector_support/type_schema_resolver.rb +139 -0
- data/lib/grape_oas/introspectors/entity_introspector_support.rb +57 -0
- data/lib/grape_oas/range_utils.rb +87 -0
- data/lib/grape_oas/schema_constraints.rb +36 -0
- data/lib/grape_oas/type_resolvers/array_resolver.rb +3 -5
- data/lib/grape_oas/values_normalizer.rb +47 -0
- data/lib/grape_oas/version.rb +1 -1
- data/lib/grape_oas.rb +27 -0
- metadata +9 -2
|
@@ -32,9 +32,26 @@ module GrapeOAS
|
|
|
32
32
|
apply_nullable(schema_hash)
|
|
33
33
|
props = build_properties(@schema.properties)
|
|
34
34
|
schema_hash["properties"] = props if props
|
|
35
|
-
|
|
35
|
+
if @schema.items
|
|
36
|
+
schema_hash["items"] = build_schema_or_ref(@schema.items, include_metadata: false)
|
|
37
|
+
if !schema_hash["description"] && @schema.items.respond_to?(:description) && @schema.items.description
|
|
38
|
+
schema_hash["description"] = @schema.items.description.to_s
|
|
39
|
+
end
|
|
40
|
+
if @schema.items.respond_to?(:canonical_name) && @schema.items.canonical_name &&
|
|
41
|
+
@schema.items.respond_to?(:nullable) && @schema.items.nullable
|
|
42
|
+
case @nullable_strategy
|
|
43
|
+
when Constants::NullableStrategy::KEYWORD
|
|
44
|
+
schema_hash["nullable"] = true
|
|
45
|
+
when Constants::NullableStrategy::EXTENSION
|
|
46
|
+
schema_hash["x-nullable"] = true
|
|
47
|
+
when Constants::NullableStrategy::TYPE_ARRAY
|
|
48
|
+
schema_hash["type"] = (Array(schema_hash["type"]) | ["null"])
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
36
52
|
schema_hash["required"] = @schema.required if @schema.required && !@schema.required.empty?
|
|
37
53
|
schema_hash["enum"] = normalize_enum(@schema.enum, schema_hash["type"]) if @schema.enum
|
|
54
|
+
schema_hash["default"] = @schema.default unless @schema.default.nil?
|
|
38
55
|
schema_hash
|
|
39
56
|
end
|
|
40
57
|
|
|
@@ -56,49 +73,54 @@ module GrapeOAS
|
|
|
56
73
|
schema_hash["discriminator"] = build_discriminator if @schema.discriminator
|
|
57
74
|
end
|
|
58
75
|
|
|
59
|
-
def apply_all_constraints(schema_hash)
|
|
60
|
-
apply_numeric_constraints(schema_hash)
|
|
61
|
-
apply_string_constraints(schema_hash)
|
|
62
|
-
apply_array_constraints(schema_hash)
|
|
76
|
+
def apply_all_constraints(schema_hash, schema = @schema)
|
|
77
|
+
apply_numeric_constraints(schema_hash, schema)
|
|
78
|
+
apply_string_constraints(schema_hash, schema)
|
|
79
|
+
apply_array_constraints(schema_hash, schema)
|
|
63
80
|
end
|
|
64
81
|
|
|
65
82
|
private
|
|
66
83
|
|
|
67
84
|
# Build allOf schema for inheritance
|
|
68
85
|
def build_all_of_schema
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
result = { "allOf" => all_of_items }
|
|
74
|
-
result["description"] = @schema.description.to_s if @schema.description
|
|
86
|
+
items = @schema.all_of.map { |item| build_schema_or_ref(item) }
|
|
87
|
+
result = { "allOf" => items }
|
|
88
|
+
apply_composition_attributes(result)
|
|
89
|
+
apply_nullable(result)
|
|
75
90
|
result
|
|
76
91
|
end
|
|
77
92
|
|
|
78
93
|
# Build oneOf schema for polymorphism
|
|
79
94
|
def build_one_of_schema
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
result = { "oneOf" => one_of_items }
|
|
85
|
-
result["description"] = @schema.description.to_s if @schema.description
|
|
95
|
+
items = @schema.one_of.map { |item| build_schema_or_ref(item) }
|
|
96
|
+
result = { "oneOf" => items }
|
|
97
|
+
apply_composition_attributes(result)
|
|
86
98
|
result["discriminator"] = build_discriminator if @schema.discriminator
|
|
99
|
+
apply_nullable(result)
|
|
87
100
|
result
|
|
88
101
|
end
|
|
89
102
|
|
|
90
103
|
# Build anyOf schema for polymorphism
|
|
91
104
|
def build_any_of_schema
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
result = { "anyOf" => any_of_items }
|
|
97
|
-
result["description"] = @schema.description.to_s if @schema.description
|
|
105
|
+
items = @schema.any_of.map { |item| build_schema_or_ref(item) }
|
|
106
|
+
result = { "anyOf" => items }
|
|
107
|
+
apply_composition_attributes(result)
|
|
98
108
|
result["discriminator"] = build_discriminator if @schema.discriminator
|
|
109
|
+
apply_nullable(result)
|
|
99
110
|
result
|
|
100
111
|
end
|
|
101
112
|
|
|
113
|
+
def apply_composition_attributes(result)
|
|
114
|
+
result["type"] = nullable_type if @schema.type
|
|
115
|
+
result["format"] = @schema.format if @schema.format
|
|
116
|
+
result["description"] = @schema.description.to_s if @schema.description
|
|
117
|
+
result["default"] = @schema.default unless @schema.default.nil?
|
|
118
|
+
result["enum"] = normalize_enum(@schema.enum, result["type"]) if @schema.enum
|
|
119
|
+
sanitize_enum_against_type(result)
|
|
120
|
+
apply_all_constraints(result)
|
|
121
|
+
result.merge!(@schema.extensions) if @schema.extensions
|
|
122
|
+
end
|
|
123
|
+
|
|
102
124
|
# Build OAS3 discriminator object
|
|
103
125
|
def build_discriminator
|
|
104
126
|
return nil unless @schema.discriminator
|
|
@@ -146,13 +168,20 @@ module GrapeOAS
|
|
|
146
168
|
end
|
|
147
169
|
end
|
|
148
170
|
|
|
149
|
-
def build_schema_or_ref(schema)
|
|
171
|
+
def build_schema_or_ref(schema, include_metadata: true)
|
|
150
172
|
if schema.respond_to?(:canonical_name) && schema.canonical_name
|
|
151
173
|
@ref_tracker << schema.canonical_name if @ref_tracker
|
|
152
174
|
ref_name = schema.canonical_name.gsub("::", "_")
|
|
153
175
|
ref_hash = { "$ref" => "#/components/schemas/#{ref_name}" }
|
|
176
|
+
return ref_hash unless include_metadata
|
|
177
|
+
|
|
154
178
|
result = {}
|
|
155
179
|
result["description"] = schema.description.to_s if schema.description
|
|
180
|
+
result["default"] = schema.default unless schema.default.nil?
|
|
181
|
+
result["enum"] = normalize_enum(schema.enum, schema.type) if schema.enum
|
|
182
|
+
sanitize_enum_against_type(result, type: schema.type)
|
|
183
|
+
apply_all_constraints(result, schema)
|
|
184
|
+
result.merge!(schema.extensions) if schema.extensions
|
|
156
185
|
apply_nullable_to_ref(result, schema)
|
|
157
186
|
if result.empty?
|
|
158
187
|
ref_hash
|
|
@@ -161,10 +190,16 @@ module GrapeOAS
|
|
|
161
190
|
result
|
|
162
191
|
end
|
|
163
192
|
else
|
|
164
|
-
Schema.new(schema, @ref_tracker, nullable_strategy: @nullable_strategy).build
|
|
193
|
+
built = Schema.new(schema, @ref_tracker, nullable_strategy: @nullable_strategy).build
|
|
194
|
+
strip_items_metadata(built) unless include_metadata
|
|
195
|
+
built
|
|
165
196
|
end
|
|
166
197
|
end
|
|
167
198
|
|
|
199
|
+
def strip_items_metadata(hash)
|
|
200
|
+
hash.delete("description")
|
|
201
|
+
end
|
|
202
|
+
|
|
168
203
|
def apply_nullable_to_ref(result, schema)
|
|
169
204
|
return unless schema.respond_to?(:nullable) && schema.nullable
|
|
170
205
|
|
|
@@ -197,40 +232,40 @@ module GrapeOAS
|
|
|
197
232
|
result
|
|
198
233
|
end
|
|
199
234
|
|
|
200
|
-
def apply_numeric_constraints(hash)
|
|
201
|
-
hash["minimum"] =
|
|
202
|
-
hash["maximum"] =
|
|
235
|
+
def apply_numeric_constraints(hash, schema = @schema)
|
|
236
|
+
hash["minimum"] = schema.minimum unless schema.minimum.nil?
|
|
237
|
+
hash["maximum"] = schema.maximum unless schema.maximum.nil?
|
|
203
238
|
|
|
204
239
|
if @nullable_strategy == Constants::NullableStrategy::TYPE_ARRAY
|
|
205
|
-
if
|
|
206
|
-
hash["exclusiveMinimum"] =
|
|
240
|
+
if schema.exclusive_minimum && !schema.minimum.nil?
|
|
241
|
+
hash["exclusiveMinimum"] = schema.minimum
|
|
207
242
|
hash.delete("minimum")
|
|
208
243
|
end
|
|
209
|
-
if
|
|
210
|
-
hash["exclusiveMaximum"] =
|
|
244
|
+
if schema.exclusive_maximum && !schema.maximum.nil?
|
|
245
|
+
hash["exclusiveMaximum"] = schema.maximum
|
|
211
246
|
hash.delete("maximum")
|
|
212
247
|
end
|
|
213
248
|
else
|
|
214
|
-
hash["exclusiveMinimum"] =
|
|
215
|
-
hash["exclusiveMaximum"] =
|
|
249
|
+
hash["exclusiveMinimum"] = schema.exclusive_minimum if schema.exclusive_minimum
|
|
250
|
+
hash["exclusiveMaximum"] = schema.exclusive_maximum if schema.exclusive_maximum
|
|
216
251
|
end
|
|
217
252
|
end
|
|
218
253
|
|
|
219
|
-
def apply_string_constraints(hash)
|
|
220
|
-
hash["minLength"] =
|
|
221
|
-
hash["maxLength"] =
|
|
222
|
-
hash["pattern"] =
|
|
254
|
+
def apply_string_constraints(hash, schema = @schema)
|
|
255
|
+
hash["minLength"] = schema.min_length unless schema.min_length.nil?
|
|
256
|
+
hash["maxLength"] = schema.max_length unless schema.max_length.nil?
|
|
257
|
+
hash["pattern"] = schema.pattern if schema.pattern
|
|
223
258
|
end
|
|
224
259
|
|
|
225
|
-
def apply_array_constraints(hash)
|
|
226
|
-
hash["minItems"] =
|
|
227
|
-
hash["maxItems"] =
|
|
260
|
+
def apply_array_constraints(hash, schema = @schema)
|
|
261
|
+
hash["minItems"] = schema.min_items unless schema.min_items.nil?
|
|
262
|
+
hash["maxItems"] = schema.max_items unless schema.max_items.nil?
|
|
228
263
|
end
|
|
229
264
|
|
|
230
265
|
# Ensure enum values match the declared type; drop enum if incompatible to avoid invalid specs
|
|
231
|
-
def sanitize_enum_against_type(hash)
|
|
266
|
+
def sanitize_enum_against_type(hash, type: nil)
|
|
232
267
|
enum_vals = hash["enum"]
|
|
233
|
-
type_val = hash["type"]
|
|
268
|
+
type_val = type || hash["type"]
|
|
234
269
|
return unless enum_vals && type_val
|
|
235
270
|
|
|
236
271
|
base_type = if type_val.is_a?(Array)
|
|
@@ -244,13 +279,13 @@ module GrapeOAS
|
|
|
244
279
|
when Constants::SchemaTypes::ARRAY, Constants::SchemaTypes::OBJECT, nil
|
|
245
280
|
hash.delete("enum")
|
|
246
281
|
when Constants::SchemaTypes::INTEGER
|
|
247
|
-
hash.delete("enum") unless enum_vals.all?
|
|
282
|
+
hash.delete("enum") unless enum_vals.all?(Integer)
|
|
248
283
|
when Constants::SchemaTypes::NUMBER
|
|
249
|
-
hash.delete("enum") unless enum_vals.all?
|
|
284
|
+
hash.delete("enum") unless enum_vals.all?(Numeric)
|
|
250
285
|
when Constants::SchemaTypes::BOOLEAN
|
|
251
286
|
hash.delete("enum") unless enum_vals.all? { |v| [true, false].include?(v) }
|
|
252
287
|
else # string and fallback
|
|
253
|
-
hash.delete("enum") unless enum_vals.all?
|
|
288
|
+
hash.delete("enum") unless enum_vals.all?(String)
|
|
254
289
|
end
|
|
255
290
|
end
|
|
256
291
|
|
|
@@ -13,8 +13,6 @@ module GrapeOAS
|
|
|
13
13
|
LITERAL_TAGS = %i[value val literal class left right].freeze
|
|
14
14
|
# AST node tags for regex patterns
|
|
15
15
|
PATTERN_TAGS = %i[regexp regex].freeze
|
|
16
|
-
# Maximum size for converting ranges to enum arrays
|
|
17
|
-
MAX_ENUM_RANGE_SIZE = 100
|
|
18
16
|
|
|
19
17
|
def extract_numeric(arg)
|
|
20
18
|
return arg if arg.is_a?(Numeric)
|
|
@@ -36,43 +34,16 @@ module GrapeOAS
|
|
|
36
34
|
def extract_list(arg)
|
|
37
35
|
if list_node?(arg)
|
|
38
36
|
inner = arg[1]
|
|
39
|
-
|
|
40
|
-
# Numeric ranges should use min/max constraints instead
|
|
41
|
-
return range_to_enum_array(inner) if inner.is_a?(Range)
|
|
37
|
+
return RangeUtils.expand_range_to_enum(inner) if inner.is_a?(Range)
|
|
42
38
|
|
|
43
39
|
return inner
|
|
44
40
|
end
|
|
45
41
|
return arg if arg.is_a?(Array)
|
|
46
|
-
return
|
|
42
|
+
return RangeUtils.expand_range_to_enum(arg) if arg.is_a?(Range)
|
|
47
43
|
|
|
48
44
|
nil
|
|
49
45
|
end
|
|
50
46
|
|
|
51
|
-
# Converts a non-numeric bounded Range to an array for enum values.
|
|
52
|
-
# Returns nil for numeric ranges (should use min/max instead).
|
|
53
|
-
# Returns nil for unbounded (endless/beginless) or excessively large ranges.
|
|
54
|
-
def range_to_enum_array(range)
|
|
55
|
-
# Reject unbounded ranges (endless/beginless)
|
|
56
|
-
return nil if range.begin.nil? || range.end.nil?
|
|
57
|
-
|
|
58
|
-
# Numeric ranges should use min/max constraints, not enum
|
|
59
|
-
return nil if range.begin.is_a?(Numeric) || range.end.is_a?(Numeric)
|
|
60
|
-
|
|
61
|
-
# Use bounded iteration to avoid memory exhaustion on large ranges.
|
|
62
|
-
# Take one more than max to detect oversized ranges without full enumeration.
|
|
63
|
-
begin
|
|
64
|
-
array = range.take(MAX_ENUM_RANGE_SIZE + 1)
|
|
65
|
-
rescue TypeError
|
|
66
|
-
# Range can't be iterated (e.g., non-discrete types)
|
|
67
|
-
return nil
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
# Reject ranges exceeding the size limit
|
|
71
|
-
return nil if array.size > MAX_ENUM_RANGE_SIZE
|
|
72
|
-
|
|
73
|
-
array
|
|
74
|
-
end
|
|
75
|
-
|
|
76
47
|
def extract_literal(arg)
|
|
77
48
|
return arg unless arg.is_a?(Array)
|
|
78
49
|
return arg[1] if arg.length == 2 && LITERAL_TAGS.include?(arg.first)
|
|
@@ -7,25 +7,16 @@ module GrapeOAS
|
|
|
7
7
|
# Delegates AST walking to AstWalker and merging to ConstraintMerger.
|
|
8
8
|
class ConstraintExtractor
|
|
9
9
|
# Value object holding all possible constraints extracted from a Dry contract.
|
|
10
|
-
ConstraintSet
|
|
11
|
-
:enum,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
:excluded_values,
|
|
21
|
-
:unhandled_predicates,
|
|
22
|
-
:required,
|
|
23
|
-
:type_predicate,
|
|
24
|
-
:parity,
|
|
25
|
-
:format,
|
|
26
|
-
:extensions,
|
|
27
|
-
keyword_init: true,
|
|
28
|
-
)
|
|
10
|
+
class ConstraintSet
|
|
11
|
+
attr_accessor :enum, :nullable, :min_size, :max_size,
|
|
12
|
+
:minimum, :maximum, :exclusive_minimum, :exclusive_maximum,
|
|
13
|
+
:pattern, :excluded_values, :unhandled_predicates,
|
|
14
|
+
:required, :type_predicate, :parity, :format, :extensions
|
|
15
|
+
|
|
16
|
+
def initialize(**attrs)
|
|
17
|
+
attrs.each { |k, v| public_send(:"#{k}=", v) }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
29
20
|
|
|
30
21
|
def self.extract(contract)
|
|
31
22
|
new(contract).extract
|
|
@@ -83,15 +83,7 @@ module GrapeOAS
|
|
|
83
83
|
return if rng.begin && !rng.begin.is_a?(Numeric)
|
|
84
84
|
return if rng.end && !rng.end.is_a?(Numeric)
|
|
85
85
|
|
|
86
|
-
|
|
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
|
|
86
|
+
RangeUtils.apply_numeric_range(constraints, rng)
|
|
95
87
|
end
|
|
96
88
|
|
|
97
89
|
def apply_excluded_from_list(args)
|
|
@@ -135,7 +127,7 @@ module GrapeOAS
|
|
|
135
127
|
|
|
136
128
|
def handle_range(args)
|
|
137
129
|
rng = ArgumentExtractor.extract_range(args.first)
|
|
138
|
-
|
|
130
|
+
RangeUtils.apply_numeric_range(constraints, rng) if rng
|
|
139
131
|
end
|
|
140
132
|
|
|
141
133
|
def handle_multiple_of(args)
|
|
@@ -88,12 +88,16 @@ module GrapeOAS
|
|
|
88
88
|
def initialize_or_reuse_schema
|
|
89
89
|
@registry[@entity_class] ||= ApiModel::Schema.new(
|
|
90
90
|
type: Constants::SchemaTypes::OBJECT,
|
|
91
|
-
canonical_name:
|
|
91
|
+
canonical_name: resolve_canonical_name,
|
|
92
92
|
description: nil,
|
|
93
93
|
nullable: nil,
|
|
94
94
|
)
|
|
95
95
|
end
|
|
96
96
|
|
|
97
|
+
def resolve_canonical_name
|
|
98
|
+
EntityIntrospectorSupport.resolve_canonical_name(@entity_class)
|
|
99
|
+
end
|
|
100
|
+
|
|
97
101
|
def populate_schema(schema)
|
|
98
102
|
doc = entity_doc
|
|
99
103
|
apply_schema_metadata(schema, doc)
|
|
@@ -5,26 +5,12 @@ module GrapeOAS
|
|
|
5
5
|
module EntityIntrospectorSupport
|
|
6
6
|
# Handles discriminator fields in entity inheritance for polymorphic schemas.
|
|
7
7
|
class DiscriminatorHandler
|
|
8
|
-
# Checks if an entity inherits from a parent that uses discriminator.
|
|
9
|
-
#
|
|
10
|
-
# @param entity_class [Class] the entity class to check
|
|
11
|
-
# @return [Boolean] true if parent has a discriminator field
|
|
12
|
-
def self.inherits_with_discriminator?(entity_class)
|
|
13
|
-
parent = find_parent_entity(entity_class)
|
|
14
|
-
parent && new(parent).discriminator?
|
|
15
|
-
end
|
|
16
|
-
|
|
17
8
|
# Finds the parent entity class if one exists.
|
|
18
9
|
#
|
|
19
10
|
# @param entity_class [Class] the entity class
|
|
20
11
|
# @return [Class, nil] the parent entity class or nil
|
|
21
12
|
def self.find_parent_entity(entity_class)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
parent = entity_class.superclass
|
|
25
|
-
return nil unless parent && parent < Grape::Entity && parent != Grape::Entity
|
|
26
|
-
|
|
27
|
-
parent
|
|
13
|
+
EntityIntrospectorSupport.find_parent_entity(entity_class)
|
|
28
14
|
end
|
|
29
15
|
|
|
30
16
|
def initialize(entity_class)
|
|
@@ -65,17 +51,8 @@ module GrapeOAS
|
|
|
65
51
|
|
|
66
52
|
private
|
|
67
53
|
|
|
68
|
-
# Gets the exposures defined on the entity class.
|
|
69
|
-
#
|
|
70
|
-
# @return [Array] list of entity exposures
|
|
71
54
|
def exposures
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
root = @entity_class.root_exposures
|
|
75
|
-
list = root.instance_variable_get(:@exposures) || []
|
|
76
|
-
Array(list)
|
|
77
|
-
rescue NoMethodError
|
|
78
|
-
[]
|
|
55
|
+
EntityIntrospectorSupport.exposures(@entity_class)
|
|
79
56
|
end
|
|
80
57
|
end
|
|
81
58
|
end
|