Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/workflows/test-subgraph-graphql-go.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: GraphQL Go Test

on:
pull_request:
branches:
- main
paths:
- 'implementations/graphql-go/**'

jobs:
compatibility:
uses: ./.github/workflows/test-subgraph.yaml
with:
library: "graphql-go"
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ The following open-source GraphQL server libraries and hosted subgraphs provide
<tr><th width="300">Library</th><th>Federation 1 Support</th><th>Federation 2 Support</th></tr>
</thead>
<tbody>
<tr><td><a href="https://github.com/graphql-go/graphql">GraphQL Go</a></td><td><table><tr><th>_service</th><td>🟢</td></tr><tr><th>@key (single)</th><td>🟢</td></tr><tr><th>@key (multi)</th><td>🟢</td></tr><tr><th>@key (composite)</th><td>🟢</td></tr><tr><th>repeatable @key</th><td>🟢</td></tr><tr><th>@requires</th><td>🟢</td></tr><tr><th>@provides</th><td>🟢</td></tr><tr><th>federated tracing</th><td>🔲</td></tr></table></td><td><table><tr><th>@link</th><td>🟢</td></tr><tr><th>@shareable</th><td>🟢</td></tr><tr><th>@tag</th><td>🟢</td></tr><tr><th>@override</th><td>🟢</td></tr><tr><th>@inaccessible</th><td>🟢</td></tr></table></td></tr>
<tr><td><a href="https://gqlgen.com">gqlgen</a></td><td><table><tr><th>_service</th><td>🟢</td></tr><tr><th>@key (single)</th><td>🟢</td></tr><tr><th>@key (multi)</th><td>🟢</td></tr><tr><th>@key (composite)</th><td>🟢</td></tr><tr><th>repeatable @key</th><td>🟢</td></tr><tr><th>@requires</th><td>🔲</td></tr><tr><th>@provides</th><td>🟢</td></tr><tr><th>federated tracing</th><td>🟢</td></tr></table></td><td><table><tr><th>@link</th><td>🟢</td></tr><tr><th>@shareable</th><td>🟢</td></tr><tr><th>@tag</th><td>🟢</td></tr><tr><th>@override</th><td>🟢</td></tr><tr><th>@inaccessible</th><td>🟢</td></tr></table></td></tr>
</tbody>
</table>
Expand Down
9 changes: 9 additions & 0 deletions implementations/graphql-go/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM golang:1.19

WORKDIR /go/src/server
COPY . .

RUN go get -d -v ./...
RUN go install -v ./...

CMD go run server.go
6 changes: 6 additions & 0 deletions implementations/graphql-go/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
services:
products:
# must be relative to the root of the project
build: implementations/graphql-go
ports:
- 4001:4001
10 changes: 10 additions & 0 deletions implementations/graphql-go/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module graphql-go-compatibility

go 1.19

require (
github.com/graphql-go/graphql v0.8.0
github.com/graphql-go/handler v0.2.3
)

replace github.com/graphql-go/graphql => github.com/dariuszkuc/graphql v0.9.0-federation
4 changes: 4 additions & 0 deletions implementations/graphql-go/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
github.com/dariuszkuc/graphql v0.9.0-federation h1:dHxP3Z+wX/hRkj/3jaxGXb7iG+Zw3fjW1frQbgsq3lI=
github.com/dariuszkuc/graphql v0.9.0-federation/go.mod h1:nKiHzRM0qopJEwCITUuIsxk9PlVlwIiiI8pnJEhordQ=
github.com/graphql-go/handler v0.2.3 h1:CANh8WPnl5M9uA25c2GBhPqJhE53Fg0Iue/fRNla71E=
github.com/graphql-go/handler v0.2.3/go.mod h1:leLF6RpV5uZMN1CdImAxuiayrYYhOk33bZciaUGaXeU=
3 changes: 3 additions & 0 deletions implementations/graphql-go/metadata.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fullName: GraphQL Go
language: Go
documentation: https://github.com/graphql-go/graphql
47 changes: 47 additions & 0 deletions implementations/graphql-go/model/models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package model

type User struct {
// AverageProductsCreatedPerYear *int `json:"averageProductsCreatedPerYear"`
Email string `json:"email"`
Name *string `json:"name"`
TotalProductsCreated *int `json:"totalProductsCreated"`
YearsOfEmployment int `json:"yearsOfEmployment"`
}

type CaseStudy struct {
CaseNumber string `json:"caseNumber"`
Description *string `json:"description"`
}

type DeprecatedProduct struct {
Sku string `json:"sku"`
Package string `json:"package"`
Reason *string `json:"reason"`
CreatedBy *User `json:"createdBy"`
}

type Product struct {
ID string `json:"id"`
Sku *string `json:"sku"`
Package *string `json:"package"`
Variation *ProductVariation `json:"variation"`
Dimensions *ProductDimension `json:"dimensions"`
CreatedBy *User `json:"createdBy"`
Notes *string `json:"notes"`
Research []*ProductResearch `json:"research"`
}

type ProductDimension struct {
Size *string `json:"size"`
Weight *float64 `json:"weight"`
Unit *string `json:"unit"`
}

type ProductResearch struct {
Study *CaseStudy `json:"study"`
Outcome *string `json:"outcome"`
}

type ProductVariation struct {
ID string `json:"id"`
}
56 changes: 56 additions & 0 deletions implementations/graphql-go/products.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
extend schema
@link(
url: "https://specs.apollo.dev/federation/v2.0",
import: ["@extends", "@external", "@inaccessible", "@key", "@override", "@provides", "@requires", "@shareable", "@tag"]
)

type Product @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
}
78 changes: 78 additions & 0 deletions implementations/graphql-go/resolver/data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package resolver

import "graphql-go-compatibility/model"

var federationSku = "federation"
var federationPackage = "@apollo/federation"

var studioSku = "studio"

var productSize = "small"
var productWeight = 1.0
var productUnit = "kg"

var products = []*model.Product{
{
ID: "apollo-federation",
Sku: &federationSku,
Package: &federationPackage,
Variation: &model.ProductVariation{ID: "OSS"},
Dimensions: &model.ProductDimension{
Size: &productSize,
Weight: &productWeight,
Unit: &productUnit,
},
},
{
ID: "apollo-studio",
Sku: &studioSku,
Package: nil,
Variation: &model.ProductVariation{ID: "platform"},
Dimensions: &model.ProductDimension{
Size: &productSize,
Weight: &productWeight,
Unit: &productUnit,
},
},
}

var userName = "Jane Smith"
var totalProductsCreated = 1337

var users = []*model.User{
{
Email: "[email protected]",
Name: &userName,
TotalProductsCreated: &totalProductsCreated,
},
}

var DefaultUser = users[0]

var deprecationReason = "Migrate to Federation V2"

var deprecatedProducts = []*model.DeprecatedProduct{
{
Sku: "apollo-federation-v1",
Package: "@apollo/federation-v1",
Reason: &deprecationReason,
},
}

var federationStudyDescription = "Federation Study"
var studioStudyDescription = "Studio Study"

var research = []*model.ProductResearch{
{
Study: &model.CaseStudy{
CaseNumber: "1234",
Description: &federationStudyDescription,
},
},
{
Study: &model.CaseStudy{
CaseNumber: "1235",
Description: &studioStudyDescription,
},
},
}
146 changes: 146 additions & 0 deletions implementations/graphql-go/resolver/resolvers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package resolver

import (
"math"

"graphql-go-compatibility/model"
)

func FindDeprecatedProductBySkuAndPackage(sku string, packageArg string) (*model.DeprecatedProduct, error) {
for i := range deprecatedProducts {
if deprecatedProducts[i].Sku == sku && deprecatedProducts[i].Package == packageArg {
return deprecatedProducts[i], nil
}
}
return nil, nil
}

func FindProductById(id string) (*model.Product, error) {
for i := range products {
if products[i].ID == id {
return products[i], nil
}
}
return nil, nil
}

func FindProductBySkuAndPackage(sku string, packageArg string) (*model.Product, error) {
for i := range products {
if *products[i].Sku == sku && *products[i].Package == packageArg {
return products[i], nil
}
}
return nil, nil
}

func FindProductBySkuAndVariationID(sku string, variationID string) (*model.Product, error) {
for i := range products {
if *products[i].Sku == sku && products[i].Variation.ID == variationID {
return products[i], nil
}
}
return nil, nil
}

func FindProductResearchByProductId(product *model.Product) ([]*model.ProductResearch, error) {
switch product.ID {
case "apollo-federation":
return research[:1], nil
case "apollo-studio":
return research[1:], nil
default:
return nil, nil
}
}

func CalculateAverageProductsCreatedPerYear(user *model.User) (*int, error) {
if user.TotalProductsCreated == nil {
return nil, nil
}
var avgProductsCreated = int(math.Round(float64(*user.TotalProductsCreated) / float64(user.YearsOfEmployment)))
return &avgProductsCreated, nil
}

// entity resolvers

// @key(fields: "sku package")
func DeprecatedProductEntityResolver(params map[string]interface{}) (*model.DeprecatedProduct, error) {
sku, skuOk := params["sku"].(string)
pkg, pkgOk := params["package"].(string)
if skuOk && pkgOk {
return FindDeprecatedProductBySkuAndPackage(sku, pkg)
}
return nil, nil
}

// @key(fields: "id")
// @key(fields: "sku package")
// @key(fields: "sku variation { id }")
func ProductEntityResolver(params map[string]interface{}) (*model.Product, error) {
id, ok := params["id"].(string)
if ok {
return FindProductById(id)
} else {
sku, skuOk := params["sku"].(string)
if skuOk {
pkg, pkgOk := params["package"].(string)
if pkgOk {
return FindProductBySkuAndPackage(sku, pkg)
} else {
variation, varOk := params["variation"].(map[string]interface{})
if varOk {
varId, varIdOk := variation["id"].(string)
if varIdOk {
return FindProductBySkuAndVariationID(sku, varId)
}
}
}
}
}
return nil, nil
}

// @key(fields: "study { caseNumber }")
func ProductResearchEntityResolver(params map[string]interface{}) (*model.ProductResearch, error) {
study, ok := params["study"].(map[string]interface{})
if ok {
caseNumber, caseOk := study["caseNumber"].(string)
if caseOk {
for i := range research {
if research[i].Study.CaseNumber == caseNumber {
return research[i], nil
}
}
}
}
return nil, nil
}

// @key(fields: "email")
func UserEntityResolver(params map[string]interface{}) (*model.User, error) {
email, ok := params["email"].(string)
if ok {
for i := range users {
var user *model.User
if users[i].Email == email {
user = users[i]
}

totalProducts, totalSpecified := params["totalProductsCreated"].(float64)
if totalSpecified {
total := int(totalProducts)
user.TotalProductsCreated = &total
}

yearsOfEmployment, yearsSpecified := params["yearsOfEmployment"].(float64)
if yearsSpecified {
user.YearsOfEmployment = int(yearsOfEmployment)
}

if user != nil {
return user, nil
}
}
}
return nil, nil
}
Loading