From 5c2aadf159696af2c704e7f6547c0e0fd3ef7081 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Thu, 24 Aug 2023 15:41:17 +0000 Subject: [PATCH 1/3] Disable BSP for JS/Native --- build.sbt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/build.sbt b/build.sbt index f2f6e983..c2ce7882 100644 --- a/build.sbt +++ b/build.sbt @@ -121,6 +121,7 @@ lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform) libraryDependencies += "io.github.cquiroz" %%% "scala-java-time" % "2.5.0", scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule)) ) + .platformsSettings(JSPlatform, NativePlatform)(bspEnabled := false) lazy val circe = crossProject(JVMPlatform, JSPlatform, NativePlatform) .crossType(CrossType.Pure) @@ -132,6 +133,7 @@ lazy val circe = crossProject(JVMPlatform, JSPlatform, NativePlatform) .settings( name := "gsp-graphql-circe", ) + .platformsSettings(JSPlatform, NativePlatform)(bspEnabled := false) lazy val buildInfo = crossProject(JVMPlatform, JSPlatform, NativePlatform) .crossType(CrossType.Pure) @@ -141,6 +143,7 @@ lazy val buildInfo = crossProject(JVMPlatform, JSPlatform, NativePlatform) .settings( buildInfoKeys += "baseDirectory" -> (LocalRootProject / baseDirectory).value.toString ) + .platformsSettings(JSPlatform, NativePlatform)(bspEnabled := false) lazy val sql = crossProject(JVMPlatform, JSPlatform, NativePlatform) .crossType(CrossType.Full) @@ -166,6 +169,7 @@ lazy val sql = crossProject(JVMPlatform, JSPlatform, NativePlatform) "com.github.jnr" % "jnr-unixsocket" % jnrUnixsocketVersion % "test" ) ) + .platformsSettings(JSPlatform, NativePlatform)(bspEnabled := false) lazy val doobie = project .in(file("modules/doobie-pg")) @@ -206,6 +210,7 @@ lazy val skunk = crossProject(JVMPlatform, JSPlatform, NativePlatform) .jsSettings( scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule)) ) + .platformsSettings(JSPlatform, NativePlatform)(bspEnabled := false) lazy val generic = crossProject(JVMPlatform, JSPlatform, NativePlatform) .crossType(CrossType.Pure) @@ -222,6 +227,7 @@ lazy val generic = crossProject(JVMPlatform, JSPlatform, NativePlatform) case Scala2 => "com.chuusai" %%% "shapeless" % shapeless2Version }) ) + .platformsSettings(JSPlatform, NativePlatform)(bspEnabled := false) lazy val demo = project .in(file("demo")) From ff637a82a9f9375b5cea53db2fde8cd25b395f21 Mon Sep 17 00:00:00 2001 From: Rob Norris Date: Mon, 28 Aug 2023 11:33:59 -0500 Subject: [PATCH 2/3] allow opaque json mapping --- .../circe/src/main/scala/circemapping.scala | 71 ++++++++------ modules/circe/src/test/scala/Opaque.scala | 96 +++++++++++++++++++ .../shared/src/main/scala/SqlMapping.scala | 12 +-- 3 files changed, 144 insertions(+), 35 deletions(-) create mode 100644 modules/circe/src/test/scala/Opaque.scala diff --git a/modules/circe/src/main/scala/circemapping.scala b/modules/circe/src/main/scala/circemapping.scala index 0012b367..244ad820 100644 --- a/modules/circe/src/main/scala/circemapping.scala +++ b/modules/circe/src/main/scala/circemapping.scala @@ -23,55 +23,67 @@ trait CirceMappingLike[F[_]] extends Mapping[F] { // Syntax to allow Circe-specific root effects implicit class CirceMappingRootEffectSyntax(self: RootEffect.type) { - def computeJson(fieldName: String)(effect: (Query, Path, Env) => F[Result[Json]])(implicit pos: SourcePos): RootEffect = - self.computeCursor(fieldName)((q, p, e) => effect(q, p, e).map(_.map(circeCursor(p, e, _)))) + def computeJson(fieldName: String, opaque: Boolean = false)(effect: (Query, Path, Env) => F[Result[Json]])(implicit pos: SourcePos): RootEffect = + self.computeCursor(fieldName)((q, p, e) => effect(q, p, e).map(_.map(circeCursor(p, e, _, opaque)))) - def computeEncodable[A](fieldName: String)(effect: (Query, Path, Env) => F[Result[A]])(implicit pos: SourcePos, enc: Encoder[A]): RootEffect = - computeJson(fieldName)((q, p, e) => effect(q, p, e).map(_.map(enc(_)))) + def computeEncodable[A](fieldName: String, opaque: Boolean = false)(effect: (Query, Path, Env) => F[Result[A]])(implicit pos: SourcePos, enc: Encoder[A]): RootEffect = + computeJson(fieldName, opaque)((q, p, e) => effect(q, p, e).map(_.map(enc(_)))) } implicit class CirceMappingRootStreamSyntax(self: RootStream.type) { - def computeJson(fieldName: String)(effect: (Query, Path, Env) => Stream[F, Result[Json]])(implicit pos: SourcePos): RootStream = - self.computeCursor(fieldName)((q, p, e) => effect(q, p, e).map(_.map(circeCursor(p, e, _)))) + def computeJson(fieldName: String, opaque: Boolean = false)(effect: (Query, Path, Env) => Stream[F, Result[Json]])(implicit pos: SourcePos): RootStream = + self.computeCursor(fieldName)((q, p, e) => effect(q, p, e).map(_.map(circeCursor(p, e, _, opaque)))) - def computeEncodable[A](fieldName: String)(effect: (Query, Path, Env) => Stream[F, Result[A]])(implicit pos: SourcePos, enc: Encoder[A]): RootStream = - computeJson(fieldName)((q, p, e) => effect(q, p, e).map(_.map(enc(_)))) + def computeEncodable[A](fieldName: String, opaque: Boolean = false)(effect: (Query, Path, Env) => Stream[F, Result[A]])(implicit pos: SourcePos, enc: Encoder[A]): RootStream = + computeJson(fieldName, opaque)((q, p, e) => effect(q, p, e).map(_.map(enc(_)))) } - def circeCursor(path: Path, env: Env, value: Json): Cursor = + def circeCursor(path: Path, env: Env, value: Json, opaque: Boolean = false): Cursor = if(path.isRoot) - CirceCursor(Context(path.rootTpe), value, None, env) + CirceCursor(Context(path.rootTpe), value, None, env, opaque) else - DeferredCursor(path, (context, parent) => CirceCursor(context, value, Some(parent), env).success) + DeferredCursor(path, (context, parent) => CirceCursor(context, value, Some(parent), env, opaque).success) override def mkCursorForField(parent: Cursor, fieldName: String, resultName: Option[String]): Result[Cursor] = { val context = parent.context val fieldContext = context.forFieldOrAttribute(fieldName, resultName) - (fieldMapping(context, fieldName), parent.focus) match { - case (Some(CirceField(_, json, _)), _) => - CirceCursor(fieldContext, json, Some(parent), parent.env).success - case (Some(CursorFieldJson(_, f, _, _)), _) => - f(parent).map(res => CirceCursor(fieldContext, focus = res, parent = Some(parent), env = parent.env)) - case (None|Some(_: EffectMapping), json: Json) => - val f = json.asObject.flatMap(_(fieldName)) - f match { - case None if fieldContext.tpe.isNullable => CirceCursor(fieldContext, Json.Null, Some(parent), parent.env).success - case Some(json) => CirceCursor(fieldContext, json, Some(parent), parent.env).success - case _ => - Result.failure(s"No field '$fieldName' for type ${context.tpe}") - } + + // Create a cursor for the requested field by selecting it from `json`. + def jsonField(json: Json, opaque: Boolean): Result[Cursor] = { + val f = json.asObject.flatMap(_(fieldName)) + f match { + case None if fieldContext.tpe.isNullable => CirceCursor(fieldContext, Json.Null, Some(parent), parent.env, opaque).success + case Some(json) => CirceCursor(fieldContext, json, Some(parent), parent.env, opaque).success + case _ => Result.failure(s"No field '$fieldName' of type ${context.tpe} in json blob: ${json.noSpaces}") + } + } + + parent match { + // If the parent is a CirceCursor and this is an opaque cursor (i.e., the JSON is a terminal + // result and we mever consider other possible mappings once we're here) then immediately + // return a new cursor focused on the requested JSON field. Otherwise we drop through and + // delegate to the explicit type mapping for this field if there is one. + case CirceCursor(_, json, _, _ , true) => jsonField(json, true) case _ => - super.mkCursorForField(parent, fieldName, resultName) + (fieldMapping(context, fieldName), parent.focus) match { + case (Some(CirceField(_, json, _, opaque)), _) => + CirceCursor(fieldContext, json, Some(parent), parent.env, opaque).success + case (Some(CursorFieldJson(_, f, _, _, opaque)), _) => + f(parent).map(res => CirceCursor(fieldContext, focus = res, parent = Some(parent), env = parent.env, opaque)) + case (None|Some(_: EffectMapping), json: Json) => jsonField(json, false) + case _ => super.mkCursorForField(parent, fieldName, resultName) + } } + } sealed trait CirceFieldMapping extends FieldMapping { def withParent(tpe: Type): FieldMapping = this } - case class CirceField(fieldName: String, value: Json, hidden: Boolean = false)(implicit val pos: SourcePos) extends CirceFieldMapping + case class CirceField(fieldName: String, value: Json, hidden: Boolean = false, opaque: Boolean = false)(implicit val pos: SourcePos) extends CirceFieldMapping - case class CursorFieldJson(fieldName: String, f: Cursor => Result[Json], required: List[String], hidden: Boolean = false)( + case class CursorFieldJson(fieldName: String, f: Cursor => Result[Json], required: List[String], hidden: Boolean = false, opaque: Boolean = false)( implicit val pos: SourcePos ) extends CirceFieldMapping @@ -79,12 +91,13 @@ trait CirceMappingLike[F[_]] extends Mapping[F] { context: Context, focus: Json, parent: Option[Cursor], - env: Env + env: Env, + opaque: Boolean = false, ) extends Cursor { def withEnv(env0: Env): Cursor = copy(env = env.add(env0)) def mkChild(context: Context = context, focus: Json = focus): CirceCursor = - CirceCursor(context, focus, Some(this), Env.empty) + CirceCursor(context, focus, Some(this), Env.empty, opaque) def isLeaf: Boolean = tpe.dealias match { diff --git a/modules/circe/src/test/scala/Opaque.scala b/modules/circe/src/test/scala/Opaque.scala new file mode 100644 index 00000000..ade79832 --- /dev/null +++ b/modules/circe/src/test/scala/Opaque.scala @@ -0,0 +1,96 @@ +// Copyright (c) 2016-2020 Association of Universities for Research in Astronomy, Inc. (AURA) +// For license information see LICENSE or https://opensource.org/licenses/BSD-3-Clause + +package edu.gemini.grackle +package circetests + +import cats.effect.IO +import io.circe.Json +import io.circe.literal._ +import edu.gemini.grackle.circe.CirceMapping +import edu.gemini.grackle.syntax._ +import munit.CatsEffectSuite + +object OpaqueMapping extends CirceMapping[IO] { + + val schema = schema""" + scalar Foo + type Monkey { + name: Foo + } + type Barrel { + monkey: Monkey + } + type Query { + opaque: Barrel + notOpaque: Barrel + } + """ + + val QueryType = schema.ref("Query") + val MonkeyType = schema.ref("Monkey") + val BarrelType = schema.ref("Barrel") + val FooType = schema.ref("Foo") + + val typeMappings: List[TypeMapping] = + List( + ObjectMapping( + tpe = QueryType, + fieldMappings = + List( + CirceField("opaque", json"""{ "monkey": { "name": "Bob" }}""", opaque = true), + CirceField("notOpaque", json"""{ "monkey": { "name": "Bob" }}"""), + ) + ), + ObjectMapping( + tpe = MonkeyType, + fieldMappings = + List( + CirceField("name", Json.fromString("Steve")) + ) + ), + LeafMapping[String](FooType) + ) + +} + +final class OpaqueSuite extends CatsEffectSuite { + + test("Opaque field should not see explicit mapping.") { + + val query = """ + query { + opaque { + monkey { + name + } + } + notOpaque { + monkey { + name + } + } + } + """ + + val expected = json""" + { + "data" : { + "opaque" : { + "monkey" : { + "name" : "Bob" + } + }, + "notOpaque" : { + "monkey" : { + "name" : "Steve" + } + } + } + } + """ + + assertIO(OpaqueMapping.compileAndRunOne(query), expected) + + } +} diff --git a/modules/sql/shared/src/main/scala/SqlMapping.scala b/modules/sql/shared/src/main/scala/SqlMapping.scala index b397cead..22e0a1e0 100644 --- a/modules/sql/shared/src/main/scala/SqlMapping.scala +++ b/modules/sql/shared/src/main/scala/SqlMapping.scala @@ -718,10 +718,10 @@ trait SqlMappingLike[F[_]] extends CirceMappingLike[F] with SqlModule[F] { self val fieldContext = context.forFieldOrAttribute(fieldName, resultName) val fieldTpe = fieldContext.tpe (fieldMapping(context, fieldName), parent) match { - case (Some(_: SqlJson), sc: SqlCursor) => + case (Some(sj: SqlJson), sc: SqlCursor) => sc.asTable.flatMap { table => def mkCirceCursor(f: Json): Result[Cursor] = - CirceCursor(fieldContext, focus = f, parent = Some(parent), env = parent.env).success + CirceCursor(fieldContext, focus = f, parent = Some(parent), env = parent.env, opaque = sj.opaque).success sc.mapped.selectAtomicField(context, fieldName, table).flatMap(_ match { case Some(j: Json) if fieldTpe.isNullable => mkCirceCursor(j) case None => mkCirceCursor(Json.Null) @@ -776,7 +776,7 @@ trait SqlMappingLike[F[_]] extends CirceMappingLike[F] with SqlModule[F] { self def apply(fieldName: String, joins: Join*): SqlObject = apply(fieldName, joins.toList) } - case class SqlJson(fieldName: String, columnRef: ColumnRef)( + case class SqlJson(fieldName: String, columnRef: ColumnRef, opaque: Boolean = false)( implicit val pos: SourcePos ) extends SqlFieldMapping { def hidden: Boolean = false @@ -868,8 +868,8 @@ trait SqlMappingLike[F[_]] extends CirceMappingLike[F] with SqlModule[F] { self def columnsForLeaf(context: Context, fieldName: String): Result[List[SqlColumn]] = fieldMapping(context, fieldName) match { case Some(SqlField(_, cr, _, _, _, _)) => List(SqlColumn.TableColumn(context, cr, fieldName :: context.resultPath)).success - case Some(SqlJson(_, cr)) => List(SqlColumn.TableColumn(context, cr, fieldName :: context.resultPath)).success - case Some(CursorFieldJson(_, _, required, _)) => + case Some(SqlJson(_, cr, _)) => List(SqlColumn.TableColumn(context, cr, fieldName :: context.resultPath)).success + case Some(CursorFieldJson(_, _, required, _, _)) => required.flatTraverse(r => columnsForLeaf(context, r)) case Some(CursorField(_, _, _, required, _)) => required.flatTraverse(r => columnsForLeaf(context, r)) @@ -896,7 +896,7 @@ trait SqlMappingLike[F[_]] extends CirceMappingLike[F] with SqlModule[F] { self def columnForAtomicField(context: Context, fieldName: String): Result[SqlColumn] = { fieldMapping(context, fieldName) match { case Some(SqlField(_, cr, _, _, _, _)) => SqlColumn.TableColumn(context, cr, fieldName :: context.resultPath).success - case Some(SqlJson(_, cr)) => SqlColumn.TableColumn(context, cr, fieldName :: context.resultPath).success + case Some(SqlJson(_, cr, _)) => SqlColumn.TableColumn(context, cr, fieldName :: context.resultPath).success case _ => Result.internalError(s"No column for atomic field '$fieldName' in context $context") } } From 1ff67f00c23d13fe78a647873f48c50de473bd45 Mon Sep 17 00:00:00 2001 From: Rob Norris Date: Mon, 28 Aug 2023 12:00:29 -0500 Subject: [PATCH 3/3] undo build changes --- build.sbt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/build.sbt b/build.sbt index c2ce7882..f2f6e983 100644 --- a/build.sbt +++ b/build.sbt @@ -121,7 +121,6 @@ lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform) libraryDependencies += "io.github.cquiroz" %%% "scala-java-time" % "2.5.0", scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule)) ) - .platformsSettings(JSPlatform, NativePlatform)(bspEnabled := false) lazy val circe = crossProject(JVMPlatform, JSPlatform, NativePlatform) .crossType(CrossType.Pure) @@ -133,7 +132,6 @@ lazy val circe = crossProject(JVMPlatform, JSPlatform, NativePlatform) .settings( name := "gsp-graphql-circe", ) - .platformsSettings(JSPlatform, NativePlatform)(bspEnabled := false) lazy val buildInfo = crossProject(JVMPlatform, JSPlatform, NativePlatform) .crossType(CrossType.Pure) @@ -143,7 +141,6 @@ lazy val buildInfo = crossProject(JVMPlatform, JSPlatform, NativePlatform) .settings( buildInfoKeys += "baseDirectory" -> (LocalRootProject / baseDirectory).value.toString ) - .platformsSettings(JSPlatform, NativePlatform)(bspEnabled := false) lazy val sql = crossProject(JVMPlatform, JSPlatform, NativePlatform) .crossType(CrossType.Full) @@ -169,7 +166,6 @@ lazy val sql = crossProject(JVMPlatform, JSPlatform, NativePlatform) "com.github.jnr" % "jnr-unixsocket" % jnrUnixsocketVersion % "test" ) ) - .platformsSettings(JSPlatform, NativePlatform)(bspEnabled := false) lazy val doobie = project .in(file("modules/doobie-pg")) @@ -210,7 +206,6 @@ lazy val skunk = crossProject(JVMPlatform, JSPlatform, NativePlatform) .jsSettings( scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule)) ) - .platformsSettings(JSPlatform, NativePlatform)(bspEnabled := false) lazy val generic = crossProject(JVMPlatform, JSPlatform, NativePlatform) .crossType(CrossType.Pure) @@ -227,7 +222,6 @@ lazy val generic = crossProject(JVMPlatform, JSPlatform, NativePlatform) case Scala2 => "com.chuusai" %%% "shapeless" % shapeless2Version }) ) - .platformsSettings(JSPlatform, NativePlatform)(bspEnabled := false) lazy val demo = project .in(file("demo"))