~emersion/hut-dev

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
7 2

[PATCH v1 0/1] git: show license spike

Details
Message ID
<20220122233022.1256016-1-renato.torres@protonmail.com>
DKIM signature
pass
Download raw message
This is not a final patch.

Following one of the comments from Thorben Günther regarding the git 
show command, I did a spike to show the license of a repository.

The command to show the license is:

go run . git show hut --license 
go run . git show hut -l

If used with the "license" flag the git show command shows only the
license, shipping all the other information. This allows to fetch the
license and use the output to feed other scripts.

I was able to get the LICENSE contents by defining the query 
licenseByRepositoryName.

I had an issue with this query because the Go structure returned is
an *Object and therefore I could not cast to a TextBlob to get the
contents of the file.

To make it work I changed manually the gql.go (I know I cannot do it)
to have the Object field be a map[string]interface{} instead of *Object:

type TreeEntry struct {
	Id     string                 `json:"id"`
	Name   string                 `json:"name"`
	Object map[string]interface{} `json:"object"` // change
	Mode   int32                  `json:"mode"`
}

Questions:

1. Is this feature usefull?

2. Is there a better way to get a file?

3. Should we change gqlclientgen to define a Go interface{}
when the type in the schema.graphqls is an interface?

4. Do you see any other alternative?


Renato Torres (1):
  git: show license

 git.go                          | 121 ++++++++++++++++++++------------
 srht/gitsrht/gql.go             |  20 ++++--
 srht/gitsrht/operations.graphql |  14 ++++
 3 files changed, 105 insertions(+), 50 deletions(-)

-- 
2.34.1

[PATCH v1 1/1] git: show license

Details
Message ID
<20220122233022.1256016-2-renato.torres@protonmail.com>
In-Reply-To
<20220122233022.1256016-1-renato.torres@protonmail.com> (view parent)
DKIM signature
pass
Download raw message
Patch: +105 -50
---
 git.go                          | 121 ++++++++++++++++++++------------
 srht/gitsrht/gql.go             |  20 ++++--
 srht/gitsrht/operations.graphql |  14 ++++
 3 files changed, 105 insertions(+), 50 deletions(-)

diff --git a/git.go b/git.go
index cc5f18c..379da20 100644
--- a/git.go
+++ b/git.go
@@ -411,6 +411,7 @@ func newGitACLDeleteCommand() *cobra.Command {
}

func newGitShowCommand() *cobra.Command {
	var showLicense bool
	run := func(cmd *cobra.Command, args []string) {
		ctx := cmd.Context()

@@ -424,53 +425,12 @@ func newGitShowCommand() *cobra.Command {

		c := createClientWithInstance("git", cmd, instance)

		repo, err := gitsrht.RepositoryByName(c.Client, ctx, name)
		if err != nil {
			log.Fatal(err)
		}

		// prints basic information
		fmt.Printf("%s (%s)\n", termfmt.Bold.String(repo.Name), repo.Visibility.TermString())
		if repo.Description != nil && *repo.Description != "" {
			fmt.Printf("  %s\n", *repo.Description)
		}
		if repo.UpstreamUrl != nil && *repo.UpstreamUrl != "" {
			fmt.Printf("  Upstream URL: %s\n", *repo.UpstreamUrl)
		}

		// prints latest tag
		tags := repo.References.Tags()
		if len(tags) > 0 {
			fmt.Println()
			fmt.Printf("  Latest tag: %s\n", tags[0])
		}

		// prints branches
		branches := repo.References.Heads()
		if len(branches) > 0 {
			fmt.Println()
			fmt.Printf("  Branches:\n")
			for i := 0; i < len(branches); i++ {
				fmt.Printf("    %s\n", branches[i])
			}
		if showLicense {
			showRepositoryLicense(c, ctx, name)
			return
		}

		// prints the three most recent commits
		if len(repo.Log.Results) >= 3 {
			fmt.Println()
			fmt.Printf("  Recent log:\n")

			for _, commit := range repo.Log.Results[:3] {
				fmt.Printf("    %s %s <%s> (%s ago)\n",
					termfmt.Yellow.Sprintf("%s", commit.ShortId),
					commit.Author.Name,
					commit.Author.Email,
					timeDelta(commit.Author.Time))

				commitLines := strings.Split(commit.Message, "\n")
				fmt.Printf("      %s\n", commitLines[0])
			}
		}
		showRepository(c, ctx, name)
	}

	cmd := &cobra.Command{
@@ -481,9 +441,80 @@ func newGitShowCommand() *cobra.Command {
		Run:               run,
	}

	cmd.Flags().BoolVarP(&showLicense, "license", "l", false, "shows only the license")

	return cmd
}

func showRepositoryLicense(c *Client, ctx context.Context, name string) {
	repo, err := gitsrht.LicenseByRepositoryName(c.Client, ctx, name)
	if err != nil {
		log.Fatal(err)
	}
	if repo == nil {
		log.Fatalf("repository %s does not exist", name)
	}

	if repo.Path == nil {
		log.Fatalf("LICENSE file not found for %s", name)
	}
	license := repo.Path.Object["text"]

	fmt.Println(license)
}

func showRepository(c *Client, ctx context.Context, name string) {
	repo, err := gitsrht.RepositoryByName(c.Client, ctx, name)
	if err != nil {
		log.Fatal(err)
	}
	if repo == nil {
		log.Fatalf("repository %s does not exist", name)
	}

	// prints basic information
	if repo.Description != nil && *repo.Description != "" {
		fmt.Printf("  %s\n", *repo.Description)
	}
	if repo.UpstreamUrl != nil && *repo.UpstreamUrl != "" {
		fmt.Printf("  Upstream URL: %s\n", *repo.UpstreamUrl)
	}

	// prints latest tag
	tags := repo.References.Tags()
	if len(tags) > 0 {
		fmt.Println()
		fmt.Printf("  Latest tag: %s\n", tags[0])
	}

	// prints branches
	branches := repo.References.Heads()
	if len(branches) > 0 {
		fmt.Println()
		fmt.Printf("  Branches:\n")
		for i := 0; i < len(branches); i++ {
			fmt.Printf("    %s\n", branches[i])
		}
	}

	// prints the three most recent commits
	if len(repo.Log.Results) >= 3 {
		fmt.Println()
		fmt.Printf("  Recent log:\n")

		for _, commit := range repo.Log.Results[:3] {
			fmt.Printf("    %s %s <%s> (%s ago)\n",
				termfmt.Yellow.Sprintf("%s", commit.ShortId),
				commit.Author.Name,
				commit.Author.Email,
				timeDelta(commit.Author.Time))

			commitLines := strings.Split(commit.Message, "\n")
			fmt.Printf("      %s\n", commitLines[0])
		}
	}
}

func getRepoName(ctx context.Context, cmd *cobra.Command) (repoName, instance string) {
	if repoName, err := cmd.Flags().GetString("repo"); err != nil {
		log.Fatal(err)
diff --git a/srht/gitsrht/gql.go b/srht/gitsrht/gql.go
index 55d59ea..ac225a4 100644
--- a/srht/gitsrht/gql.go
+++ b/srht/gitsrht/gql.go
@@ -205,10 +205,10 @@ type Tree struct {
}

type TreeEntry struct {
	Id     string  `json:"id"`
	Name   string  `json:"name"`
	Object *Object `json:"object"`
	Mode   int32   `json:"mode"`
	Id     string                  `json:"id"`
	Name   string                  `json:"name"`
	Object map[string]interface {} `json:"object"`
	Mode   int32                   `json:"mode"`
}

type TreeEntryCursor struct {
@@ -267,7 +267,17 @@ func ListArtifacts(client *gqlclient.Client, ctx context.Context, name string) (
}

func RepositoryByName(client *gqlclient.Client, ctx context.Context, name string) (repositoryByName *Repository, err error) {
	op := gqlclient.NewOperation("query repositoryByName ($name: String!) {\n\trepositoryByName(name: $name) {\n\t\tname\n\t\tdescription\n\t\tvisibility\n\t\tupstreamUrl\n\t\treferences {\n\t\t\tresults {\n\t\t\t\tname\n\t\t\t}\n\t\t}\n\t\tlog {\n\t\t\tresults {\n\t\t\t\tshortId\n\t\t\t\tauthor {\n\t\t\t\t\tname\n\t\t\t\t\temail\n\t\t\t\t\ttime\n\t\t\t\t}\n\t\t\t\tmessage\n\t\t\t}\n\t\t}\n\t}\n}\n")
	op := gqlclient.NewOperation("query repositoryByName ($name: String!) {\n\trepositoryByName(name: $name) {\n\t\tname\n\t\tdescription\n\t\tvisibility\n\t\tupstreamUrl\n\t\tpath(path: \"LICENSE\") {\n\t\t\tname\n\t\t\tobject {\n\t\t\t\t... on TextBlob {\n\t\t\t\t\ttext\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treferences {\n\t\t\tresults {\n\t\t\t\tname\n\t\t\t}\n\t\t}\n\t\tlog {\n\t\t\tresults {\n\t\t\t\tshortId\n\t\t\t\tauthor {\n\t\t\t\t\tname\n\t\t\t\t\temail\n\t\t\t\t\ttime\n\t\t\t\t}\n\t\t\t\tmessage\n\t\t\t}\n\t\t}\n\t}\n}\n")
	op.Var("name", name)
	var respData struct {
		RepositoryByName *Repository
	}
	err = client.Execute(ctx, op, &respData)
	return respData.RepositoryByName, err
}

func LicenseByRepositoryName(client *gqlclient.Client, ctx context.Context, name string) (repositoryByName *Repository, err error) {
	op := gqlclient.NewOperation("query licenseByRepositoryByName ($name: String!) {\n\trepositoryByName(name: $name) {\n\t\tpath(path: \"LICENSE\") {\n\t\t\tname\n\t\t\tobject {\n\t\t\t\t... on TextBlob {\n\t\t\t\t\ttext\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n")
	op.Var("name", name)
	var respData struct {
		RepositoryByName *Repository
diff --git a/srht/gitsrht/operations.graphql b/srht/gitsrht/operations.graphql
index 57d2cfd..3ec5ad1 100644
--- a/srht/gitsrht/operations.graphql
+++ b/srht/gitsrht/operations.graphql
@@ -45,6 +45,20 @@ query repositoryByName($name: String!) {
    }
}


query licenseByRepositoryName($name: String!) {
    repositoryByName(name: $name) {
        path(path: "LICENSE") {
            name
            object {
                ... on TextBlob {
                    text
                }
            }
        }
    }
}

query repositories {
    repositories {
        ...repos
-- 
2.34.1
Details
Message ID
<VVEqWeSlVFfoMZ2hNXzVOcwM-VtrM6VF_vBqhaMIw4dtqyuGvleSLKbEU2Cxd8ync0sEEg0NfB3SzUYhdIZLRZ1eq0K9nUzT23bTaLVfi0I=@emersion.fr>
In-Reply-To
<20220122233022.1256016-1-renato.torres@protonmail.com> (view parent)
DKIM signature
pass
Download raw message
On Sunday, January 23rd, 2022 at 00:31, Renato Torres <renato.torres@protonmail.com> wrote:
> This is not a final patch.
>
> Following one of the comments from Thorben Günther regarding the git
> show command, I did a spike to show the license of a repository.
>
> The command to show the license is:
>
> go run . git show hut --license
> go run . git show hut -l
>
> If used with the "license" flag the git show command shows only the
> license, shipping all the other information. This allows to fetch the
> license and use the output to feed other scripts.
>
> I was able to get the LICENSE contents by defining the query
> licenseByRepositoryName.
>
> I had an issue with this query because the Go structure returned is
> an *Object and therefore I could not cast to a TextBlob to get the
> contents of the file.
>
> To make it work I changed manually the gql.go (I know I cannot do it)
> to have the Object field be a map[string]interface{} instead of *Object:
>
> type TreeEntry struct {
> 	Id     string                 `json:"id"`
> 	Name   string                 `json:"name"`
> 	Object map[string]interface{} `json:"object"` // change
> 	Mode   int32                  `json:"mode"`
> }
>
> Questions:
>
> 1. Is this feature usefull?

I wonder. Maybe a more generally useful feature could be a cat-file command.

> 2. Is there a better way to get a file?

The current way sounds good.

> 3. Should we change gqlclientgen to define a Go interface{}
> when the type in the schema.graphqls is an interface?

I've been wondering about this, and I'm not sure a Go interface is the best
way to represent a GraphQL interface. Go interfaces describe common methods,
here what we want to do is describe common struct fields. Moreover, unless the
user explicitly asks for the special GraphQL "__typename" field, gqlclient has
no way to know the real underlying type of an Object.

Maybe instead we can generate a method on Object which takes care of the
unwrapping.

    type Object struct {
        Name string
        …

        raw json.RawMessage
    }

    func (object *Object) AsTextBlob() (*TextBlob, error) {
        var textBlob TextBlob
        err := json.Decode(object.raw, &textBlob)
        return textBlob, err
    }

Ideally we'd check "__typename" too, and fail on mismatch. Maybe it would be a
good idea to require the user to request "__typename" for queries containing
type conditions, to make sure we don't perform any erroneous type conversion.

Maybe we could just have a single method to unwrap the interface:

    type Object struct {
        Name string
        …

        unwrapped interface{}
    }

    func (object *Object) UnmarshalJSON(b []byte) error {
        // Unmarshal the interface fields
        …

        // Unmarshal the real underlying type
        switch typeName {
        case "TextBlob":
            var textBlob TextBlob
            err := json.Decode(object.raw, &textBlob)
            return textBlob, err
        default:
            return nil, fmt.Errorf("unexpected __typename %q", typeName)
        }
    }

    func (object *Object) Unwrap() interface{} {
        switch object.unwrapped
    }

This would require us to know about all implementations of a GraphQL interface
at generation time. It would seem like this is a promise we can make for simple
cases, but not sure about federated GraphQL schemas and schemas split into
multiple files. Would need to do more research.

Any other ideas?

Ultimately we can always pick a solution and change it later if needed.
gqlclient is still in the early stages and we can make breaking changes if
deemed necessary.
Details
Message ID
<CHCVEDAG1NOA.3OHNG8NXUR1CG@mint2>
In-Reply-To
<VVEqWeSlVFfoMZ2hNXzVOcwM-VtrM6VF_vBqhaMIw4dtqyuGvleSLKbEU2Cxd8ync0sEEg0NfB3SzUYhdIZLRZ1eq0K9nUzT23bTaLVfi0I=@emersion.fr> (view parent)
DKIM signature
pass
Download raw message
> I wonder. Maybe a more generally useful feature could be a cat-file
> command.

Sounds good.

> I've been wondering about this, and I'm not sure a Go interface is the 
> best way to represent a GraphQL interface. (...)

I'm checking your suggestions, I have no previous experience with
graphql nor gqlclientgen.

Just adding extra information.

Previouslly I had requested the raw fields and they were empty.

For example:

query licenseByRepositoryName($name: String!) {
    repositoryByName(name: $name) {
        path(path: "LICENSE") {
            name
            object {
                id
                shortId
                raw
                __typename
                ... on TextBlob {
                    text
                }
            }
        }
    }
}

Returns:

{
  "data": {
    "repositoryByName": {
      "path": {
        "name": "LICENSE",
        "object": {
          "id": "0ad25db4bd1d86c452db3f9602ccdbe172438f52",
          "shortId": "0ad25db",
          "raw": "",
          "__typename": "TextBlob",
          "text": "                    GNU AFFERO GENERAL ..."
        }
      }
    }
  }
}

I took a look a the git.sr.ht API and found the following TODO at
api/graph/model/object.go:

...

func LookupObject(repo *RepoWrapper, hash plumbing.Hash) (Object, error) {
	repo.Lock()
	obj, err := repo.Object(plumbing.AnyObject, hash)
	repo.Unlock()
	if err != nil {
		return nil, fmt.Errorf("lookup object %s: %w", hash.String(), err)
	}
	// TODO: Add raw object data, if requested
	switch obj := obj.(type) {
	case *object.Commit:
		return CommitFromObject(repo, obj), nil
	case *object.Tree:
		return TreeFromObject(repo, obj), nil
	case *object.Blob:
		return BlobFromObject(repo, obj), nil
	default:
		return nil, fmt.Errorf("Unknown object type %T", obj)
	}
}

We would need first to add the raw support.
Details
Message ID
<CHCXY7MI4TNI.15PV94O7IM34Q@mint2>
In-Reply-To
<VVEqWeSlVFfoMZ2hNXzVOcwM-VtrM6VF_vBqhaMIw4dtqyuGvleSLKbEU2Cxd8ync0sEEg0NfB3SzUYhdIZLRZ1eq0K9nUzT23bTaLVfi0I=@emersion.fr> (view parent)
DKIM signature
pass
Download raw message
> Any other ideas?

The client can have an ExecuteRaw method to return json.RawMessage, 
giving developers the possibility to decode the JSON in scenarios with
complicated nested interfaces:

func (c *Client) ExecuteRaw(ctx context.Context,
                            op *Operation) (*json.RawMessage, error) {
    // ...
}

The generate code in gql.go could be augmented with functions like:

func RepositoryByNameRaw(client *gqlclient.Client, 
    ctx context.Context, name string) (message **json.RawMessage, err error) {

    // ...
}

This is not as friendly as having well defined structures but unlocks
the possibility to tackle cases we cannot forsee or quickly add support
to.

What do you think?
Details
Message ID
<R_U-YApCNRJAAxTbZ4yIlsn0KKEDhOcnZSfs8wAtt2L8MdAM7ARLc4JYMOwgtSnqo4z6Xxm9zaiYfze-4mfGpocgY_gW306tEdrY_ZExxKc=@emersion.fr>
In-Reply-To
<CHCXY7MI4TNI.15PV94O7IM34Q@mint2> (view parent)
DKIM signature
pass
Download raw message
Users can already do this with something like

    var raw json.RawMessage
    client.Execute(ctx, op, &raw)

(See for instance the `hut graphql` command.)

I'd prefer to add proper support for all GraphQL features, instead of
providing additional ways for users to shoot themselves in the foot.
Details
Message ID
<CHD9MDHMB3F1.2VFOFYZCZ9B69@mint2>
In-Reply-To
<R_U-YApCNRJAAxTbZ4yIlsn0KKEDhOcnZSfs8wAtt2L8MdAM7ARLc4JYMOwgtSnqo4z6Xxm9zaiYfze-4mfGpocgY_gW306tEdrY_ZExxKc=@emersion.fr> (view parent)
DKIM signature
pass
Download raw message
I was exploring your comments a bit further (and learned a lot about
GraphQL...).

If I understood correctly we cannot use "raw" as a generic solution
because that's a specific field from the git.sr.ht GraphQL API.

I've hacked gqlclient to explore a solution for this.

Consider the following schema ("stolen" from 99designs/gqlgen):

```graphql
interface Character {
  # ...
}
type Human implements Character {
  # ...
}
type Droid implements Character {
  # ...
}
```

Instead of generating:

```go
type Character struct {
  // ...
}
type Human struct {
  // ...
}
type Droid struct {
  // ...
}
```

We would generate:

```go
type Character interface{}
type CharacterFields struct {
  // ...
}
type Human struct {
  // ...
}
type Droid struct {
  // ...
}
```

And the following methods:

```go
func (i *Character) AsCharacterFields() *CharacterFields {
  bytes, _ := json.Marshal(i)
  charFields = new(CharacterFields)
  json.Unmarshal(bytes, &charFields)
  return charFields
}
func (i *Character) AsHuman() *Human {
  bytes, _ := json.Marshal(i)
  obj = new(Human)
  json.Unmarshal(bytes, &obj)
  return obj
}

// Etc....
```

The operations remain unchanged:

```go
func Characters(client *gqlclient.Client, 
  ctx context.Context) (characters []Character, err error) {

  op := gqlclient.NewOperation("query characters ... ")
  var respData struct {
    Characters []Character
  }
  err = client.Execute(ctx, op, &respData)
  return respData.Characters, err
}

```

I don't like very much the "Fields" suffix, eventually we would need
to find a better naming schema.

Additionally this has the drawback of having to use "AsCharacterFields"
to access the fields declared in the GraphQL interface, something that
we don't need with the current approach.

> It would seem like this is a promise we can make for simple
> cases, but not sure about federated GraphQL schemas and schemas split
> into multiple files.  Would need to do more research.

I think with this approach we wouldn't have problems with other cases,
because it does not require us to know about all implementations of a
GraphQL interface at generation time (but I haven't done a proper 
research on this...).

Example in cmd/gqlclientgen/main.go:

```go
// ...
func genDef(schema *ast.Schema, def *ast.Definition) *jen.Statement {
  switch def.Kind {
  // ...
  case ast.Object:
    var stmts []jen.Code
    var fields []jen.Code
    for _, field := range def.Fields {
      //...
    }
    for _, i := range def.Interfaces {
      interfaceName := i
      stmts = append(stmts, jen.Line())
      stmts = append(stmts,
        jen.Func().Params(jen.Id("i").Op("*").Id(interfaceName)).
          Id("As"+def.Name).Params().Params(jen.Op("*").
          Id(def.Name)).Block(
          jen.Id("bytes, _").Op(":=").
            Qual("encoding/json", "Marshal").Call(jen.Id("i")),
          jen.Id("obj").Op("=").New(jen.Id(def.Name)),
          jen.Qual("encoding/json",
            "Unmarshal").Call(jen.Id("bytes"), jen.Id("&obj")),
          jen.Return(jen.Id("obj"))))
    }
  // ...
}
```

What do you think? I can send a gqlclient patch if you want to take
a look at the code I used to generate this.
Details
Message ID
<G87oqACN6Q_OWpURN4Fnvt7MHa8MjfZTiB67-aEdAPsal6ndYGezJ6ayY8VVgarSq_AHMzov3trVb1Yuj7wH5AfQfHmy4c4_FaRxBuBDDr4=@emersion.fr>
In-Reply-To
<CHD9MDHMB3F1.2VFOFYZCZ9B69@mint2> (view parent)
DKIM signature
pass
Download raw message
On Sunday, January 23rd, 2022 at 19:29, Renato Torres <renato.torres@protonmail.com> wrote:

> I was exploring your comments a bit further (and learned a lot about
> GraphQL...).
>
> If I understood correctly we cannot use "raw" as a generic solution
> because that's a specific field from the git.sr.ht GraphQL API.

We should be able to accommodate for name collisions. If we had an unexported
"raw" field in the struct then it'll get ignored by encoding/json and an
exported "Raw" field can co-exist.

> I've hacked gqlclient to explore a solution for this.
>
> Consider the following schema ("stolen" from 99designs/gqlgen):
>
> ```graphql
> interface Character {
>   # ...
> }
> type Human implements Character {
>   # ...
> }
> type Droid implements Character {
>   # ...
> }
> ```
>
> Instead of generating:
>
> ```go
> type Character struct {
>   // ...
> }
> type Human struct {
>   // ...
> }
> type Droid struct {
>   // ...
> }
> ```
>
> We would generate:
>
> ```go
> type Character interface{}

Not a fan of this, because:

- Character is an empty interface, so all types will satisfy it.
- You can't do anything useful with just a Character, since it has no methods?
- gqlclient would need to always know the concrete type of the Character to
  make it so users can type-switch.

But below it seems like methods are added to Character, so not sure how this
all ties together.

> type CharacterFields struct {
>   // ...
> }
> type Human struct {
>   // ...
> }
> type Droid struct {
>   // ...
> }
> ```
>
> And the following methods:
>
> ```go
> func (i *Character) AsCharacterFields() *CharacterFields {
>   bytes, _ := json.Marshal(i)
>   charFields = new(CharacterFields)
>   json.Unmarshal(bytes, &charFields)
>   return charFields
> }
> func (i *Character) AsHuman() *Human {
>   bytes, _ := json.Marshal(i)
>   obj = new(Human)
>   json.Unmarshal(bytes, &obj)
>   return obj
> }
>
> // Etc....
> ```

It's not possible to define methods on an interface type. And generating these
requires knowing about all possible Character implementations?

> The operations remain unchanged:
>
> ```go
> func Characters(client *gqlclient.Client,
>   ctx context.Context) (characters []Character, err error) {
>
>   op := gqlclient.NewOperation("query characters ... ")
>   var respData struct {
>     Characters []Character
>   }
>   err = client.Execute(ctx, op, &respData)
>   return respData.Characters, err
> }
>
> ```

encoding/json is not able to unmarshal interface{} types.

> I don't like very much the "Fields" suffix, eventually we would need
> to find a better naming schema.
>
> Additionally this has the drawback of having to use "AsCharacterFields"
> to access the fields declared in the GraphQL interface, something that
> we don't need with the current approach.
>
> > It would seem like this is a promise we can make for simple
> > cases, but not sure about federated GraphQL schemas and schemas split
> > into multiple files.  Would need to do more research.
>
> I think with this approach we wouldn't have problems with other cases,
> because it does not require us to know about all implementations of a
> GraphQL interface at generation time (but I haven't done a proper
> research on this...).
>
> Example in cmd/gqlclientgen/main.go:
>
> ```go
> // ...
> func genDef(schema *ast.Schema, def *ast.Definition) *jen.Statement {
>   switch def.Kind {
>   // ...
>   case ast.Object:
>     var stmts []jen.Code
>     var fields []jen.Code
>     for _, field := range def.Fields {
>       //...
>     }
>     for _, i := range def.Interfaces {
>       interfaceName := i
>       stmts = append(stmts, jen.Line())
>       stmts = append(stmts,
>         jen.Func().Params(jen.Id("i").Op("*").Id(interfaceName)).
>           Id("As"+def.Name).Params().Params(jen.Op("*").
>           Id(def.Name)).Block(
>           jen.Id("bytes, _").Op(":=").
>             Qual("encoding/json", "Marshal").Call(jen.Id("i")),
>           jen.Id("obj").Op("=").New(jen.Id(def.Name)),
>           jen.Qual("encoding/json",
>             "Unmarshal").Call(jen.Id("bytes"), jen.Id("&obj")),
>           jen.Return(jen.Id("obj"))))
>     }
>   // ...
> }
> ```
>
> What do you think? I can send a gqlclient patch if you want to take
> a look at the code I used to generate this.

I think I'm not fully understanding your proposal yet. Please enlighten me. :)
Reply to thread Export thread (mbox)