gqlgenc is a fully featured go gql client, powered by codegen

gqlgenc

Note: ⚠️ This is a WIP, backward-compatibility cannot be guaranteed yet, use at your own risk

gqlgenc is a fully featured go gql client, powered by codegen

Why yet another Go GQL client ?

Package Codegen Websocket Subscription Extensions
https://github.com/shurcooL/graphql
https://github.com/Yamashou/gqlgenc
https://github.com/hasura/go-graphql-client
https://github.com/infiotinc/gqlgenc

GQL Client

Transports

gqlgenc is transport agnostic, and ships with 3 transport implementations:

  • http: Transports GQL queries over http
  • ws: Transports GQL queries over websocket
  • split: Can be used to have a single client use multiple transports depending on the type of query (query, mutation over http and subscription over ws)

Quickstart

Quickstart with a client with http & ws transports:

package main

import (
    "context"
    "github.com/infiotinc/gqlgenc/client"
    "github.com/infiotinc/gqlgenc/client/transport"
)

func main() {
    wstr := &transport.Ws{
        URL: "wss://example.org/graphql",
    }
    wstr.Start(context.Background())
    defer wstr.Close()

    httptr := &transport.Http{
        URL: "https://example.org/graphql",
    }

    tr := transport.SplitSubscription(wstr, httptr)

    cli := &client.Client {
        Transport: tr,
    }
}

Query/Mutation

var res struct {
    Room string `json:"room"`
}
_, err := cli.Query(ctx, "", "query { room }", nil, &res) // or Mutation
if err != nil {
    panic(err)
}

Subscription

sub, stop := cli.Subscription(ctx, "", "subscription { newRoom }", nil)
defer stop()

for sub.Next() {
    msg := sub.Get()
    
    if len(msg.Errors) > 0 {
        // Do something with them
    }
    
    var res struct {
        Room string `json:"newRoom"`
    }
    err := msg.UnmarshalData(&res)
    if err != nil {
        // Do something with that
    }
}

if err := sub.Err(); err != nil {
    panic(err)
}

GQL Client Codegen

Create a .gqlgenc.yml at the root of your module:

model:
  package: graph
  filename: ./graph/gen_models.go
client:
  package: graph
  filename: ./graph/gen_client.go
models:
  Int:
    model: github.com/99designs/gqlgen/graphql.Int64
  DateTime:
    model: github.com/99designs/gqlgen/graphql.Time
# The schema can be fetched from files or through introspection
schema:
  - schema.graphqls
endpoint:
  url: https://api.annict.com/graphql # Where do you want to send your request?
  headers: # If you need header for getting introspection query, set it
    Authorization: "Bearer ${ANNICT_KEY}" # support environment variables
query:
  - query.graphql

Fill your query.graphql with queries:

query GetRoom {
    room(name: "secret room") {
        name
    }
}

Run go run github.com/infiotinc/gqlgenc

Enjoy:

// Create codegen client
gql := &graph.Client{
    Client: cli,
}

gql.GetRoom(...)

Extensions

APQ

Automatic Persisted Queries can be enabled by adding:

cli.Use(&extensions.APQ{})

Release

TAG=v0.0.x make tag
Comments
  • Don't send 'null' when optional fields are not set

    Don't send 'null' when optional fields are not set

    Continuing the discussion from https://github.com/Yamashou/gqlgenc/issues/64

    Given this schema:

    input GetFooInput {
        optionalParam1: String
        optionalParam2: String
    }
    
    type Query {
        getFoo(input: GetFooInput!): String!
    }
    

    this operation:

    query getFoo($p1: String, $p2: String) {
      getFoo(input: {optionalParam1: $p1, optionalParam2: $p2})
    }
    

    and this example code:

    package main
    
    import (
    	"context"
    	"lol/dgraphclient"
    
    	"github.com/infiotinc/gqlgenc/client"
    	"github.com/infiotinc/gqlgenc/client/transport"
    )
    
    func main() {
    	wstr := &transport.Ws{
    		URL: "ws://localhost:8686/subscribe",
    	}
    	wstr.Start(context.Background())
    	defer wstr.Close()
    
    	httptr := &transport.Http{
    		URL: "http://localhost:8686/subscribe",
    	}
    
    	tr := transport.SplitSubscription(wstr, httptr)
    
    	cli := &client.Client{
    		Transport: tr,
    	}
    
    	c := dgraphclient.Client{Client: cli}
    
    	p1 := "hi"
    
    	c.GetFoo(context.Background(), &p1, nil)
    }
    

    Following request is being made to the server:

    {
      "query": "query getFoo ($p1: String, $p2: String) {\n\tgetFoo(input: {optionalParam1:$p1,optionalParam2:$p2})\n}\n",
      "operationName": "GetFoo",
      "variables": { "p1": "hi", "p2": null }
    }
    
    

    This is problematic, because setting p2 to null is different than not sending it in the first place. Sending null can (and will) invoke actions on the server side. E.g. deleting some database entry.

    Therefore it would be great if we could have the concept of undefined in this golang client, which is of course difficult as golang does not support an undefined type.

    Possible Solution

    Maybe something like this could work:

    type OptionalString struct {
    	Value *string
    }
    
    type GetFooInput struct {
            OptionalParam1 *OptionalString `json:"optionalParam1,omitempty"`
    	OptionalParam2 *OptionalString `json:"optionalParam2,omitempty"`
    }
    

    I.e. when optional input types are present (on any depth) an Optional<Type> type is used in combination with jsons omitempty tag instead. This behavior could be made configurable via .gqlgenc.yml.

    Case A: When the field is nil, it will not be serialized. Case B: When it is set to &OptionalString{}, the client sends null. Case C: When it is set to &OptionalString{Value: "foo"}, the client sends "foo".

    Here is an working example:

    package main
    
    import (
    	"encoding/json"
    	"fmt"
    )
    
    type OptionalString struct {
    	Value *string
    }
    
    func (o *OptionalString) MarshalJSON() ([]byte, error) {
    	return json.Marshal(o.Value)
    }
    
    type GetFooInput struct {
    	OptionalParam1 *OptionalString `json:"optionalParam1,omitempty"`
    	OptionalParam2 *OptionalString `json:"optionalParam2,omitempty"`
    }
    
    func main() {
    	s := "foo"
    	f := GetFooInput{
    		OptionalParam1: &OptionalString{Value: &s},
    	}
    
    	result, err := json.Marshal(f)
    	if err != nil {
    		panic(err)
    	}
    
    	fmt.Println(string(result))
    }
    
    

    Which outputs:

    {"optionalParam1":"foo"}
    

    It would be great if you could add this feature!

  • [input_as_map] Can't send null for slice inputs

    [input_as_map] Can't send null for slice inputs

    Schema:

    input FooInput {
       ids: [String!]
    }
    

    Generates:

    func WithIDs(v []string) { ... }
    

    I think that instead this should be generated so a list is also nullable:

    func WithIDs(v *[]string) { ... }
    
  • [Bug] OperationName is always capitalized

    [Bug] OperationName is always capitalized

    Operation:

    mutation foo {
      bar
    }
    

    Generates:

    res, err := Ξc.Client.Mutation(ctх, "Foo", FooDocument, Ξvars, &data)
    

    Note how the generated operation-name Foo is capitalized while the schema says that it's not capitalized. This is resulting in an error when doing the actual request.

  • How to handle custom scalars?

    How to handle custom scalars?

    I have custom scalars like scalar uuid in my schema.

    When generating the client, following panic is thrown:

    panic: scalars must be predeclared: uuid
    

    What can we do here? Is this currently completely unsupport as the code suggests?

  • nil pointer deref when trying to run first time

    nil pointer deref when trying to run first time

    Trying to debug this but haven't gotten to the bottom of it yet.

    ❯ gqlgenc
    panic: runtime error: invalid memory address or nil pointer dereference
    [signal SIGSEGV: segmentation violation code=0x2 addr=0x28 pc=0x100433d00]
    
    goroutine 1 [running]:
    github.com/Yamashou/gqlgenc/clientgen.RenderTemplate(0x14000083380, 0x1400bea4fc0, 0x1400bea50e0, 0x1008beb60, 0x0, 0x0, 0x1008beb60, 0x0, 0x0, 0x1008beb60, ...)
    	/.../go/pkg/mod/github.com/!yamashou/[email protected]/clientgen/template.go:22 +0x2c0
    github.com/Yamashou/gqlgenc/clientgen.(*Plugin).MutateConfig(0x140000e4a80, 0x14000083380, 0x140000e4a80, 0x128f3a1e8)
    	/.../go/pkg/mod/github.com/!yamashou/[email protected]/clientgen/client.go:80 +0x62c
    github.com/Yamashou/gqlgenc/generator.Generate(0x1005f1630, 0x140000ac000, 0x140000d07e0, 0x14000107f50, 0x1, 0x1, 0x14000036800, 0x100081584)
    	/.../go/pkg/mod/github.com/!yamashou/[email protected]/generator/generater.go:52 +0x230
    main.main()
    	/.../go/pkg/mod/github.com/!yamashou/[email protected]/main.go:30 +0x154
    

    With this yaml config:

    model:
      package: generated
      filename: ./generated/models.go # http://localhost:8080/v1/graphql
    client:
      package: generated
      filename: ./generated/client.go # Where should any generated client go?
    models:
      Int:
        model: github.com/99designs/gqlgen/graphql.Int64
      Date:
        model: github.com/99designs/gqlgen/graphql.Time
    endpoint:
      url: http://localhost:8080/v1/graphql # Where do you want to send your request?
      headers:
        x-hasura-admin-secret: admin-secret # support environment variables
    query:
      - "./query/*.graphql" # Where are all the query files located?
    
  • updated gqlgen to v0.16.0 and fixed compilation

    updated gqlgen to v0.16.0 and fixed compilation

    When working alongside gqlgen in the same project with tool vendoring, gqlgenc fails to compile due to it using v0.13.0 and breaking changes since then.

    Since gqlgen's version is marked as unstable (v0.*) go does only complain at compile time. For an example see https://git.sr.ht/~poldi1405/mpsync

  • [Bug] gen type already defined

    [Bug] gen type already defined

    Schema:

    type A {
        a: String!
    }
    
    type Foo {
        blub: String!
        aA: A!
    }
    
    type Payload {
        foo1: Foo!
        foo2: Foo!
    }
    
    type Query {
        getFoo: Payload
    }
    

    Operation:

    query getFoo {
      getFoo {
        foo1 {
          blub
          aA {
            a
          }
        }
        foo2 {
          blub
          aA {
            a
          }
        }
      }
    }
    

    There is some issue with deeply nested objects that have the same name.

  • Subscriptions not working

    Subscriptions not working

    Hi,

    I cannot get subscriptions to work. What am I doing wrong? This should be a very simple, working example.

    package main
    
    import (
    	"context"
    	"fmt"
    
    	"github.com/infiotinc/gqlgenc/client"
    	"github.com/infiotinc/gqlgenc/client/transport"
    )
    
    func main() {
    	wstr := &transport.Ws{
    		URL: "ws://localhost:8686/subscribe",
    	}
    	wstr.Start(context.Background())
    	defer wstr.Close()
    
    	httptr := &transport.Http{
    		URL: "http://localhost:8686/subscribe",
    	}
    
    	tr := transport.SplitSubscription(wstr, httptr)
    
    	cli := &client.Client{
    		Transport: tr,
    	}
    	sub := cli.Subscription(context.Background(), "", "subscription  { subscribeFoo { name }}", nil)
    
    	for sub.Next() {
    		msg := sub.Get()
    
    		fmt.Println(msg)
    	}
    
    	if err := sub.Err(); err != nil {
    		panic(err)
    	}
    }
    
    

    The server is working correctly and I get results for instance with altair.

  • fix: load remote schema

    fix: load remote schema

    Loading a remote schema using the endpoint config option fails, because the program cannot unmarshal the received data. The problem stems from an misnamed stuct tag on the Query type.

  • Generating queries from json structs

    Generating queries from json structs

    Trying to migrate from https://github.com/shurcooL/graphql for sake of subscriptions, I'm wondering if it is still supported to create the query from the result struct?

  • Use Optional Types for GraphQL Optional

    Use Optional Types for GraphQL Optional

    With go 1.18 generics are now possible to use.

    These can be used for example to implement the Optional concept: https://github.com/frenchie4111/go-generic-optional

    Although the current workaround with input_as_map works, we also have following issues:

    • When the order of arguments change for a GraphQL input object, we must migrate our existing code
    • When the variable-name order changes but the type order does not, we won't even notice this through our linter
    • auto-completion is not very good for function arguments

    When instead we go back to structs but use the Optional concept from above package, we could have the best of both worlds:

    • no order dependencies
    • don't send optional values as nil
  • How to provide the content-type for the Upload file?

    How to provide the content-type for the Upload file?

    How to set the content-type for the Upload file?

    Its default value: application/octet-stream, but I would like to provide a custom one (e.g. "text/csv").

  • Fieldname

    Fieldname "map"

    Hey,

    first of all: Thanks for the great work.

    I have a mutation like this:

    mutation testmutation {
      addFile(input: {
        Ids: ["xyxy", "xyxy2"]
        Maps: {
          Id: "daw"
          map: ....
        } 
      }){
        testSuccess
      }
    }
    

    The Field "map" is of type Upload!, but map is also a keyword in go. The generator ends up in the following error: "mixed named and unnamed parameters" because the generated go func has the form

    
    type ObjectInput map[string]interface{}
            func ObjectInput(map transport.Upload,probability float64,) ObjectInput {
                return map[string]interface{}{
                    "map": map,
                    "probability": probability,
                }
            }
    
    

    (this is another mutation, but it should be clear)

    The generator should filter for some keywords, i.e. map and change it to something like

    
    type ObjectInput map[string]interface{}
            func ObjectInput(map_ transport.Upload,probability float64,) ObjectInput {
                return map[string]interface{}{
                    "map": map_,
                    "probability": probability,
                }
            }
    
    

    Any Ideas how to do it and where? I could also do a PR if I had a starting point

    Thanks

  • Implement Typename as Enum

    Implement Typename as Enum

    It would be great if you could implement Typename as an enum.

    Currently a string type is used which is error prone because you have to type out possible types (e.g. from interfaces or enums) per hand.

Related tags
DeSo is a blockchain built from the ground up to support a fully-featured social network

DeSo is a blockchain built from the ground up to support a fully-featured social network. Its architecture is similar to Bitcoin, only it supports complex social network data like profiles, posts, follows, creator coin transactions, and more.

Dec 22, 2022
Prisma Client Go is an auto-generated and fully type-safe database client

Prisma Client Go Typesafe database access for Go Quickstart • Website • Docs • API reference • Blog • Slack • Twitter Prisma Client Go is an auto-gene

Jan 9, 2023
Full-featured BitTorrent client package and utilities

torrent This repository implements BitTorrent-related packages and command-line utilities in Go. The emphasis is on use as a library from other projec

Jan 2, 2023
GoSNMP is an SNMP client library fully written in Go

GoSNMP is an SNMP client library fully written in Go. It provides Get, GetNext, GetBulk, Walk, BulkWalk, Set and Traps. It supports IPv4 and IPv6, using SNMPv1, SNMPv2c or SNMPv3. Builds are tested against linux/amd64 and linux/386.

Jan 5, 2023
Fast Telegram client fully in go.

Telegram client, in Go. (MTProto API)

Jan 4, 2023
GoSNMP is an SNMP client library fully written in Go.

GoSNMP is an SNMP client library fully written in Go. It provides Get, GetNext, GetBulk, Walk, BulkWalk, Set and Traps. It supports IPv4 and IPv6, using SNMPv1, SNMPv2c or SNMPv3. Builds are tested against linux/amd64 and linux/386.

Oct 28, 2021
An userspace SORACOM Arc client powered by wireguard-go

soratun An easy-to-use, userspace SORACOM Arc client powered by wireguard-go. For deploying and scaling Linux servers/Raspberry Pi devices working wit

Jun 2, 2022
[deprecated] A full-featured SPDY library for the Go language.

Deprecated With the release of Go1.6 and the addition of http2 to the standard library, this package is no longer under active development. It is high

Oct 1, 2022
Gmqtt is a flexible, high-performance MQTT broker library that fully implements the MQTT protocol V3.1.1 and V5 in golang

中文文档 Gmqtt News: MQTT V5 is now supported. But due to those new features in v5, there area lots of breaking changes. If you have any migration problem

Jan 5, 2023
Automatically spawn a reverse shell fully interactive for Linux or Windows victim
Automatically spawn a reverse shell fully interactive for Linux or Windows victim

Girsh (Golang Interactive Reverse SHell) Who didn't get bored of manually typing the few lines to upgrade a reverse shell to a full interactive revers

Dec 14, 2022
纯Go编写的IM,完全自定义协议的高性能即时通讯服务(High-performance instant messaging service with fully customizable protocol)
纯Go编写的IM,完全自定义协议的高性能即时通讯服务(High-performance instant messaging service with fully customizable protocol)

LiMaoIM (Everything so easy) This project is a simple and easy to use, powerful performance, simple design concept instant messaging service, fully cu

Dec 5, 2022
Xray, Penetrates Everything. Also the best v2ray-core, with XTLS support. Fully compatible configuration.

Project X Project X originates from XTLS protocol, provides a set of network tools such as Xray-core and Xray-flutter. License Mozilla Public License

Jan 7, 2023
Laptop Booking Application in Golang and gRPC, load-balancing with NGINX, and fully compatible with HTTPS OpenAPI v3

Laptop Booking Application in Golang and gRPC Goals GitHub CI & Coverage Badge Serialize protobuf messages Create laptop unary gRPC Search laptop Serv

Jun 17, 2022
Episode VII: The DB Awakens (fully stablished JSON communication system)

APITrials0.7 Episode VII: The DB Awakens (fully stablished JSON communication system) Captain's log: Im too deep into the rabbit hole now. This seems

Jan 10, 2022
This project provides fully automated one-click experience to create Cloud and Kubernetes environment to run Data Analytics workload like Apache Spark.
This project provides fully automated one-click experience to create Cloud and Kubernetes environment to run Data Analytics workload like Apache Spark.

Introduction This project provides a fully automated one-click tool to create Data Analytics platform in Cloud and Kubernetes environment: Single scri

Nov 25, 2022
GraphQL API server for galaxy powered blockchain network

ICICB GraphQL API Server GraphQL API server for galaxy powered blockchain network. Releases Please check the release tags to get more details and to d

Jan 5, 2022
Powered by Matterbridge, MatterAMXX is a plugin for AMXX that allows simple bridging between your game servers, Mattermost, IRC, XMPP, Gitter, Slack, Discord, Telegram, and more.
Powered by Matterbridge, MatterAMXX is a plugin for AMXX that allows simple bridging between your game servers, Mattermost, IRC, XMPP, Gitter, Slack, Discord, Telegram, and more.

Powered by Matterbridge, MatterAMXX is a plugin for AMXX that allows simple bridging between your game servers, Mattermost, IRC, XMPP, Gitter, Slack, Discord, Telegram, and more.

Dec 27, 2022
Nuke-Net is a VERY VERY over powered and ridiculous web crawler that is well- very very noisy XD read more here
Nuke-Net is a VERY VERY over powered and ridiculous web crawler that is well- very very noisy XD read more here

Nuke-Net is a VERY VERY over powered and ridiculous web crawler that is well- very very noisy XD read more here

Dec 20, 2021
Jun 6, 2022