Skip to content

[BUG] [Elixir] Invalid typespecs when using allOf and single schema reference #21135

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
5 of 6 tasks
efcasado opened this issue Apr 23, 2025 · 1 comment
Closed
5 of 6 tasks

Comments

@efcasado
Copy link
Contributor

efcasado commented Apr 23, 2025

Bug Report Checklist

  • Have you provided a full/minimal spec to reproduce the issue?
  • Have you validated the input using an OpenAPI validator (example)?
  • Have you tested with the latest master to confirm the issue still exists?
  • Have you searched for related issues/PRs?
  • What's the actual output vs expected output?
  • [Optional] Sponsorship to speed up the bug fix or feature request (example)
Description

When using the Elixir generator with model composition, the resulting type specifications can be both incorrect and invalid - leading to compilation errors in the generated Elixir client.

This issue manifests when an allOf composition references only a single schema. In such cases, the generated typespec looks like the following:

@spec bar(Tesla.Env.client(), keyword()) :: {:ok, OpenAPI.Model.any().t()} | {:error, Tesla.Env.t()}

Notice the problematic OpenAPI.Model.any().t(). This is not a valid Elixir type. At best, it should be any(), but ideally, the generator should produce the actual schema module, such as OpenAPI.Model.Bar200Response.t().

This suggests there are two separate problems:

  • Type name normalization is failing and not treating any() as a terminal type.
  • Schema resolution is not correctly identifying the intended model for the typespec.
openapi-generator version

7.12.0

OpenAPI declaration file content or url
openapi: 3.0.0
info:
  version: 1.0.0
  title: A minimal OpenAPI example
paths:
  /foo:
    get:
      operationId: foo
      responses:
        200:
          description: Successful operation
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/Foo"
                  - $ref: "#/components/schemas/Bar"

  /bar:
    get:
      operationId: bar
      responses:
        200:
          description: Successful operation
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/Foo"

components:
  schemas:
    Foo:
      type: object
      properties:
        foo_value:
          type: string

    Bar:
      type: object
      properties:
        bar_value:
          type: string
Generation Details
openapi-generator-cli generate -i $(OAS_FILE) -g elixir -o local/out/elixir
@efcasado
Copy link
Contributor Author

efcasado commented Apr 23, 2025

In case it helps, these are the debug logs I got for foo and bar responses, respectively.

CodegenResponse{headers=[], code='200', is1xx='false', is2xx='false', is3xx='false', is4xx='false', is5xx='false', message='Successful operation', examples=null, dataType='AMinimalOpenAPIExample.Model.Foo200Response.t', baseType='Foo200Response', containerType='null', containerTypeMapped='null', hasHeaders=false, isString=false, isNumeric=false, isInteger=false, isShort=false, isLong=false, isUnboundedInteger=false, isNumber=false, isFloat=false, isDouble=false, isDecimal=false, isByteArray=false, isBoolean=false, isDate=false, isDateTime=false, isUuid=false, isEmail=false, isPassword=false, isModel=true, isFreeFormObject=false, isAnyType=false, isDefault=false, simpleType=false, primitiveType=false, isMap=false, isOptional=false, isArray=false, isBinary=false, isFile=false, schema=class Schema {
    type: null
    format: null
    $ref: #/components/schemas/foo_200_response
    description: null
    title: null
    multipleOf: null
    maximum: null
    exclusiveMaximum: null
    minimum: null
    exclusiveMinimum: null
    maxLength: null
    minLength: null
    pattern: null
    maxItems: null
    minItems: null
    uniqueItems: null
    maxProperties: null
    minProperties: null
    required: null
    not: null
    properties: null
    additionalProperties: null
    nullable: null
    readOnly: null
    writeOnly: null
    example: null
    externalDocs: null
    deprecated: null
    discriminator: null
    xml: null
}, jsonSchema='{
  "description" : "Successful operation",
  "content" : {
    "application/json" : {
      "schema" : {
        "$ref" : "#/components/schemas/foo_200_response"
      }
    }
  }
}', vendorExtensions={}, maxProperties=null, minProperties=null, uniqueItems=false, uniqueItemsBoolean=null, maxItems=null, minItems=null, maxLength=null, minLength=null, exclusiveMinimum=false, exclusiveMaximum=false, minimum='null', maximum='null', pattern='null', multipleOf='null', items='null', additionalProperties='null', vars='[]', requiredVars='[]', isNull='false, isVoid='false, hasValidation='false, getAdditionalPropertiesIsAnyType=false, getHasVars=false, getHasRequired=false, getHasDiscriminatorWithNonEmptyMapping=false, composedSchemas=null, hasMultipleTypes=false, responseHeaders=[], content={application/json=CodegenMediaType{schema=CodegenProperty{openApiType='Foo200Response', baseName='SchemaFor200ResponseBodyApplicationJson', complexType='Foo200Response', getter='getSchemaFor200ResponseBodyApplicationJson', setter='setSchemaFor200ResponseBodyApplicationJson', description='null', dataType='AMinimalOpenAPIExample.Model.Foo200Response.t', datatypeWithEnum='AMinimalOpenAPIExample.Model.Foo200Response.t', dataFormat='null', name='SchemaFor200ResponseBodyApplicationJson', min='null', max='null', defaultValue='null', defaultValueWithParam=' = data.SchemaFor200ResponseBodyApplicationJson;', baseType='Foo200Response', containerType='null', containerTypeMapped='null', title='null', unescapedDescription='null', maxLength=null, minLength=null, pattern='null', example='null', jsonSchema='{
  "$ref" : "#/components/schemas/foo_200_response"
}', minimum='null', maximum='null', exclusiveMinimum=false, exclusiveMaximum=false, required=false, deprecated=false, hasMoreNonReadOnly=false, isPrimitiveType=false, isModel=true, isContainer=false, isString=false, isNumeric=false, isInteger=false, isShort=false, isLong=false, isUnboundedInteger=false, isNumber=false, isFloat=false, isDouble=false, isDecimal=false, isByteArray=false, isBinary=false, isFile=false, isBoolean=false, isDate=false, isDateTime=false, isUuid=false, isUri=false, isEmail=false, isPassword=false, isFreeFormObject=false, isArray=false, isMap=false, isOptional=false, isEnum=false, isInnerEnum=false, isEnumRef=false, isAnyType=false, isReadOnly=false, isWriteOnly=false, isNullable=false, isSelfReference=false, isCircularReference=false, isDiscriminator=false, isNew=false, isOverridden=null, _enum=null, allowableValues=null, items=null, additionalProperties=null, vars=[], requiredVars=[], mostInnerItems=null, vendorExtensions={}, hasValidation=false, isInherited=false, discriminatorValue='null', nameInCamelCase='schemaFor200ResponseBodyApplicationJson', nameInPascalCase='SchemaFor200ResponseBodyApplicationJson', nameInSnakeCase='SCHEMA_FOR200_RESPONSE_BODY_APPLICATION_JSON', enumName='null', maxItems=null, minItems=null, maxProperties=null, minProperties=null, uniqueItems=false, uniqueItemsBoolean=null, multipleOf=null, isXmlAttribute=false, xmlPrefix='null', xmlName='null', xmlNamespace='null', isXmlWrapped=false, isNull=false, isVoid=false, getAdditionalPropertiesIsAnyType=false, getHasVars=false, getHasRequired=false, getHasDiscriminatorWithNonEmptyMapping=false, composedSchemas=null, hasMultipleTypes=false, hasSanitizedName=false, requiredVarsMap=null, ref=#/components/schemas/foo_200_response, schemaIsFromAdditionalProperties=false, isBooleanSchemaTrue=false, isBooleanSchemaFalse=false, format=null, dependentRequired=null, contains=null}, encoding=null, vendorExtensions={}}}, requiredVarsMap=null, ref=null, schemaIsFromAdditionalProperties=false}
CodegenResponse{headers=[], code='200', is1xx='false', is2xx='false', is3xx='false', is4xx='false', is5xx='false', message='Successful operation', examples=null, dataType='any()', baseType='Foo', containerType='null', containerTypeMapped='null', hasHeaders=false, isString=false, isNumeric=false, isInteger=false, isShort=false, isLong=false, isUnboundedInteger=false, isNumber=false, isFloat=false, isDouble=false, isDecimal=false, isByteArray=false, isBoolean=false, isDate=false, isDateTime=false, isUuid=false, isEmail=false, isPassword=false, isModel=true, isFreeFormObject=false, isAnyType=false, isDefault=false, simpleType=false, primitiveType=false, isMap=false, isOptional=false, isArray=false, isBinary=false, isFile=false, schema=class ComposedSchema {
    class Schema {
        type: null
        format: null
        $ref: null
        description: null
        title: null
        multipleOf: null
        maximum: null
        exclusiveMaximum: null
        minimum: null
        exclusiveMinimum: null
        maxLength: null
        minLength: null
        pattern: null
        maxItems: null
        minItems: null
        uniqueItems: null
        maxProperties: null
        minProperties: null
        required: null
        not: null
        properties: null
        additionalProperties: null
        nullable: null
        readOnly: null
        writeOnly: null
        example: null
        externalDocs: null
        deprecated: null
        discriminator: null
        xml: null
    }
}, jsonSchema='{
  "description" : "Successful operation",
  "content" : {
    "application/json" : {
      "schema" : {
        "allOf" : [ {
          "$ref" : "#/components/schemas/Foo"
        } ]
      }
    }
  }
}', vendorExtensions={}, maxProperties=null, minProperties=null, uniqueItems=false, uniqueItemsBoolean=null, maxItems=null, minItems=null, maxLength=null, minLength=null, exclusiveMinimum=false, exclusiveMaximum=false, minimum='null', maximum='null', pattern='null', multipleOf='null', items='null', additionalProperties='null', vars='[]', requiredVars='[]', isNull='false, isVoid='false, hasValidation='false, getAdditionalPropertiesIsAnyType=false, getHasVars=false, getHasRequired=false, getHasDiscriminatorWithNonEmptyMapping=false, composedSchemas=null, hasMultipleTypes=false, responseHeaders=[], content={application/json=CodegenMediaType{schema=CodegenProperty{openApiType='Foo', baseName='SchemaFor200ResponseBodyApplicationJson', complexType='Foo', getter='getSchemaFor200ResponseBodyApplicationJson', setter='setSchemaFor200ResponseBodyApplicationJson', description='null', dataType='AMinimalOpenAPIExample.Model.Foo.t', datatypeWithEnum='AMinimalOpenAPIExample.Model.Foo.t', dataFormat='null', name='SchemaFor200ResponseBodyApplicationJson', min='null', max='null', defaultValue='null', defaultValueWithParam=' = data.SchemaFor200ResponseBodyApplicationJson;', baseType='Foo', containerType='null', containerTypeMapped='null', title='null', unescapedDescription='null', maxLength=null, minLength=null, pattern='null', example='null', jsonSchema='{
  "$ref" : "#/components/schemas/Foo"
}', minimum='null', maximum='null', exclusiveMinimum=false, exclusiveMaximum=false, required=false, deprecated=false, hasMoreNonReadOnly=false, isPrimitiveType=false, isModel=true, isContainer=false, isString=false, isNumeric=false, isInteger=false, isShort=false, isLong=false, isUnboundedInteger=false, isNumber=false, isFloat=false, isDouble=false, isDecimal=false, isByteArray=false, isBinary=false, isFile=false, isBoolean=false, isDate=false, isDateTime=false, isUuid=false, isUri=false, isEmail=false, isPassword=false, isFreeFormObject=false, isArray=false, isMap=false, isOptional=false, isEnum=false, isInnerEnum=false, isEnumRef=false, isAnyType=false, isReadOnly=false, isWriteOnly=false, isNullable=false, isSelfReference=false, isCircularReference=false, isDiscriminator=false, isNew=false, isOverridden=null, _enum=null, allowableValues=null, items=null, additionalProperties=null, vars=[], requiredVars=[], mostInnerItems=null, vendorExtensions={}, hasValidation=false, isInherited=false, discriminatorValue='null', nameInCamelCase='schemaFor200ResponseBodyApplicationJson', nameInPascalCase='SchemaFor200ResponseBodyApplicationJson', nameInSnakeCase='SCHEMA_FOR200_RESPONSE_BODY_APPLICATION_JSON', enumName='null', maxItems=null, minItems=null, maxProperties=null, minProperties=null, uniqueItems=false, uniqueItemsBoolean=null, multipleOf=null, isXmlAttribute=false, xmlPrefix='null', xmlName='null', xmlNamespace='null', isXmlWrapped=false, isNull=false, isVoid=false, getAdditionalPropertiesIsAnyType=false, getHasVars=false, getHasRequired=false, getHasDiscriminatorWithNonEmptyMapping=false, composedSchemas=null, hasMultipleTypes=false, hasSanitizedName=false, requiredVarsMap=null, ref=#/components/schemas/Foo, schemaIsFromAdditionalProperties=false, isBooleanSchemaTrue=false, isBooleanSchemaFalse=false, format=null, dependentRequired=null, contains=null}, encoding=null, vendorExtensions={}}}, requiredVarsMap=null, ref=null, schemaIsFromAdditionalProperties=false}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant