ChilliCream / graphql-platform

Welcome to the home of the Hot Chocolate GraphQL server for .NET, the Strawberry Shake GraphQL client for .NET and Banana Cake Pop the awesome Monaco based GraphQL IDE.
https://chillicream.com
MIT License
5.26k stars 745 forks source link

Option to generate separate types that are reused #5572

Open SierraNL opened 1 year ago

SierraNL commented 1 year ago

Is your feature request related to a problem?

I'm currently using StrawberryShake to retrieve data from a GraphQL endpoint, than map it to another object model, serialize it to XML to provide it to another service.

The object model and the query response contain quite a few types that are reused on several levels in the object model. But StrawBerryShake generated the type names in a hierarchical way. Like IMyQueryName_RootObject_ChildObject. This makes IMyQueryName_RootObject_ChildObject and IMyQueryName_RootObject_OtherNode_ChildObject two different types, although they are the same.

This causes my mapping to require more duplicate methods that only differ in incoming type name.

The solution you'd like

I know this hierarchical way of generating type names is safer to prevent type clashes.

That's why I would like to see the option to generate types in a way that types with the same name are considered the same. So they are reused in the generated client code.

So I have an easier time mapping.

Product

Strawberry Shake

michaelstaib commented 1 year ago

Can you post your GraphQL query?

SierraNL commented 1 year ago

I made it a bit smaller and translated to english:

query Fetch($personId: UUID!) {
    getPersonByPersonID(personID: $personId)
    {
        personID
        name
        startdate
        enddate
        contactPerson
        {
              id
              organisation
              name
              contactInfo
              {
                    address
                    {
                        addressType
                        streetName
                        houseNumber
                        postalCode
                        city
                        countryCode
                        startdate
                       enddate
                    }
                    telephone
                    {
                        telephoneNumber
                        countryPrefix
                        startdate
                       enddate
                    }
                    email
                    {
                        emailaddress
                        startdate
                        enddate
                    }
             }
        }
        contactInfo
        {
             address
             {
                    addressType
                    streetName
                    houseNumber
                    postalCode
                    city
                    countryCode
                    startdate
                    enddate
              }
              telephone
              {
                    telephoneNumber
                    countryPrefix
                    startdate
                    enddate
              }
              email
               {
                    emailaddress
                    startdate
                    enddate
               }
             }
         }
     }
     comments
     }
}

As you can see the whole contactInfo element is part of person type and contactPerson type, and are really the same elements. But generated as 2 different classes.

solonrice commented 1 year ago

It looks like if you use GraphQL fragments, the code generator will turn those into interfaces. Those can be reused and might help with your mappings. I just discovered this, so there may be gotchas, but you can check it out if you are still stuck on this (or anyone else finds this issue).

fragment FullAddress on address {
  addressType
  streetName
  houseNumber
  postalCode
  city
  countryCode
  startdate
  enddate
}

I think this will make a CLR interface called IFullAddress. Then you refer to that fragment in your query:

query Fetch($personId: UUID!) {
    getPersonByPersonID(personID: $personId)
    {
        personID
        name
        startdate
        enddate
        contactPerson
        {
              id
              organisation
              name
              contactInfo
              {
                    address
                    {
                      ...FullAddress
                    }
.
.
.

So now that contact info object will have address class in it that implements the interface IFullAddress. And anywhere else you use that address fragment will get the same interface.

You can also have a fragment inside another one and the generator will make them inherited interfaces:

fragment ShortAddress on address {
  addressType
  streetName
}

fragment FullAddress on address {
  ...ShortAddress
  houseNumber
  postalCode
  city
  countryCode
  startdate
  enddate