Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .generator/src/generator/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def cli(specs, output):
env.globals["get_default"] = openapi.get_default
env.globals["get_container_type"] = openapi.get_container_type
env.globals["get_security_names"] = openapi.get_security_names
env.globals["prepare_oneof_methods"] = formatter.prepare_oneof_methods

api_j2 = env.get_template("Api.j2")
model_j2 = env.get_template("model.j2")
Expand Down
73 changes: 73 additions & 0 deletions .generator/src/generator/formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -650,3 +650,76 @@ def get_response_type(schema, version):

def attribute_path(attribute):
return ".".join(attribute_name(a) for a in attribute.split("."))


def prepare_oneof_methods(model, get_type_func):
"""
Pre-compute method information for oneOf types to handle erasure collisions.

Returns a list of dicts with:
- schema: the original oneOf schema
- param_type: full parameterized type (e.g., "List<String>")
- unparam_type: unparameterized type (e.g., "List")
- use_factory: True if factory method needed (collision detected)
- constructor_name: name for constructor/factory method
- getter_name: name for getter method
"""
# Handle both dict-style and object-style access
if isinstance(model, dict):
one_of = model.get('oneOf', [])
elif hasattr(model, 'oneOf'):
one_of = model.oneOf
elif hasattr(model, 'get'):
one_of = model.get('oneOf', [])
else:
return []

if not one_of:
return []

# First pass: count unparameterized types
unparam_counts = {}
for oneOf in one_of:
param_type = get_type_func(oneOf)
unparam_type = un_parameterize_type(param_type)
unparam_counts[unparam_type] = unparam_counts.get(unparam_type, 0) + 1

# Second pass: compute method names
result = []
for oneOf in one_of:
param_type = get_type_func(oneOf)
unparam_type = un_parameterize_type(param_type)
has_collision = unparam_counts[unparam_type] > 1

# Compute constructor/factory method name
if has_collision:
if param_type.startswith('List<'):
inner_type = param_type[5:-1]
constructor_name = f"from{inner_type}List"
else:
safe_type = param_type.replace('<', '').replace('>', '').replace(' ', '').replace(',', '')
constructor_name = f"from{safe_type}"
else:
constructor_name = None # Regular constructor

# Compute getter method name
if has_collision:
if param_type.startswith('List<'):
inner_type = param_type[5:-1]
getter_name = f"get{inner_type}List"
else:
safe_type = param_type.replace('<', '').replace('>', '').replace(' ', '').replace(',', '')
getter_name = f"get{safe_type}"
else:
getter_name = f"get{unparam_type}"

result.append({
'schema': oneOf,
'param_type': param_type,
'unparam_type': unparam_type,
'use_factory': has_collision,
'constructor_name': constructor_name,
'getter_name': getter_name,
})

return result
28 changes: 18 additions & 10 deletions .generator/src/generator/templates/modelOneOf.j2
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,20 @@ public class {{ name }} extends AbstractOpenApiSchema {
super("oneOf", Boolean.{{ "TRUE" if model.nullable else "FALSE" }});
}

{%- for oneOf in model.oneOf %}
public {{ name }}({{ get_type(oneOf) }} o) {
{%- set oneof_methods = prepare_oneof_methods(model, get_type) %}
{%- for method_info in oneof_methods %}
{%- if method_info.use_factory %}
public static {{ name }} {{ method_info.constructor_name }}({{ method_info.param_type }} o) {
{{ name }} instance = new {{ name }}();
instance.setActualInstance(o);
return instance;
}
{%- else %}
public {{ name }}({{ method_info.param_type }} o) {
super("oneOf", Boolean.{{ "TRUE" if model.nullable else "FALSE" }});
setActualInstance(o);
}
{%- endif %}
{%- endfor %}

static {
Expand Down Expand Up @@ -204,19 +213,18 @@ public class {{ name }} extends AbstractOpenApiSchema {
return super.getActualInstance();
}

{%- for oneOf in model.oneOf %}
{%- set dataType = get_type(oneOf) %}
{%- set unParameterizedDataType = get_type(oneOf)|un_parameterize_type %}
{%- set oneof_methods = prepare_oneof_methods(model, get_type) %}
{%- for method_info in oneof_methods %}

/**
* Get the actual instance of `{{ dataType|escape_html }}`. If the actual instance is not `{{ dataType|escape_html }}`,
* Get the actual instance of `{{ method_info.param_type|escape_html }}`. If the actual instance is not `{{ method_info.param_type|escape_html }}`,
* the ClassCastException will be thrown.
*
* @return The actual instance of `{{ dataType|escape_html }}`
* @throws ClassCastException if the instance is not `{{ dataType|escape_html }}`
* @return The actual instance of `{{ method_info.param_type|escape_html }}`
* @throws ClassCastException if the instance is not `{{ method_info.param_type|escape_html }}`
*/
public {{ dataType }} get{{ unParameterizedDataType }}() throws ClassCastException {
return ({{ dataType }})super.getActualInstance();
public {{ method_info.param_type }} {{ method_info.getter_name }}() throws ClassCastException {
return ({{ method_info.param_type }})super.getActualInstance();
}

{%- endfor %}
Expand Down