graphql-mesh icon indicating copy to clipboard operation
graphql-mesh copied to clipboard

OAS 'additionalProperties' treated as regular field in some cases

Open ivan-zakharchuk opened this issue 7 months ago • 6 comments

Issue workflow progress

Progress of the issue based on the Contributor Workflow

  • [ ] 1. The issue provides a reproduction available on CodeSandbox
  • [x ] 2. A failing test has been provided
  • [ ] 3. A local solution has been provided
  • [ ] 4. A pull request is pending review

Describe the bug

After running mesh-compose on a swagger yaml with 'additionalProperties' field, in cases when it is an object, the resulting supegraph contains it as a simple object property instead of JSON:

application/json:
              schema:
                type: object
                properties:
                  "additionalPropertiesTrue":
                    type: object
                    additionalProperties: true
                  "additionalPropertiesInteger":
                    type: object
                    additionalProperties:
                      type: integer
                  "additionalPropertiesObject":
                    type: object
                    additionalProperties:
                      type: object
                      properties:
                        code:
                          type: integer
                        text:
                          type: string
                  "additionalPropertiesObjectWithCustomTitle":
                    type: object
                    additionalProperties:
                      type: object
                      properties:
                        code:
                          type: integer
                        text:
                          type: string
                      title: This is a custom title
                  "additionalPropertiesArrayInteger":
                    type: object
                    additionalProperties:
                      type: array
                      items:
                        type: integer
                  "additionalPropertiesObjectRef":
                    type: object
                    additionalProperties:
                      "$ref": "#/components/schemas/ref"
                  "additionalPropertiesObjectRefNested":
                    type: object
                    additionalProperties:
                      "$ref": "#/components/schemas/ref-with-ref"

translated into

  additionalPropertiesTrue: JSON
  additionalPropertiesInteger: JSON
  additionalPropertiesObject: IvanV3Api_PetStore_QueryPostAdditionalPropertiesObject @source(
    name: "additionalPropertiesObject"
    type: "query_post_additionalPropertiesObject"
    subgraph: "V3_Petstore"
  )
  additionalPropertiesObjectWithCustomTitle: IvanV3Api_PetStore_QueryPostAdditionalPropertiesObjectWithCustomTitle @source(
    name: "additionalPropertiesObjectWithCustomTitle"
    type: "query_post_additionalPropertiesObjectWithCustomTitle"
    subgraph: "V3_Petstore"
  )
  additionalPropertiesArrayInteger: JSON
  additionalPropertiesObjectRef: JSON
  additionalPropertiesObjectRefNested: IvanV3Api_PetStore_QueryPostAdditionalPropertiesObjectRefNested @source(
    name: "additionalPropertiesObjectRefNested"
    type: "query_post_additionalPropertiesObjectRefNested"
    subgraph: "V3_Petstore"
  )

To Reproduce Steps to reproduce the behavior:

open attached sanbox and run pnpm run start

Expected behavior

all the output for response fields should have JSON type

Environment:

"@graphql-mesh/compose-cli": "^1.4.0", "@omnigraph/openapi": "^0.109.0",

Additional context

the original testing file is from: https://github.com/readmeio/oas-examples/blob/060d4ffbbe63003f923c8cd7268ee39a495cf21f/3.0/yaml/schema-additional-properties.yaml#L18

ivan-zakharchuk avatar Jun 16 '25 10:06 ivan-zakharchuk

If you have additionalProperties defined, Mesh adds an additional properties field in order to have those extra fields to be resolvers. This is intentional. If you don't want that, you can filter those fields out by using transforms.

ardatan avatar Jun 16 '25 10:06 ardatan

Thanks for the reply @ardatan! I guess I understand that. Does that means the fragment to such gql should be:

additionalPropertiesObject {
    additionalProperties {
      key
      value {
        code
        text
      }
    }
  }

and it'll be auto-mapped with correct resolvers right?

Note: I'm using codegen to generate hooks out of this gql

ivan-zakharchuk avatar Jun 16 '25 13:06 ivan-zakharchuk

If it is JSON, you can't select the fields under of it (which is related to GraphQL itself not Mesh)
But if it is an object, you can select fields like that as in generated in the schema.

ardatan avatar Jun 16 '25 13:06 ardatan

@ardatan That's the issue, it is a JSON like:

{
  en: {
    code: '<span>some str</span>',
    text: 'some example text'
  },
  fr: {},
  // pl: ... etc 
}

and when we have schema with

additionalProperties:
  type: object
  # or ref to object

it basically means it's a JSON, since additionalProperties is a schema directive - not a field, so if this GraphQL limitation is not worked around somehow, then I don't understand why this is intentional and how it can be used to make an actual call.

Note: generated type(from the mentioned sandbox):

additionalPropertiesObject: IvanV3Api_PetStore_QueryPostAdditionalPropertiesObject @source(
    name: "additionalPropertiesObject"
    type: "query_post_additionalPropertiesObject"
    subgraph: "V3_Petstore"
  )

type IvanV3Api_PetStore_QueryPostAdditionalPropertiesObject @source(name: "query_post_additionalPropertiesObject", subgraph: "V3_Petstore")  @join__type(graph: V3_PETSTORE)  {
  additionalProperties: [IvanV3Api_PetStore_QueryPostAdditionalPropertiesObjectAdditionalPropertiesEntry] @dictionary(subgraph: "V3_Petstore")  @source(
    name: "additionalProperties"
    type: "[query_post_additionalPropertiesObject_additionalProperties_entry]"
    subgraph: "V3_Petstore"
  )
}
type IvanV3Api_PetStore_QueryPostAdditionalPropertiesObjectAdditionalPropertiesEntry @source(
  name: "query_post_additionalPropertiesObject_additionalProperties_entry"
  subgraph: "V3_Petstore"
) @join__type(graph: V3_PETSTORE)  {
  key: ID!
  value: IvanV3Api_PetStore_QueryPostAdditionalPropertiesObjectAdditionalProperties @source(
    name: "value"
    type: "query_post_additionalPropertiesObject_additionalProperties"
    subgraph: "V3_Petstore"
  )
}

type IvanV3Api_PetStore_QueryPostAdditionalPropertiesObjectAdditionalProperties @source(
  name: "query_post_additionalPropertiesObject_additionalProperties"
  subgraph: "V3_Petstore"
) @join__type(graph: V3_PETSTORE)  {
  code: Int
  text: String
}

ivan-zakharchuk avatar Jun 16 '25 13:06 ivan-zakharchuk

If the generated type is JSON, you can't select fields like;

additionalProperties { key value }

If only the generated type is XAdditionalPropertiesObjectAdditionalProperties, you can select fields

ardatan avatar Jun 16 '25 14:06 ardatan

@ardatan in the example above we have: QueryPostAdditionalPropertiesObject → QueryPostAdditionalPropertiesObjectAdditionalPropertiesEntry → PetStore_QueryPostAdditionalPropertiesObjectAdditionalProperties

so that I can select fields, the question is how such call will work, basically my gql call data return will have a property additionalProperties with the correctly mapped key: value records?

ivan-zakharchuk avatar Jun 16 '25 14:06 ivan-zakharchuk