crashappsec / gql_schema_codegen

A module to generate Python typings from a GrapqhQL schema
MIT License
0 stars 1 forks source link

Add basic interface inheritance support (fixes #8) #9

Closed nettrino closed 1 year ago

nettrino commented 1 year ago

This PR partially addresses #8. Currently, most graphql servers (and particularly for our case, neo4j over apollo) don't appropriately support interfaces inheriting from interfaces. Thus, if we have smth like

  interface ResourceEntity {
    id: ID! @id(autogenerate: false)
    type: NodeType!
  }

  interface CloudResourceEntity implements ResourceEntity {
    id: ID! @id(autogenerate: false)
    type: NodeType!
    provider: CloudProvider!

then CloudResourceEntity will be a regular interface without implementing the secondary interface in the schema that will be emitted. As we autogenerate types, we want any field or directive (e.g., a @relationship directive) that is of type ResourceEntity, to support CloudResourceEntity. We want to be able to do this in a best effort way for auto-generated code, until things are supported by neo4j or other servers.

For types that implement CloudResourceEntity, they need to declare that they implement ResourceEntity as well, so for python, we just need to fix inheritance for CloudResourceEntity classes, as well as any types that implement the interface (e.g., so as to not have method resolution order problems). Thus, assuming a type

 type AWSCloudPlatform implements CloudPlatform & CloudResourceEntity & ResourceEntity {
...
} 

we want the emitted python code to be

@dataclass(kw_only=True)
class ResourceEntity(DataClassJSONMixin):
...

@dataclass(kw_only=True)
class CloudResourceEntity(ResourceEntity):
...

@dataclass(kw_only=True)
class AWSCloudPlatform(CloudPlatform, CloudResourceEntity):
...

This was not supported as of today as

This still leaves a question open as to how do we deal with accessing said types via graphql when it comes to neo4j. For instance, if we have a field of type ResourceEntity, and we want to pass CloudResourceEntity there, that is allowed by the RFC (see https://github.com/graphql/graphql-spec/pull/373) but this won't be understood by the graphql server for now. Unions of interfaces are also not allowed by the SPEC (https://github.com/graphql/graphql-js/issues/451).

That said, I don't know if we will ever run into this issue, since we would be able to pass all types implementing the interface as inputs (they will be implementing all interfaces in case of intermediate interfaces by default, thus AWSCloudPlatform always has to implement both CloudResourceEntity and ResourceEntity). Thus I think this will be OK for now for most practical scenarios.

Since the GraphQL that will be getting emitted by Apollo+Neo4j as of today will strip away the implements part of any interface, essentially converting

  interface CloudResourceEntity implements ResourceEntity {
    id: ID! @id(autogenerate: false)
    ...

to

  interface CloudResourceEntity  {
    id: ID! @id(autogenerate: false)
 ...

we are forced to pass interface inheritance information via the config file. To do so, we use interfaceInheritance as a magic key in the yaml config, so we would be passing smth like:

scalars:
  CVE: str
  ...

interfaceInheritance:
  CloudResourceEntity: ResourceEntity