Skip to content

Commit 54465f4

Browse files
committed
Recover actual types from VariantCollectionCoercer in multi-type params
In Grape >= 3.3 `type: [A, B]` is wrapped in a VariantCollectionCoercer, and Grape's documentation pipeline serialises that wrapper via #to_s before storing it in route.params, which discards the original type list. The previous behaviour exposed the coercer's #inspect string (e.g. "#<Grape::Validations::Types::VariantCollectionCoercer:0x...>") as the swagger type. The live coercer is still reachable via the matching CoerceValidator's @converter, so collect_variant_types walks namespace_stackable[:validations] and builds a name => [types] lookup. When fulfill_params sees the VariantCollectionCoercer signature in the serialised type, it substitutes the real type list and lets the existing Array handling in DataType.parse_multi_type pick the primary type, matching how older Grape versions behaved when they passed the type array through directly.
1 parent c7458b4 commit 54465f4

1 file changed

Lines changed: 37 additions & 2 deletions

File tree

  • lib/grape-swagger/request_param_parsers

lib/grape-swagger/request_param_parsers/route.rb

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ def parse
1919
stackable_values = route.app&.inheritable_setting&.namespace_stackable
2020

2121
path_params = build_path_params(stackable_values)
22+
variant_types = collect_variant_types(stackable_values)
2223

23-
fulfill_params(path_params)
24+
fulfill_params(path_params, variant_types)
2425
end
2526

2627
private
@@ -47,7 +48,33 @@ def fetch_inherited_params(stackable_values)
4748
end
4849
end
4950

50-
def fulfill_params(path_params)
51+
# In Grape >= 3.3 `type: [A, B]` is wrapped in VariantCollectionCoercer and
52+
# Grape's documentation pipeline serialises it via `#to_s`, so the original
53+
# type list is lost in route.params. The live coercer is still reachable
54+
# through the CoerceValidator's @converter, so we rebuild a name => types
55+
# map and restore it in fulfill_params.
56+
def collect_variant_types(stackable_values)
57+
variant_types = {}
58+
return variant_types unless defined?(Grape::Validations::Types::VariantCollectionCoercer) &&
59+
defined?(Grape::Validations::Validators::CoerceValidator) &&
60+
stackable_values.respond_to?(:[])
61+
62+
Array(stackable_values[:validations]).each do |validator|
63+
next unless validator.is_a?(Grape::Validations::Validators::CoerceValidator)
64+
65+
converter = validator.instance_variable_get(:@converter)
66+
next unless converter.is_a?(Grape::Validations::Types::VariantCollectionCoercer)
67+
68+
types = converter.instance_variable_get(:@types)
69+
Array(validator.instance_variable_get(:@attrs)).each do |attr|
70+
variant_types[attr.to_s] = types
71+
end
72+
end
73+
74+
variant_types
75+
end
76+
77+
def fulfill_params(path_params, variant_types)
5178
# Merge path params options into route params
5279
route.params.each_with_object({}) do |(param, definition), accum|
5380
# The route.params hash includes both parametrized params (with a string as a key)
@@ -57,10 +84,18 @@ def fulfill_params(path_params)
5784
next if param.is_a?(String) && accum.key?(key)
5885

5986
defined_options = definition.is_a?(Hash) ? definition : {}
87+
defined_options = restore_variant_type(defined_options, param, variant_types)
6088
value = (path_params[param] || {}).merge(defined_options)
6189
accum[key] = value.empty? ? DEFAULT_PARAM_TYPE : value
6290
end
6391
end
92+
93+
def restore_variant_type(defined_options, param, variant_types)
94+
types = variant_types[param.to_s]
95+
return defined_options unless types
96+
97+
defined_options.merge(type: types)
98+
end
6499
end
65100
end
66101
end

0 commit comments

Comments
 (0)