From 378ef0d05de627edc70b2a572129244dc7be2594 Mon Sep 17 00:00:00 2001 From: Paul Daniels Date: Tue, 21 May 2024 15:09:54 +0800 Subject: [PATCH] add subgraph compatibility check (#2234) * add subgraph compatibility check * fix schema resolution * fix docker image * fix graphql path * fix project name * try with graalvm * prebuild repo for faster startup * remove readline * revert accidental changes, disable assembly outside of compat * remove compat build from 2.12 and 3 * use RC7 methods * try enabling scala3 * Update apollo-compatibility/README.md Co-authored-by: Pierre Ricadat --------- Co-authored-by: Pierre Ricadat --- .github/workflows/compatibility.yaml | 41 +++++++ apollo-compatibility/Dockerfile | 7 ++ apollo-compatibility/README.md | 18 +++ apollo-compatibility/docker-compose.yaml | 7 ++ apollo-compatibility/products.graphql | 83 ++++++++++++++ .../src/main/scala/Main.scala | 35 ++++++ .../src/main/scala/ProductSchema.scala | 103 ++++++++++++++++++ .../src/main/scala/models/CaseStudy.scala | 12 ++ .../src/main/scala/models/CaseStudyArgs.scala | 10 ++ .../src/main/scala/models/Custom.scala | 6 + .../main/scala/models/DeprecatedProduct.scala | 19 ++++ .../scala/models/DeprecatedProductArgs.scala | 13 +++ .../src/main/scala/models/ID.scala | 11 ++ .../src/main/scala/models/Inventory.scala | 16 +++ .../src/main/scala/models/InventoryArgs.scala | 10 ++ .../src/main/scala/models/MyFederation.scala | 14 +++ .../src/main/scala/models/Product.scala | 25 +++++ .../src/main/scala/models/ProductArgs.scala | 29 +++++ .../main/scala/models/ProductDimension.scala | 14 +++ .../main/scala/models/ProductResearch.scala | 13 +++ .../scala/models/ProductResearchArgs.scala | 10 ++ .../main/scala/models/ProductVariation.scala | 11 ++ .../main/scala/models/QueryProductArgs.scala | 10 ++ .../src/main/scala/models/User.scala | 17 +++ .../src/main/scala/models/UserArgs.scala | 10 ++ .../src/main/scala/models/package.scala | 1 + .../scala/services/InventoryService.scala | 30 +++++ .../main/scala/services/ProductService.scala | 83 ++++++++++++++ .../src/main/scala/services/UserService.scala | 23 ++++ build.sbt | 60 +++++++++- project/plugins.sbt | 1 + 31 files changed, 739 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/compatibility.yaml create mode 100644 apollo-compatibility/Dockerfile create mode 100644 apollo-compatibility/README.md create mode 100644 apollo-compatibility/docker-compose.yaml create mode 100644 apollo-compatibility/products.graphql create mode 100644 apollo-compatibility/src/main/scala/Main.scala create mode 100644 apollo-compatibility/src/main/scala/ProductSchema.scala create mode 100644 apollo-compatibility/src/main/scala/models/CaseStudy.scala create mode 100644 apollo-compatibility/src/main/scala/models/CaseStudyArgs.scala create mode 100644 apollo-compatibility/src/main/scala/models/Custom.scala create mode 100644 apollo-compatibility/src/main/scala/models/DeprecatedProduct.scala create mode 100644 apollo-compatibility/src/main/scala/models/DeprecatedProductArgs.scala create mode 100644 apollo-compatibility/src/main/scala/models/ID.scala create mode 100644 apollo-compatibility/src/main/scala/models/Inventory.scala create mode 100644 apollo-compatibility/src/main/scala/models/InventoryArgs.scala create mode 100644 apollo-compatibility/src/main/scala/models/MyFederation.scala create mode 100644 apollo-compatibility/src/main/scala/models/Product.scala create mode 100644 apollo-compatibility/src/main/scala/models/ProductArgs.scala create mode 100644 apollo-compatibility/src/main/scala/models/ProductDimension.scala create mode 100644 apollo-compatibility/src/main/scala/models/ProductResearch.scala create mode 100644 apollo-compatibility/src/main/scala/models/ProductResearchArgs.scala create mode 100644 apollo-compatibility/src/main/scala/models/ProductVariation.scala create mode 100644 apollo-compatibility/src/main/scala/models/QueryProductArgs.scala create mode 100644 apollo-compatibility/src/main/scala/models/User.scala create mode 100644 apollo-compatibility/src/main/scala/models/UserArgs.scala create mode 100644 apollo-compatibility/src/main/scala/models/package.scala create mode 100644 apollo-compatibility/src/main/scala/services/InventoryService.scala create mode 100644 apollo-compatibility/src/main/scala/services/ProductService.scala create mode 100644 apollo-compatibility/src/main/scala/services/UserService.scala diff --git a/.github/workflows/compatibility.yaml b/.github/workflows/compatibility.yaml new file mode 100644 index 0000000000..c0c3eb5658 --- /dev/null +++ b/.github/workflows/compatibility.yaml @@ -0,0 +1,41 @@ +name: Federation Specification Compatibility Test + +on: + pull_request: + branches: + - series/2.x + +jobs: + compatibility: + runs-on: ubuntu-latest + steps: + - name: Checkout current branch + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Set up Java + uses: actions/setup-java@v3 + with: + java-version: 17 + distribution: 'adopt' + cache: 'sbt' + - name: Cache scala dependencies + uses: coursier/cache-action@v6 + - name: Run assembly + run: sbt "apolloCompatibility/assembly" + + - name: Compatibility Test + uses: apollographql/federation-subgraph-compatibility@v2 + with: + compose: 'apollo-compatibility/docker-compose.yaml' + schema: 'apollo-compatibility/products.graphql' + path: '/graphql' + port: 4001 + debug: true + token: ${{ secrets.GITHUB_TOKEN }} + # Boolean flag to indicate whether any failing test should fail the script + failOnWarning: false + # Boolean flag to indicate whether any required test should fail the script + failOnRequired: false + # Working directory to run the test from + workingDirectory: '' \ No newline at end of file diff --git a/apollo-compatibility/Dockerfile b/apollo-compatibility/Dockerfile new file mode 100644 index 0000000000..cab242f548 --- /dev/null +++ b/apollo-compatibility/Dockerfile @@ -0,0 +1,7 @@ +FROM sbtscala/scala-sbt:graalvm-ce-22.3.3-b1-java17_1.9.8_2.13.12 AS build + +WORKDIR /app +COPY build.sbt . +COPY apollo-compatibility/target/apollo-subgraph-compatibility.jar /app/artifact.jar +EXPOSE 4001 +CMD java $* -jar artifact.jar \ No newline at end of file diff --git a/apollo-compatibility/README.md b/apollo-compatibility/README.md new file mode 100644 index 0000000000..755902ea64 --- /dev/null +++ b/apollo-compatibility/README.md @@ -0,0 +1,18 @@ +# Federated subgraph to test apollo federation spec compatibility + +Implementation of a federated subgraph aligned to the requirements outlined in [apollo-federation-subgraph-compatibility](https://github.com/apollographql/apollo-federation-subgraph-compatibility). + +The subgraph can be used to verify compability against [Apollo Federation Subgraph Specification](https://www.apollographql.com/docs/federation/subgraph-spec). + +### Run compatibility tests +Execute the following command from the root of the repo + +``` +npx @apollo/federation-subgraph-compatibility docker --compose apollo-compatibility/docker-compose.yml --schema apollo-compatibility/schema.graphql +``` + +### Printing the GraphQL Schema (SDL) + +``` +sbt "apollo-compability/run printSchema" +``` \ No newline at end of file diff --git a/apollo-compatibility/docker-compose.yaml b/apollo-compatibility/docker-compose.yaml new file mode 100644 index 0000000000..deb5596a50 --- /dev/null +++ b/apollo-compatibility/docker-compose.yaml @@ -0,0 +1,7 @@ +services: + products: + build: + context: . + dockerfile: ./apollo-compatibility/Dockerfile + ports: + - 4001:4001 \ No newline at end of file diff --git a/apollo-compatibility/products.graphql b/apollo-compatibility/products.graphql new file mode 100644 index 0000000000..79b4848fa0 --- /dev/null +++ b/apollo-compatibility/products.graphql @@ -0,0 +1,83 @@ +extend schema +@link( + url: "https://specs.apollo.dev/federation/v2.3" + import: [ + "@composeDirective" + "@extends" + "@external" + "@key" + "@inaccessible" + "@interfaceObject" + "@override" + "@provides" + "@requires" + "@shareable" + "@tag" + ] +) +@link(url: "https://myspecs.dev/myCustomDirective/v1.0", import: ["@custom"]) +@composeDirective(name: "@custom") + +directive @custom on OBJECT + +type Product +@custom +@key(fields: "id") +@key(fields: "sku package") +@key(fields: "sku variation { id }") { + id: ID! + sku: String + package: String + variation: ProductVariation + dimensions: ProductDimension + createdBy: User @provides(fields: "totalProductsCreated") + notes: String @tag(name: "internal") + research: [ProductResearch!]! +} + +type DeprecatedProduct @key(fields: "sku package") { + sku: String! + package: String! + reason: String + createdBy: User +} + +type ProductVariation { + id: ID! +} + +type ProductResearch @key(fields: "study { caseNumber }") { + study: CaseStudy! + outcome: String +} + +type CaseStudy { + caseNumber: ID! + description: String +} + +type ProductDimension @shareable { + size: String + weight: Float + unit: String @inaccessible +} + +extend type Query { + product(id: ID!): Product + deprecatedProduct(sku: String!, package: String!): DeprecatedProduct + @deprecated(reason: "Use product query instead") +} + +extend type User @key(fields: "email") { + averageProductsCreatedPerYear: Int + @requires(fields: "totalProductsCreated yearsOfEmployment") + email: ID! @external + name: String @override(from: "users") + totalProductsCreated: Int @external + yearsOfEmployment: Int! @external +} + +type Inventory @interfaceObject @key(fields: "id") { + id: ID! + deprecatedProducts: [DeprecatedProduct!]! +} \ No newline at end of file diff --git a/apollo-compatibility/src/main/scala/Main.scala b/apollo-compatibility/src/main/scala/Main.scala new file mode 100644 index 0000000000..cab34e17f2 --- /dev/null +++ b/apollo-compatibility/src/main/scala/Main.scala @@ -0,0 +1,35 @@ +import caliban.CalibanError +import zio._ +import caliban.quick._ +import services.{ InventoryService, ProductService, UserService } +import zio.http.{ Response, Routes, Server } + +object Main extends ZIOAppDefault { + + def run = for { + args <- ZIOAppArgs.getArgs + _ <- (args match { + case Chunk("printSchema") => printSchema + case _ => runServer + }) + } yield () + + private val printSchema = Console.printLine(ProductSchema.print) + + private val runServer = { + val routes: Task[Routes[ProductService with UserService with InventoryService, Response]] = + ProductSchema.api.routes("/graphql") + + val server: ZIO[ProductService with UserService with InventoryService with Server, Throwable, Response] = + routes.flatMap(Server.serve(_)) + + server.orDie + .provide( + Server.defaultWithPort(4001), + ProductService.inMemory, + UserService.inMemory, + InventoryService.inMemory + ) + } + +} diff --git a/apollo-compatibility/src/main/scala/ProductSchema.scala b/apollo-compatibility/src/main/scala/ProductSchema.scala new file mode 100644 index 0000000000..c602d0731a --- /dev/null +++ b/apollo-compatibility/src/main/scala/ProductSchema.scala @@ -0,0 +1,103 @@ +import caliban._ +import caliban.federation.EntityResolver +import caliban.federation.tracing.ApolloFederatedTracing +import caliban.introspection.adt.{ __Directive, __DirectiveLocation } +import caliban.schema.Annotations.GQLDeprecated +import caliban.schema.{ GenericSchema, Schema } +import models._ +import services.{ InventoryService, ProductService, UserService } +import zio.query.ZQuery +import zio.{ URIO, ZIO } + +case class Query( + product: QueryProductArgs => URIO[ProductService, Option[models.Product]], + @GQLDeprecated("Use product query instead") deprecatedProduct: DeprecatedProductArgs => URIO[ + ProductService, + Option[DeprecatedProduct] + ] +) + +object Query { + object apiSchema extends GenericSchema[ProductService with UserService] + implicit val schema: Schema[ProductService with UserService, Query] = apiSchema.gen +} + +object ProductSchema extends GenericSchema[ProductService with UserService] { + val productResolver: EntityResolver[ProductService with UserService] = + EntityResolver[ProductService with UserService, ProductArgs, models.Product] { + case ProductArgs.IdOnly(id) => + ZQuery.serviceWithZIO[ProductService](_.getProductById(id.id)) + case ProductArgs.SkuAndPackage(sku, p) => + ZQuery.serviceWithZIO[ProductService](_.getProductBySkuAndPackage(sku, p)) + case ProductArgs.SkuAndVariationId(sku, variation) => + ZQuery.serviceWithZIO[ProductService](_.getProductBySkuAndVariationId(sku, variation.id.id)) + } + + val userResolver: EntityResolver[UserService with ProductService] = + EntityResolver[UserService with ProductService, UserArgs, User] { args => + ZQuery.serviceWithZIO[UserService](_.getUser) + } + + val productResearchResolver: EntityResolver[UserService with ProductService] = + EntityResolver.from[ProductResearchArgs] { args => + ZQuery.some( + ProductResearch( + CaseStudy(caseNumber = args.study.caseNumber, Some("Federation Study")), + None + ) + ) + } + + val deprecatedProductResolver: EntityResolver[ProductService with UserService] = + EntityResolver[ProductService with UserService, DeprecatedProductArgs, DeprecatedProduct] { args => + ZQuery.some( + models.DeprecatedProduct( + sku = "apollo-federation-v1", + `package` = "@apollo/federation-v1", + reason = Some("Migrate to Federation V2"), + createdBy = ZIO.serviceWithZIO[UserService](_.getUser) + ) + ) + } + + val inventoryResolver: EntityResolver[InventoryService with UserService] = + EntityResolver[InventoryService with UserService, InventoryArgs, Inventory] { args => + ZQuery.serviceWith[InventoryService](_.getById(args.id.id)) + } + + val api: GraphQL[ProductService with UserService with InventoryService] = + graphQL( + RootResolver( + Query( + args => ZIO.serviceWithZIO[ProductService](_.getProductById(args.id.id)), + args => + ZIO.some( + models.DeprecatedProduct( + sku = "apollo-federation-v1", + `package` = "@apollo/federation-v1", + reason = Some("Migrate to Federation V2"), + createdBy = ZIO.serviceWithZIO[UserService](_.getUser) + ) + ) + ) + ), + directives = List( + __Directive( + "custom", + None, + Set(__DirectiveLocation.OBJECT), + _ => Nil, + isRepeatable = false + ) + ) + ) @@ federated( + productResolver, + userResolver, + productResearchResolver, + deprecatedProductResolver, + inventoryResolver + ) @@ ApolloFederatedTracing.wrapper() + + val print = api.render + +} diff --git a/apollo-compatibility/src/main/scala/models/CaseStudy.scala b/apollo-compatibility/src/main/scala/models/CaseStudy.scala new file mode 100644 index 0000000000..573245d9f2 --- /dev/null +++ b/apollo-compatibility/src/main/scala/models/CaseStudy.scala @@ -0,0 +1,12 @@ +package models + +import caliban.schema.Schema + +case class CaseStudy( + caseNumber: ID, + description: Option[String] +) + +object CaseStudy { + implicit val schema: Schema[Any, CaseStudy] = Schema.gen +} diff --git a/apollo-compatibility/src/main/scala/models/CaseStudyArgs.scala b/apollo-compatibility/src/main/scala/models/CaseStudyArgs.scala new file mode 100644 index 0000000000..eb056ecb61 --- /dev/null +++ b/apollo-compatibility/src/main/scala/models/CaseStudyArgs.scala @@ -0,0 +1,10 @@ +package models + +import caliban.schema.{ ArgBuilder, Schema } + +case class CaseStudyArgs(caseNumber: ID) + +object CaseStudyArgs { + implicit val schema: Schema[Any, CaseStudyArgs] = Schema.gen + implicit val argBuilder: ArgBuilder[CaseStudyArgs] = ArgBuilder.gen +} diff --git a/apollo-compatibility/src/main/scala/models/Custom.scala b/apollo-compatibility/src/main/scala/models/Custom.scala new file mode 100644 index 0000000000..8ad77d2b9e --- /dev/null +++ b/apollo-compatibility/src/main/scala/models/Custom.scala @@ -0,0 +1,6 @@ +package models + +import caliban.parsing.adt.Directive +import caliban.schema.Annotations.GQLDirective + +case class Custom() extends GQLDirective(Directive("custom")) diff --git a/apollo-compatibility/src/main/scala/models/DeprecatedProduct.scala b/apollo-compatibility/src/main/scala/models/DeprecatedProduct.scala new file mode 100644 index 0000000000..9bf6bcbbd9 --- /dev/null +++ b/apollo-compatibility/src/main/scala/models/DeprecatedProduct.scala @@ -0,0 +1,19 @@ +package models + +import caliban.schema.{ GenericSchema, Schema } +import services.UserService +import zio.URIO + +@GQLKey("sku package") +case class DeprecatedProduct( + sku: String, + `package`: String, + reason: Option[String], + createdBy: URIO[UserService, Option[User]] +) + +object DeprecatedProduct { + object apiSchema extends GenericSchema[UserService] + + implicit val schema: Schema[UserService, DeprecatedProduct] = apiSchema.gen[UserService, DeprecatedProduct] +} diff --git a/apollo-compatibility/src/main/scala/models/DeprecatedProductArgs.scala b/apollo-compatibility/src/main/scala/models/DeprecatedProductArgs.scala new file mode 100644 index 0000000000..bc005b22bd --- /dev/null +++ b/apollo-compatibility/src/main/scala/models/DeprecatedProductArgs.scala @@ -0,0 +1,13 @@ +package models + +import caliban.schema.{ ArgBuilder, Schema } + +case class DeprecatedProductArgs( + sku: String, + `package`: String +) + +object DeprecatedProductArgs { + implicit val schema: Schema[Any, DeprecatedProductArgs] = Schema.gen + implicit val argBuilder: ArgBuilder[DeprecatedProductArgs] = ArgBuilder.gen[DeprecatedProductArgs] +} diff --git a/apollo-compatibility/src/main/scala/models/ID.scala b/apollo-compatibility/src/main/scala/models/ID.scala new file mode 100644 index 0000000000..59404f6fe4 --- /dev/null +++ b/apollo-compatibility/src/main/scala/models/ID.scala @@ -0,0 +1,11 @@ +package models + +import caliban.schema.{ ArgBuilder, Schema } +import caliban.Value.StringValue + +case class ID(id: String) extends AnyVal + +object ID { + implicit val schema: Schema[Any, ID] = Schema.scalarSchema[ID]("ID", None, None, None, id => StringValue(id.id)) + implicit val argBuilder: ArgBuilder[ID] = ArgBuilder.string.map(ID(_)) +} diff --git a/apollo-compatibility/src/main/scala/models/Inventory.scala b/apollo-compatibility/src/main/scala/models/Inventory.scala new file mode 100644 index 0000000000..582792d376 --- /dev/null +++ b/apollo-compatibility/src/main/scala/models/Inventory.scala @@ -0,0 +1,16 @@ +package models + +import caliban.schema.{ GenericSchema, Schema } +import services.{ InventoryService, UserService } + +@GQLInterfaceObject +@GQLKey("email") +case class Inventory( + id: ID, + deprecatedProducts: List[DeprecatedProduct] +) + +object Inventory { + object genSchema extends GenericSchema[InventoryService with UserService] + implicit val schema: Schema[InventoryService with UserService, Inventory] = genSchema.gen +} diff --git a/apollo-compatibility/src/main/scala/models/InventoryArgs.scala b/apollo-compatibility/src/main/scala/models/InventoryArgs.scala new file mode 100644 index 0000000000..f672a6d9de --- /dev/null +++ b/apollo-compatibility/src/main/scala/models/InventoryArgs.scala @@ -0,0 +1,10 @@ +package models + +import caliban.schema.{ ArgBuilder, Schema } + +case class InventoryArgs(id: ID) + +object InventoryArgs { + implicit val schema: Schema[Any, InventoryArgs] = Schema.gen + implicit val argBuilder: ArgBuilder[InventoryArgs] = ArgBuilder.gen +} diff --git a/apollo-compatibility/src/main/scala/models/MyFederation.scala b/apollo-compatibility/src/main/scala/models/MyFederation.scala new file mode 100644 index 0000000000..142c66a696 --- /dev/null +++ b/apollo-compatibility/src/main/scala/models/MyFederation.scala @@ -0,0 +1,14 @@ +package models + +import caliban.federation.v2x._ + +abstract class MyFederation + extends caliban.federation.v2x.FederationV2( + Versions.v2_3 :: Link( + "https://myspecs.dev/myCustomDirective/v1.0", + List( + Import("@custom") + ) + ) :: ComposeDirective("@custom") :: Nil + ) + with FederationDirectivesV2_3 diff --git a/apollo-compatibility/src/main/scala/models/Product.scala b/apollo-compatibility/src/main/scala/models/Product.scala new file mode 100644 index 0000000000..acfea6a0cd --- /dev/null +++ b/apollo-compatibility/src/main/scala/models/Product.scala @@ -0,0 +1,25 @@ +package models + +import caliban.schema.Schema +import zio.UIO + +@GQLKey("id") +@GQLKey("sku package") +@GQLKey("sku variation { id }") +@Custom +case class Product( + id: ID, + sku: Option[String], + `package`: Option[String], + variation: Option[ProductVariation], + dimensions: Option[ProductDimension], + @GQLProvides("totalProductsCreated") createdBy: UIO[Option[User]], + @GQLTag("internal") notes: Option[String], + research: List[ProductResearch] +) + +object Product { + + implicit val schema: Schema[Any, Product] = Schema.gen + +} diff --git a/apollo-compatibility/src/main/scala/models/ProductArgs.scala b/apollo-compatibility/src/main/scala/models/ProductArgs.scala new file mode 100644 index 0000000000..94eb063a17 --- /dev/null +++ b/apollo-compatibility/src/main/scala/models/ProductArgs.scala @@ -0,0 +1,29 @@ +package models + +import caliban.InputValue +import caliban.schema.{ ArgBuilder, Schema } + +sealed trait ProductArgs + +object ProductArgs { + case class IdOnly(id: ID) extends ProductArgs + case class SkuAndPackage(sku: String, `package`: String) extends ProductArgs + case class SkuAndVariationId(sku: String, variation: ProductVariation) extends ProductArgs + + private implicit val variationArgs: ArgBuilder[ProductVariation] = ArgBuilder.gen[ProductVariation] + val idOnlyArgBuilder: ArgBuilder[IdOnly] = ArgBuilder.gen[IdOnly] + val skuAndPackageArgBuilder: ArgBuilder[SkuAndPackage] = ArgBuilder.gen[SkuAndPackage] + val skuAndVariationIdArgBuilder: ArgBuilder[SkuAndVariationId] = ArgBuilder.gen[SkuAndVariationId] + + implicit val argBuilder: ArgBuilder[ProductArgs] = (input: InputValue) => + (for { + error <- skuAndVariationIdArgBuilder.build(input).swap + _ <- skuAndPackageArgBuilder.build(input).swap + _ <- idOnlyArgBuilder.build(input).swap + } yield error).swap + + implicit val idOnlySchema: Schema[Any, ProductArgs.IdOnly] = Schema.gen + implicit val skuAndPackageSchema: Schema[Any, ProductArgs.SkuAndPackage] = Schema.gen + implicit val skuAndVariationIdSchema: Schema[Any, ProductArgs.SkuAndVariationId] = Schema.gen + +} diff --git a/apollo-compatibility/src/main/scala/models/ProductDimension.scala b/apollo-compatibility/src/main/scala/models/ProductDimension.scala new file mode 100644 index 0000000000..d5110051bf --- /dev/null +++ b/apollo-compatibility/src/main/scala/models/ProductDimension.scala @@ -0,0 +1,14 @@ +package models + +import caliban.schema.Schema + +@GQLShareable +case class ProductDimension( + size: Option[String], + weight: Option[Float], + @GQLInaccessible unit: Option[String] +) + +object ProductDimension { + implicit val schema: Schema[Any, ProductDimension] = Schema.gen +} diff --git a/apollo-compatibility/src/main/scala/models/ProductResearch.scala b/apollo-compatibility/src/main/scala/models/ProductResearch.scala new file mode 100644 index 0000000000..72cb7b30f5 --- /dev/null +++ b/apollo-compatibility/src/main/scala/models/ProductResearch.scala @@ -0,0 +1,13 @@ +package models + +import caliban.schema.Schema + +@GQLKey("study { caseNumber }") +case class ProductResearch( + study: CaseStudy, + outcome: Option[String] +) + +object ProductResearch { + implicit val schema: Schema[Any, ProductResearch] = Schema.gen +} diff --git a/apollo-compatibility/src/main/scala/models/ProductResearchArgs.scala b/apollo-compatibility/src/main/scala/models/ProductResearchArgs.scala new file mode 100644 index 0000000000..8e6a67897d --- /dev/null +++ b/apollo-compatibility/src/main/scala/models/ProductResearchArgs.scala @@ -0,0 +1,10 @@ +package models + +import caliban.schema.{ ArgBuilder, Schema } + +case class ProductResearchArgs(study: CaseStudyArgs) + +object ProductResearchArgs { + implicit val schema: Schema[Any, ProductResearchArgs] = Schema.gen + implicit val argBuilder: ArgBuilder[ProductResearchArgs] = ArgBuilder.gen +} diff --git a/apollo-compatibility/src/main/scala/models/ProductVariation.scala b/apollo-compatibility/src/main/scala/models/ProductVariation.scala new file mode 100644 index 0000000000..a97e716a0b --- /dev/null +++ b/apollo-compatibility/src/main/scala/models/ProductVariation.scala @@ -0,0 +1,11 @@ +package models + +import caliban.schema.Schema + +case class ProductVariation( + id: ID +) + +object ProductVariation { + implicit val schema: Schema[Any, ProductVariation] = Schema.gen +} diff --git a/apollo-compatibility/src/main/scala/models/QueryProductArgs.scala b/apollo-compatibility/src/main/scala/models/QueryProductArgs.scala new file mode 100644 index 0000000000..73982bfac5 --- /dev/null +++ b/apollo-compatibility/src/main/scala/models/QueryProductArgs.scala @@ -0,0 +1,10 @@ +package models + +import caliban.schema.{ ArgBuilder, Schema } + +case class QueryProductArgs(id: ID) + +object QueryProductArgs { + implicit val argBuilder: ArgBuilder[QueryProductArgs] = ArgBuilder.gen + implicit val schema: Schema[Any, QueryProductArgs] = Schema.gen +} diff --git a/apollo-compatibility/src/main/scala/models/User.scala b/apollo-compatibility/src/main/scala/models/User.scala new file mode 100644 index 0000000000..8b1d30e996 --- /dev/null +++ b/apollo-compatibility/src/main/scala/models/User.scala @@ -0,0 +1,17 @@ +package models + +import caliban.schema.Schema + +@GQLKey("email") +@GQLExtend +case class User( + @GQLExternal email: ID, + @GQLExternal totalProductsCreated: Option[Int], + @GQLOverride("users") name: Option[String], + @GQLRequires("totalProductsCreated yearsOfEmployment") averageProductsCreatedPerYear: Option[Int], + @GQLExternal yearsOfEmployment: Int +) + +object User { + implicit val schema: Schema[Any, User] = Schema.gen +} diff --git a/apollo-compatibility/src/main/scala/models/UserArgs.scala b/apollo-compatibility/src/main/scala/models/UserArgs.scala new file mode 100644 index 0000000000..27ef3319bf --- /dev/null +++ b/apollo-compatibility/src/main/scala/models/UserArgs.scala @@ -0,0 +1,10 @@ +package models + +import caliban.schema.{ ArgBuilder, Schema } + +case class UserArgs(email: ID) + +object UserArgs { + implicit val schema: Schema[Any, UserArgs] = Schema.gen + implicit val argBuilder: ArgBuilder[UserArgs] = ArgBuilder.gen +} diff --git a/apollo-compatibility/src/main/scala/models/package.scala b/apollo-compatibility/src/main/scala/models/package.scala new file mode 100644 index 0000000000..0a8d098dcd --- /dev/null +++ b/apollo-compatibility/src/main/scala/models/package.scala @@ -0,0 +1 @@ +package object models extends MyFederation diff --git a/apollo-compatibility/src/main/scala/services/InventoryService.scala b/apollo-compatibility/src/main/scala/services/InventoryService.scala new file mode 100644 index 0000000000..a60ce6d164 --- /dev/null +++ b/apollo-compatibility/src/main/scala/services/InventoryService.scala @@ -0,0 +1,30 @@ +package services + +import models.{ DeprecatedProduct, ID, Inventory } +import zio.{ ULayer, ZIO, ZLayer } + +trait InventoryService { + + def getById(id: String): Option[Inventory] + +} + +object InventoryService { + val inventory = List( + Inventory( + id = ID("apollo-oss"), + deprecatedProducts = List( + DeprecatedProduct( + sku = "apollo-federation-v1", + `package` = "@apollo/federation-v1", + reason = Some("Migrate to Federation V2"), + createdBy = ZIO.serviceWithZIO[UserService](_.getUser) + ) + ) + ) + ) + + val inMemory: ULayer[InventoryService] = ZLayer.succeed(new InventoryService { + def getById(id: String): Option[Inventory] = inventory.find(_.id.id == id) + }) +} diff --git a/apollo-compatibility/src/main/scala/services/ProductService.scala b/apollo-compatibility/src/main/scala/services/ProductService.scala new file mode 100644 index 0000000000..d6e828381a --- /dev/null +++ b/apollo-compatibility/src/main/scala/services/ProductService.scala @@ -0,0 +1,83 @@ +package services + +import zio.{ Ref, UIO, ZIO, ZLayer } + +trait ProductService { + def getProductById(id: String): UIO[Option[models.Product]] + def getProductBySkuAndPackage(sku: String, pack: String): UIO[Option[models.Product]] + def getProductBySkuAndVariationId(sku: String, variationId: String): UIO[Option[models.Product]] +} + +object ProductService { + val productsResearch = List( + models.ProductResearch( + study = models.CaseStudy(models.ID("1234"), Some("Federation Study")), + outcome = None + ), + models.ProductResearch( + study = models.CaseStudy(models.ID("1235"), Some("Studio Study")), + outcome = None + ) + ) + + val inMemory: ZLayer[Any, Nothing, ProductService] = + ZLayer( + Ref + .make( + List( + models.Product( + id = models.ID("apollo-federation"), + sku = Some("federation"), + `package` = Some("@apollo/federation"), + variation = Some(models.ProductVariation(models.ID("OSS"))), + dimensions = Some(models.ProductDimension(Some("small"), Some(1.0f), Some("kg"))), + createdBy = ZIO.some( + models.User( + models.ID("support@apollographql.com"), + Some(1337), + Some("Jane Smith"), + averageProductsCreatedPerYear = Some(1337 / 10), + yearsOfEmployment = 10 + ) + ), + notes = Some("This is a test product"), + research = productsResearch.init + ), + models.Product( + id = models.ID("apollo-studio"), + sku = Some("studio"), + `package` = Some(""), + variation = Some(models.ProductVariation(models.ID("platform"))), + dimensions = Some(models.ProductDimension(Some("small"), Some(1.0f), Some("kg"))), + createdBy = ZIO.some( + models.User( + models.ID("support@apollographql.com"), + Some(1337), + Some("Jane Smith"), + averageProductsCreatedPerYear = Some(1337 / 10), + yearsOfEmployment = 10 + ) + ), + notes = Some("This is a note"), + research = productsResearch.tail + ) + ) + ) + .map { products => + new ProductService { + override def getProductById(id: String): UIO[Option[models.Product]] = + products.get.map(_.find(_.id.id == id)) + + override def getProductBySkuAndPackage(sku: String, pack: String): UIO[Option[models.Product]] = + products.get.map(_.find(p => p.sku.contains(sku) && p.`package`.contains(pack))) + + override def getProductBySkuAndVariationId(sku: String, variationId: String): UIO[Option[models.Product]] = + products.get.map( + _.find(p => + p.sku.contains(sku) && p.variation.contains(models.ProductVariation(models.ID(variationId))) + ) + ) + } + } + ) +} diff --git a/apollo-compatibility/src/main/scala/services/UserService.scala b/apollo-compatibility/src/main/scala/services/UserService.scala new file mode 100644 index 0000000000..d37df0da0f --- /dev/null +++ b/apollo-compatibility/src/main/scala/services/UserService.scala @@ -0,0 +1,23 @@ +package services + +import models.{ ID, User } +import zio.{ UIO, ULayer, ZIO, ZLayer } + +trait UserService { + + def getUser: UIO[Option[User]] +} + +object UserService { + private val theUser = User( + averageProductsCreatedPerYear = Some(1337 / 10), + email = ID("support@apollographql.com"), + name = Some("Jane Smith"), + totalProductsCreated = Some(1337), + yearsOfEmployment = 10 + ) + + val inMemory: ULayer[UserService] = ZLayer.succeed(new UserService { + def getUser: zio.UIO[Option[User]] = ZIO.some(theUser) + }) +} diff --git a/build.sbt b/build.sbt index 3aa0a3ed6c..1800258a89 100644 --- a/build.sbt +++ b/build.sbt @@ -98,7 +98,8 @@ lazy val allProjects: Seq[ProjectReference] = codegenSbt, federation, reporting, - tracing + tracing, + apolloCompatibility ) lazy val root = project @@ -116,7 +117,7 @@ lazy val rootJVM212 = project ideSkipProject := true ) .aggregate({ - val excluded: Set[ProjectReference] = Set(clientJS, clientNative, clientLaminext, play) + val excluded: Set[ProjectReference] = Set(clientJS, clientNative, clientLaminext, play, apolloCompatibility) allProjects.filterNot(excluded.contains) } *) @@ -140,7 +141,8 @@ lazy val rootJVM3 = project ideSkipProject := true ) .aggregate({ - val excluded: Set[ProjectReference] = Set(clientJS, clientNative, clientLaminext, codegenSbt, akkaHttp) + val excluded: Set[ProjectReference] = + Set(clientJS, clientNative, clientLaminext, codegenSbt, akkaHttp) allProjects.filterNot(excluded.contains) } *) @@ -149,6 +151,7 @@ lazy val macros = project .settings(name := "caliban-macros") .settings(commonSettings) .settings(enableMimaSettingsJVM) + .disablePlugins(AssemblyPlugin) .settings( libraryDependencies ++= { if (scalaVersion.value == scala3) { @@ -169,6 +172,7 @@ lazy val core = project .settings(name := "caliban") .settings(commonSettings) .settings(enableMimaSettingsJVM) + .disablePlugins(AssemblyPlugin) .settings( testFrameworks := Seq(new TestFramework("zio.test.sbt.ZTestFramework")), libraryDependencies ++= @@ -203,6 +207,7 @@ lazy val tools = project .settings(name := "caliban-tools") .settings(commonSettings) .settings(enableMimaSettingsJVM) + .disablePlugins(AssemblyPlugin) .settings( buildInfoKeys := Seq[BuildInfoKey]( "scalaPartialVersion" -> CrossVersion.partialVersion(scalaVersion.value), @@ -230,6 +235,7 @@ lazy val tracing = project .settings(name := "caliban-tracing") .settings(commonSettings) .settings(enableMimaSettingsJVM) + .disablePlugins(AssemblyPlugin) .settings( buildInfoPackage := "caliban.tracing", buildInfoObject := "BuildInfo" @@ -250,6 +256,7 @@ lazy val codegenSbt = project .settings(name := "caliban-codegen-sbt") .settings(commonSettings) .enablePlugins(BuildInfoPlugin) + .disablePlugins(AssemblyPlugin) .settings( skip := (scalaVersion.value != scala212), ideSkipProject := (scalaVersion.value != scala212), @@ -289,6 +296,7 @@ lazy val catsInterop = project .settings(name := "caliban-cats") .settings(commonSettings) .settings(enableMimaSettingsJVM) + .disablePlugins(AssemblyPlugin) .settings( testFrameworks := Seq(new TestFramework("zio.test.sbt.ZTestFramework")), libraryDependencies ++= { @@ -309,6 +317,7 @@ lazy val monixInterop = project .settings(name := "caliban-monix") .settings(commonSettings) .settings(enableMimaSettingsJVM) + .disablePlugins(AssemblyPlugin) .settings( libraryDependencies ++= Seq( "dev.zio" %% "zio-interop-reactivestreams" % zioInteropReactiveVersion, @@ -323,6 +332,7 @@ lazy val tapirInterop = project .settings(name := "caliban-tapir") .settings(commonSettings) .settings(enableMimaSettingsJVM) + .disablePlugins(AssemblyPlugin) .settings( testFrameworks := Seq(new TestFramework("zio.test.sbt.ZTestFramework")), libraryDependencies ++= { @@ -346,6 +356,7 @@ lazy val http4s = project .settings(name := "caliban-http4s") .settings(commonSettings) .settings(enableMimaSettingsJVM) + .disablePlugins(AssemblyPlugin) .settings( testFrameworks := Seq(new TestFramework("zio.test.sbt.ZTestFramework")), libraryDependencies ++= { @@ -373,6 +384,7 @@ lazy val zioHttp = project .settings(name := "caliban-zio-http") .settings(commonSettings) .settings(enableMimaSettingsJVM) + .disablePlugins(AssemblyPlugin) .settings( resolvers += "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots", testFrameworks := Seq(new TestFramework("zio.test.sbt.ZTestFramework")), @@ -390,6 +402,7 @@ lazy val quickAdapter = project .settings(name := "caliban-quick") .settings(commonSettings) .settings(enableMimaSettingsJVM) + .disablePlugins(AssemblyPlugin) .settings( testFrameworks := Seq(new TestFramework("zio.test.sbt.ZTestFramework")), libraryDependencies ++= Seq( @@ -406,6 +419,7 @@ lazy val akkaHttp = project .settings(name := "caliban-akka-http") .settings(commonSettings) .settings(enableMimaSettingsJVM) + .disablePlugins(AssemblyPlugin) .settings( skip := (scalaVersion.value == scala3), ideSkipProject := (scalaVersion.value == scala3), @@ -426,6 +440,7 @@ lazy val pekkoHttp = project .settings(name := "caliban-pekko-http") .settings(commonSettings) .settings(enableMimaSettingsJVM) + .disablePlugins(AssemblyPlugin) .settings( testFrameworks := Seq(new TestFramework("zio.test.sbt.ZTestFramework")), libraryDependencies ++= { @@ -444,6 +459,7 @@ lazy val play = project .settings(name := "caliban-play") .settings(commonSettings) .settings(enableMimaSettingsJVM) + .disablePlugins(AssemblyPlugin) .settings( skip := (scalaVersion.value == scala212), ideSkipProject := (scalaVersion.value == scala212), @@ -470,6 +486,7 @@ lazy val client = crossProject(JSPlatform, JVMPlatform, NativePlatform) .in(file("client")) .settings(name := "caliban-client") .settings(commonSettings) + .disablePlugins(AssemblyPlugin) .settings( testFrameworks := Seq(new TestFramework("zio.test.sbt.ZTestFramework")), libraryDependencies ++= Seq( @@ -514,6 +531,7 @@ lazy val clientLaminext = crossProject(JSPlatform) .settings(commonSettings) .settings(enableMimaSettingsJS) .dependsOn(clientJS) + .disablePlugins(AssemblyPlugin) .settings( testFrameworks := Seq(new TestFramework("zio.test.sbt.ZTestFramework")), Test / scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.ESModule) }, @@ -534,6 +552,7 @@ lazy val clientLaminext = crossProject(JSPlatform) lazy val examples = project .in(file("examples")) .settings(commonSettings) + .disablePlugins(AssemblyPlugin) .settings( publish / skip := true, run / fork := true, @@ -575,12 +594,44 @@ lazy val examples = project tools ) +lazy val apolloCompatibility = + project + .in(file("apollo-compatibility")) + .settings(commonSettings) + .settings( + name := "apollo-compatibility", + publish / skip := true, + run / fork := true, + run / connectInput := true + ) + .settings( + skip := (scalaVersion.value == scala212), + ideSkipProject := (scalaVersion.value != scala212), + crossScalaVersions := Seq(scala213, scala3), + libraryDependencySchemes += "org.scala-lang.modules" %% "scala-java8-compat" % "always" + ) + .settings( + assembly / assemblyJarName := s"apollo-subgraph-compatibility.jar", + assembly / mainClass := Some("Main"), + assembly / assemblyOutputPath := { + (assembly / baseDirectory).value / "target" / (assembly / assemblyJarName).value + }, + assembly / test := {}, + assembly / assemblyMergeStrategy := { + case x if Assembly.isConfigFile(x) => MergeStrategy.concat + case PathList("META-INF", "MANIFEST.MF") => MergeStrategy.discard + case _ => MergeStrategy.first + } + ) + .dependsOn(federation, core, quickAdapter) + lazy val reporting = project .in(file("reporting")) .settings(name := "caliban-reporting") .settings(commonSettings) .settings(enableMimaSettingsJVM) .dependsOn(clientJVM, core) + .disablePlugins(AssemblyPlugin) .settings( testFrameworks := Seq(new TestFramework("zio.test.sbt.ZTestFramework")), libraryDependencies ++= Seq( @@ -594,6 +645,7 @@ lazy val reporting = project lazy val benchmarks = project .in(file("benchmarks")) .settings(commonSettings) + .disablePlugins(AssemblyPlugin) .settings( skip := (scalaVersion.value == scala212), ideSkipProject := (scalaVersion.value == scala212), @@ -626,6 +678,7 @@ lazy val federation = project .settings(commonSettings) .settings(enableMimaSettingsJVM) .dependsOn(core % "compile->compile;test->test") + .disablePlugins(AssemblyPlugin) .settings( testFrameworks := Seq(new TestFramework("zio.test.sbt.ZTestFramework")), libraryDependencies ++= Seq( @@ -645,6 +698,7 @@ lazy val docs = project .in(file("mdoc")) .enablePlugins(MdocPlugin) .settings(commonSettings) + .disablePlugins(AssemblyPlugin) .settings( skip := (scalaVersion.value == scala3), ideSkipProject := (scalaVersion.value == scala3), diff --git a/project/plugins.sbt b/project/plugins.sbt index fa99143e2c..f7442d3a78 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -11,6 +11,7 @@ addSbtPlugin("com.thesamet" % "sbt-protoc" % "1.0.7") addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.12.0") addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.5.2") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.1.3") +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.2.0") addSbtPlugin("org.jetbrains.scala" % "sbt-ide-settings" % "1.1.2")