sangria icon indicating copy to clipboard operation
sangria copied to clipboard

Schema transformation

Open OlegIlyenko opened this issue 6 years ago • 4 comments

The idea is to provide an easy and convenient way to transform existing schema. Since all types are cross-referenced within the schema, the whole schema needs to be re-created in a similar way as in the AST materializer where it re-creates existing types with the help of the builder.

In this case a dedicated builder might be useful as a low-level implementation detail. But it also would be nice to have a high-level DSL. Here is just an idea of how it might look like:

val newSchema =
  schema
    .mapObjectType("Book", _.rename("Publication"))
    .mapOutputTypes(_.filterFileds(field ⇒ !field.tags.contains(Preview)))

OlegIlyenko avatar Nov 12 '18 16:11 OlegIlyenko

Hey @OlegIlyenko I'm taking a look at this issue and would like to double check that the approach we use internally fits what you have in mind.

This is our current implementation

class PreviewSchemaFilter extends DefaultAstSchemaBuilder[RequestContext] {

  override def extendObjectType(origin: MatOrigin,
                                existing: ObjectType[RequestContext, _],
                                extensions: List[ast.ObjectTypeExtensionDefinition],
                                fields: () => List[Field[RequestContext, Any]],
                                interfaces: List[InterfaceType[RequestContext, Any]],
                                mat: AstSchemaMaterializer[RequestContext]): ObjectType[RequestContext, Any] = {
    existing
      .asInstanceOf[ObjectType[RequestContext, Any]]
      .copy(
        interfaces = interfaces,
        fieldsFn = () => fields().filterNot(_.tags.contains(Preview))
      )
  }
}

object PreviewSchemaFilter {

  def filter(sangriaSchema: SangriaSchema): SangriaSchema = {
    val emptyExtension = ObjectTypeExtensionDefinition(sangriaSchema.query.name, Vector.empty, Vector.empty)
    AstSchemaMaterializer.extendSchema(sangriaSchema, Document(Vector(emptyExtension)), new PreviewSchemaFilter)
  }
}

In a nutshell, we extend an existing schema and filter our the fields that have a certain flag. This allows us to create two schema and use one or the other depending on the incoming request.

So if this looks good, I would use this as a starting point for a low-level implementation and move towards a more generic approach.

Axxiss avatar Nov 19 '18 12:11 Axxiss

Yes, exactly, this sounds like something I also was thinking about. To some extent, I think the first step might be splitting AstSchemaBuilder in 2 interfaces: once with build* methods and another one with extend* methods. I think my current concern is that this interface as well as AstSchemaMaterializer are coupled with SDL/AST. As schema is materialized from SDL, materializer also does the same logic that schema "transformer" would need to do. In practice, right now one could use AstSchemaMaterializer to just transform the schema (something that you are doing in the PR and in your example). But it does not feel right to mix in SDL-related functionality when the some component only needs to recursively copy the schema and transform some parts along the way. So I was wondering whether we can take AstSchemaMaterializer and AstSchemaBuilder (possibly in a way that is reusable for both) and extract extend* related functionality as its own thing (but without coupling it to SDL)

OlegIlyenko avatar Nov 20 '18 22:11 OlegIlyenko

I've been digging through AstSchemaMaterializer and AstSchemaBuilder.

To get to know a bit of the internals I extracted the extend methods into its own interface, you can find the details in #421

Then we have a circular dependency between AstSchemaMaterializer and AstSchemaBuilder. We have two main scenarios calling mat.document or getting some type from the schema materializer. I think we could remove the direct dependency from AstSchemaBuilder to AstSchemaMaterializer but this will require a change on AstSchemaBuilder signature which is a braking change. Perhaps I'm missing something but so far that's the only way I see to completely decouple AstSchemaMaterializer and AstSchemaBuilder

This are the class from AstSchemaBuilder to AstSchemaMaterializer.

  • document

https://github.com/sangria-graphql/sangria/blob/65ffe26e4e2f01ded45520e6e872414f9349c7d4/src/main/scala/sangria/schema/AstSchemaBuilder.scala#L327

https://github.com/sangria-graphql/sangria/blob/65ffe26e4e2f01ded45520e6e872414f9349c7d4/src/main/scala/sangria/schema/AstSchemaBuilder.scala#L349

  • getOutputType

https://github.com/sangria-graphql/sangria/blob/65ffe26e4e2f01ded45520e6e872414f9349c7d4/src/main/scala/sangria/schema/AstSchemaBuilder.scala#L555

  • getTypeFromExistingType

https://github.com/sangria-graphql/sangria/blob/65ffe26e4e2f01ded45520e6e872414f9349c7d4/src/main/scala/sangria/schema/AstSchemaBuilder.scala#L593-L598

  • getInputTypeFromExistingType

https://github.com/sangria-graphql/sangria/blob/65ffe26e4e2f01ded45520e6e872414f9349c7d4/src/main/scala/sangria/schema/AstSchemaBuilder.scala#L600-L613

  • getInputType

https://github.com/sangria-graphql/sangria/blob/65ffe26e4e2f01ded45520e6e872414f9349c7d4/src/main/scala/sangria/schema/AstSchemaBuilder.scala#L631-L638

https://github.com/sangria-graphql/sangria/blob/65ffe26e4e2f01ded45520e6e872414f9349c7d4/src/main/scala/sangria/schema/AstSchemaBuilder.scala#L656-L664

Axxiss avatar Nov 23 '18 12:11 Axxiss

Any more progress on this?

Habitats avatar May 09 '19 15:05 Habitats