From d0e0ae8db022dd7cd161f581a3ca2ad9da3d77d3 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Mon, 17 Apr 2023 17:29:52 +0000 Subject: [PATCH 1/3] wip self-contained subqueries --- .../main/scala/test/StarWarsSubquery2.scala | 1 - .../src/main/scala/clue/gen/GraphQLGen.scala | 71 ++++++++++++------- .../src/main/scala/clue/gen/QueryGen.scala | 2 +- 3 files changed, 45 insertions(+), 29 deletions(-) diff --git a/gen/output/src/main/scala/test/StarWarsSubquery2.scala b/gen/output/src/main/scala/test/StarWarsSubquery2.scala index 8925adb1..03c81999 100644 --- a/gen/output/src/main/scala/test/StarWarsSubquery2.scala +++ b/gen/output/src/main/scala/test/StarWarsSubquery2.scala @@ -9,7 +9,6 @@ import clue.GraphQLSubquery import io.circe.Json import test.StarWars - object StarWarsSubquery2 extends GraphQLSubquery.Typed[StarWars, Json]("Character") { override val subquery: String = """ { diff --git a/gen/rules/src/main/scala/clue/gen/GraphQLGen.scala b/gen/rules/src/main/scala/clue/gen/GraphQLGen.scala index e6e2adb0..ef060b58 100644 --- a/gen/rules/src/main/scala/clue/gen/GraphQLGen.scala +++ b/gen/rules/src/main/scala/clue/gen/GraphQLGen.scala @@ -32,6 +32,37 @@ class GraphQLGen(config: GraphQLGenConfig) newLineIndent + lines.replaceAll("\\n", newLineIndent) } + private object GraphQLAnnotated { + def unapply(tree: Tree): Option[(List[Mod], String, Template)] = tree match { + case Defn.Trait( + mods @ GraphQLAnnotation(_), + templateName, + Nil, + _, + template + ) => + Some((mods, templateName.value, template)) + + case Defn.Class( + mods @ GraphQLAnnotation(_), + templateName, + Nil, + _, + template + ) => + Some((mods, templateName.value, template)) + + case Defn.Object( + mods @ GraphQLAnnotation(_), + name, + template + ) => + Some((mods, name.value, template)) + + case _ => None + } + } + override def fix(implicit doc: SemanticDocument): Patch = { val importPatch: List[IO[Patch]] = doc.tokens.collect { @@ -42,18 +73,6 @@ class GraphQLGen(config: GraphQLGenConfig) val genPatch: List[IO[Patch]] = doc.tree .collect { - case obj @ Defn.Object( - GraphQLAnnotation(_), - name, - template - ) => // Annotated objects are copied as-is - // TODO: We should be able to validate the query! - IO.pure( - Patch.replaceTree( - obj, - indented(obj)(q"object $name $template".toString) - ) + Patch.removeGlobalImport(GraphQLAnnotation.symbol) - ) case obj @ Defn.Object( GraphQLStubAnnotation(_), _, @@ -87,18 +106,14 @@ class GraphQLGen(config: GraphQLGenConfig) ) ) + Patch.removeGlobalImport(GraphQLSchemaAnnotation.symbol) } - case obj @ Defn.Trait( - mods @ GraphQLAnnotation(_), - templateName, - Nil, - _, + case obj @ GraphQLAnnotated( + mods, + objName, Template(early, inits, self, stats) ) if inits.exists { case Init(Type.Apply(Type.Name("GraphQLOperation"), _), _, _) => true case _ => false } => - val objName = templateName.value - extractSchemaType(inits) match { case None => abort( @@ -155,18 +170,20 @@ class GraphQLGen(config: GraphQLGenConfig) } } } - case obj @ Defn.Class( + case obj @ GraphQLAnnotated( mods @ GraphQLAnnotation(_), - templateName, - Nil, - _, + objName, Template(early, inits, self, stats) ) if inits.exists { - case Init(Type.Apply(Type.Name("GraphQLSubquery"), _), _, _) => true - case _ => false + case Init( + Type.Apply(Type.Name("GraphQLSubquery"), _) | + Type.Apply(Type.Select(Term.Name("GraphQLSubquery"), Type.Name("Typed")), _), + _, + _ + ) => + true + case _ => false } => - val objName = templateName.value - extractSchemaAndRootTypes(inits) match { case None => abort( diff --git a/gen/rules/src/main/scala/clue/gen/QueryGen.scala b/gen/rules/src/main/scala/clue/gen/QueryGen.scala index 42abfe2c..c657c33f 100644 --- a/gen/rules/src/main/scala/clue/gen/QueryGen.scala +++ b/gen/rules/src/main/scala/clue/gen/QueryGen.scala @@ -27,7 +27,7 @@ trait QueryGen extends Generator { protected def extractSchemaAndRootTypes(list: List[Init]): Option[(Type.Name, String)] = list.collect { case Init( - Type.Apply(Type.Name("GraphQLSubquery"), List(tpe @ Type.Name(_))), + Type.Apply(_, (tpe @ Type.Name(_)) :: _), _, List(List(Lit.String(rootType))) ) => From d7df34dbddbaec3db52ab03efba8818dc329383c Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Wed, 26 Apr 2023 17:59:41 +0000 Subject: [PATCH 2/3] Validated typed operations and subqueries --- .../main/scala/test/StarWarsSubquery2.scala | 1 + .../src/main/scala/clue/gen/GraphQLGen.scala | 94 +++++++++++++------ 2 files changed, 67 insertions(+), 28 deletions(-) diff --git a/gen/output/src/main/scala/test/StarWarsSubquery2.scala b/gen/output/src/main/scala/test/StarWarsSubquery2.scala index 03c81999..8925adb1 100644 --- a/gen/output/src/main/scala/test/StarWarsSubquery2.scala +++ b/gen/output/src/main/scala/test/StarWarsSubquery2.scala @@ -9,6 +9,7 @@ import clue.GraphQLSubquery import io.circe.Json import test.StarWars + object StarWarsSubquery2 extends GraphQLSubquery.Typed[StarWars, Json]("Character") { override val subquery: String = """ { diff --git a/gen/rules/src/main/scala/clue/gen/GraphQLGen.scala b/gen/rules/src/main/scala/clue/gen/GraphQLGen.scala index ef060b58..e92fdedf 100644 --- a/gen/rules/src/main/scala/clue/gen/GraphQLGen.scala +++ b/gen/rules/src/main/scala/clue/gen/GraphQLGen.scala @@ -111,8 +111,14 @@ class GraphQLGen(config: GraphQLGenConfig) objName, Template(early, inits, self, stats) ) if inits.exists { - case Init(Type.Apply(Type.Name("GraphQLOperation"), _), _, _) => true - case _ => false + case Init( + Type.Apply(Type.Name("GraphQLOperation"), _) | + Type.Apply(Type.Select(Term.Name("GraphQLOperation"), Type.Name("Typed")), _), + _, + _ + ) => + true + case _ => false } => extractSchemaType(inits) match { case None => @@ -141,17 +147,33 @@ class GraphQLGen(config: GraphQLGenConfig) ) >> IO { val operation = queryResult.toOption.get - // Modifications to add the missing definitions. - val modObjDefs = scala.Function.chain( - List( - addImports(schemaType.value), - addVars(schema, operation, config), - addData(schema, operation, config, document.subqueries), - addVarEncoder, - addDataDecoder, - addConvenienceMethod(schemaType, operation, objName) - ) - ) + val typed = inits.exists { + case Init( + Type.Apply( + Type.Select(Term.Name("GraphQLOperation"), Type.Name("Typed")), + _ + ), + _, + _ + ) => + true + case _ => false + } + + val modObjDefs = + if (typed) // everything is already defined + identity[List[Stat]](_) + else // Modifications to add the missing definitions. + scala.Function.chain( + List( + addImports(schemaType.value), + addVars(schema, operation, config), + addData(schema, operation, config, document.subqueries), + addVarEncoder, + addDataDecoder, + addConvenienceMethod(schemaType, operation, objName) + ) + ) val newMods = GraphQLAnnotation.removeFrom(mods) @@ -211,21 +233,37 @@ class GraphQLGen(config: GraphQLGenConfig) ) >> IO { val operation = queryResult.toOption.get - // Modifications to add the missing definitions. - val modObjDefs = scala.Function.chain( - List( - addImports(schemaType.value), - addData( - schema, - operation, - config, - subquery.subqueries, - schema.types.find(_.name == rootTypeName) - ), - addDataDecoder, - addConvenienceMethod(schemaType, operation, objName) - ) - ) + val typed = inits.exists { + case Init( + Type.Apply( + Type.Select(Term.Name("GraphQLSubquery"), Type.Name("Typed")), + _ + ), + _, + _ + ) => + true + case _ => false + } + + val modObjDefs = + if (typed) // everything is already defined + identity[List[Stat]](_) + else // Modifications to add the missing definitions. + scala.Function.chain( + List( + addImports(schemaType.value), + addData( + schema, + operation, + config, + subquery.subqueries, + schema.types.find(_.name == rootTypeName) + ), + addDataDecoder, + addConvenienceMethod(schemaType, operation, objName) + ) + ) val newMods = GraphQLAnnotation.removeFrom(mods).filterNot { case Mod.Abstract() => true From 55aff20d532f8fbcadc7a525f6aca6210285a8ae Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Wed, 26 Apr 2023 18:14:52 +0000 Subject: [PATCH 3/3] DRY --- .../src/main/scala/clue/gen/GraphQLGen.scala | 88 +++++++++---------- 1 file changed, 43 insertions(+), 45 deletions(-) diff --git a/gen/rules/src/main/scala/clue/gen/GraphQLGen.scala b/gen/rules/src/main/scala/clue/gen/GraphQLGen.scala index e92fdedf..f44a525d 100644 --- a/gen/rules/src/main/scala/clue/gen/GraphQLGen.scala +++ b/gen/rules/src/main/scala/clue/gen/GraphQLGen.scala @@ -63,6 +63,44 @@ class GraphQLGen(config: GraphQLGenConfig) } } + private def isGraphQLOperation(inits: List[Init]) = + inits.exists { + case Init(Type.Apply(Type.Name("GraphQLOperation"), _), _, _) => true + case _ => false + } + + private def isGraphQLOperationTyped(inits: List[Init]) = + inits.exists { + case Init(Type.Apply( + Type.Select(Term.Name("GraphQLOperation"), Type.Name("Typed")), + _ + ), + _, + _ + ) => + true + case _ => false + } + + private def isGraphQLSubquery(inits: List[Init]) = + inits.exists { + case Init(Type.Apply(Type.Name("GraphQLSubquery"), _), _, _) => true + case _ => false + } + + private def isGraphQLSubqueryTyped(inits: List[Init]) = + inits.exists { + case Init(Type.Apply( + Type.Select(Term.Name("GraphQLSubquery"), Type.Name("Typed")), + _ + ), + _, + _ + ) => + true + case _ => false + } + override def fix(implicit doc: SemanticDocument): Patch = { val importPatch: List[IO[Patch]] = doc.tokens.collect { @@ -110,16 +148,7 @@ class GraphQLGen(config: GraphQLGenConfig) mods, objName, Template(early, inits, self, stats) - ) if inits.exists { - case Init( - Type.Apply(Type.Name("GraphQLOperation"), _) | - Type.Apply(Type.Select(Term.Name("GraphQLOperation"), Type.Name("Typed")), _), - _, - _ - ) => - true - case _ => false - } => + ) if isGraphQLOperation(inits) || isGraphQLOperationTyped(inits) => extractSchemaType(inits) match { case None => abort( @@ -147,23 +176,12 @@ class GraphQLGen(config: GraphQLGenConfig) ) >> IO { val operation = queryResult.toOption.get - val typed = inits.exists { - case Init( - Type.Apply( - Type.Select(Term.Name("GraphQLOperation"), Type.Name("Typed")), - _ - ), - _, - _ - ) => - true - case _ => false - } + val typed = isGraphQLOperationTyped(inits) val modObjDefs = if (typed) // everything is already defined identity[List[Stat]](_) - else // Modifications to add the missing definitions. + else // Modifications to add the missing definitions. scala.Function.chain( List( addImports(schemaType.value), @@ -196,16 +214,7 @@ class GraphQLGen(config: GraphQLGenConfig) mods @ GraphQLAnnotation(_), objName, Template(early, inits, self, stats) - ) if inits.exists { - case Init( - Type.Apply(Type.Name("GraphQLSubquery"), _) | - Type.Apply(Type.Select(Term.Name("GraphQLSubquery"), Type.Name("Typed")), _), - _, - _ - ) => - true - case _ => false - } => + ) if isGraphQLSubquery(inits) || isGraphQLSubqueryTyped(inits) => extractSchemaAndRootTypes(inits) match { case None => abort( @@ -233,18 +242,7 @@ class GraphQLGen(config: GraphQLGenConfig) ) >> IO { val operation = queryResult.toOption.get - val typed = inits.exists { - case Init( - Type.Apply( - Type.Select(Term.Name("GraphQLSubquery"), Type.Name("Typed")), - _ - ), - _, - _ - ) => - true - case _ => false - } + val typed = isGraphQLSubqueryTyped(inits) val modObjDefs = if (typed) // everything is already defined