Giter Site home page Giter Site logo

zaid-ajaj / snowflaqe Goto Github PK

View Code? Open in Web Editor NEW
154.0 8.0 25.0 1.12 MB

A dotnet CLI to generate type-safe GraphQL clients for F# and Fable with automatic deserialization, static query verification and type checking

License: MIT License

F# 100.00%
graphql-schema dotnet-cli fable graphql code-generation fsharp

snowflaqe's Introduction

Snowflaqe Build status Nuget

A dotnet CLI tool for generating type-safe GraphQL clients for F#.

Features

  • Static query analysis, verification and type checking against a remote or local GraphQL schema.
  • Generating idiomatic F# types from GraphQL queries
  • Generating type-safe Fable or F# GraphQL client project in a single command. This project can be then referenced by your application and will be ready to go.
  • Supports GraphQL interfaces and unions
  • Resolves type name collisions when re-using types in a single query
  • Generates a HTTP client with functions that correspond to the query or mutation names. These functions handle exact JSON deserialization for the returned data.
  • Tested with different GraphQL backends generated from graphql-dotnet, hasura, postgraphile and absinthe.

Installation

Install as a global dotnet CLI tool

dotnet tool install snowflaqe -g

Using The Tool

Create a JSON file called snowflaqe.json with the following shape:

{
    "schema": "<schema>",
    "queries": "<queries>",
    "project": "<project>",
    "output": "<output>"
    ["target"]: "<target>",
    ["errorType"]: <custom error type>,
    ["overrideClientName"]: "<clientName>",
    ["copyLocalLockFileAssemblies"]: <true | false>,
    ["emitMetadata"]: <true | false>,
    ["createProjectFile"]: <true | false>,
    ["normalizeEnumCases"]: <true | false>,
    ["asyncReturnType"]: <"async" | "task">
    ["serializer"]: <"newtonsoft" | "system">
}

Where

  • <schema> can be one of:
    • A URL to the GraphQL backend
    • A relative path to another JSON file containing the output of the standard Introspection query which you can execute against the backend yourself (this allows for offline verification and type-checking)
    • A relative path to a file with extension .gql or .graphql containing the schema definitions and types
  • <queries> is an absolute or relative path to a directory that contains *.gql or *.graphql files that contain individual GraphQL queries that snowflaqe will run the verification against.
  • <project> is the name of the project will be generated.
  • <output> is an absolute or relative path to a directory where the project will be generated. It is recommended to use an empty directory relative to configuration file because each time you generate and regenarate the project, this directory is cleaned.
  • <errorType> optional custom error type to be generated. See below to learn more.
  • <clientName> optional name for the GraphqlClient class which is {project}GraphqlClient by default when you don't provide this property.
  • <copyLocalLockFileAssemblies> Adds the attribute to the generated F# project for scenarios where embedding the dependencies is required
  • <emitMetadata> when set to true, emits code generation metadata as comments into the output modules. The option is set to false by default.
  • <normalizeEnumCases> determines whether enum case names should be normalized (true by default when omitted)
  • createProjectFile determines whether snowflaqe should output a .fsproj file when set to true (default value) or output a .props file instead when set to false
  • asyncReturnType when targetting F# on dotnet, determines the output type of the client functions, whether they return Async<'T> when this option is set to "async" or return Task<'T> when set to "task". This option is not compatible for Fable projects since Fable doesn't support tasks.
  • serializer: for fsharp targets, determines whether to use Newtonsoft.Json (default) as the JSON serializer or System.Text.Json when set to "system".
  • <target> optional the code-generation target which can either be fable (default), fsharp or shared.

Using shared as a code generation target actually builds 3 projects! One contains just the types and can be shared across platforms. The other two reference this shared projects and implement Fable specific client and dotnet specific client, respectively.

After creating the configuration file. You can cd your way to where you have the config file and run:

snowflaqe

which will by default only do static query verification and static type-checking against the <schema>. You can also reference the configuration file in another directory via a relative path:

snowflaqe --config ./src/snowflaqe.json

In this case, the file doesn't necessarily have to be called snowflaqe.json.

Generate Client Project

snowflaqe --generate

snowflaqe --config ./path/to/snowflaqe.json --generate

Will generate a full project in the designated output directory. You can start using the generated project by referencing it from another project using a reference as follows:

<ProjectReference Include=".\path\to\generated\Project.fsproj" />

You can either do this manually or using an IDE such as Visual Studio or Rider that allow you to Right Click -> Add Existing Project.

You don't need to reference extra packages or anything, once you dotnet restore the generated project will pull in the packages that it requires.

It is worth mentioning that the generated project will target netstandard2.0 which means you can use anywhere in .NET Core or even in full .NET Framework v4.7.1+

Screenshots

Here are screenshots of how such generated project looks like. In this case, we have a project generated from the Github GraphQL API (Fable target):

Given the query

GithubSearchQuery

The corresponding query types are generated

GithubSearch

All queries and mutations can be called from a dedicated GraphqlClient

GraphqlClient

Custom Error Type

By default, the error type that is generated in the global types looks like this:

type ErrorType = { message: string }

This type is important because every request you make to the GraphQL backend returns Result<Query, ErrorType list> but the errors that come back are usually determined by the backend and not exposed through the schema. That is why you can customize this error type using the errorType configuration element:

{
    "schema": "<schema>",
    "queries: "<queries>",
    "project": "<project>",
    "output": "<output>",
    "errorType": {
        "CustomErrorType": {
            "Message": "string"
            "Path": "string list"
            "RequestId": "string"
        }
    }
}

which will generate:

type CustomErrorType = {
    Message: string
    Path: string list
    RequestId: string
}

Not Supported

There are a couple of features of the GraphQL specs which snowflaqe doesn't (yet) know how to work with:

  • Subscriptions

snowflaqe's People

Contributors

adelarsq avatar anthony-mi avatar beauvankirk avatar danielstarck avatar dim-37 avatar etareduction avatar mattsonlyattack avatar njlr avatar numpsy avatar xperiandri avatar yakimych avatar zaid-ajaj avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

snowflaqe's Issues

not able to process GitHub schema file

I'm hitting a few bumps in the road while trying to run it. The hitch seems to be with parsing the schema file. I've tried two examples. First was the one I gathered from the FSharp.Data.GraphQL sample. It had a commenting style that was triple-double-quoted above and below each line.

Reading configuration from C:\repo\GitHubGraphQL\snowflaqe.json โณ Loading GraphQL schema from ./schema.docs.graphql Syntax Error GraphQL (1:1) Unexpected String "" 1: """ ^ 2: Defines what type of global IDs are accepted for a mutation argument of type ID.

Then I tried another that came from running the introspection query which gave back a json result. That too hit an error, but this time on a null.

Reading configuration from C:\repo\GitHubGraphQL\snowflaqe.json โณ Loading GraphQL schema from ./schema.docs.json Value cannot be null. (Parameter 'source')

So I'm stuck - to a first approximation. I'm continuing to work on the file handling for updating/mutating the CSV, and will save the best for last. ๐Ÿ˜ I'm attaching both files here. Thanks in advance for giving it a once-over when you can.

schema.docs.zip

Support generated projects with dots in the namespace

Hello Zaid, I've encountered some "strange" behaviour. When I define Snoflaqe.json file like this

    "schema": "http://localhost:8085/graphql",
    "queries": "./gql-queries",
    "project": "Cookbook.GraphQL.Client",
    "output": "./gql-output",
    "overrideClientName": "ClientGraphqlClient"
}

then the namespaces in the generated project are created in quotes like this

namespace rec ``Cookbook.GraphQL.Client``

and project in this state throws compilation errors.

Is this "by design" or did I encountered some bug?

It can be solved by removing the dots from the project name but then I endup with project whose name doesn't match the rest of the projects in sln.

CompiledName attribute not working for union type members

Generated union types currently contain members with different casing than the original enums.
Problem is that CompiledName attribute does not work with members of union type. It works with values and functions.

Example

Following code shows that value of PPA generates a property Ppa which causes a problem during the runtime.

enum MyEnumType { Shopping PPA }

produces

type ProductLabelDomainType = | [<CompiledName "Shopping">] Shopping | [<CompiledName "PPA">] Ppa

Errors on --generate (I have no \projects or \repo\Azure directories)

My repo is in C:\repo. I don't have a C:\repo\Azure path. I also don't have a C:\projects path. Should I be rebuilding Snowflaqe from source so that it has the right dependency paths?

`PS C:\repo\GitHubGraphQL> snowflaqe --generate

 _____                      __ _
/  ___|                    / _| |
\ `--. _ __   _____      _| |_| | __ _  __ _  ___
 `--. \ '_ \ / _ \ \ /\ / /  _| |/ _` |/ _` |/ _ \
/\__/ / | | | (_) \ V  V /| | | | (_| | (_| |  __/
\____/|_| |_|\___/ \_/\_/ |_| |_|\__,_|\__, |\___|
                                          | |
                                          |_|

โค๏ธ  Open source https://www.github.com/Zaid-Ajaj/Snowflaqe
โš–๏ธ  MIT LICENSE

โณ Loading GraphQL schema from ./schema.docs2.json
Unhandled exception. System.UnauthorizedAccessException: Access to the path 'c:\repo\Azure\CosmosDB\azure-cosmos-db-graph-gremlindotnet-getting-started.git\objects\pack\pack-c96e57ccb981e79d47f974f04a19c0b27d2911de.idx' is denied.
at System.IO.FileSystem.DeleteFile(String fullPath)
at System.IO.File.Delete(String path)
at Program.deleteFilesAndFolders(String directory, Boolean isRoot) in C:\projects\Snowflaqe\src\Program.fs:line 219
at Program.deleteFilesAndFolders(String directory, Boolean isRoot) in C:\projects\Snowflaqe\src\Program.fs:line 222
at Program.deleteFilesAndFolders(String directory, Boolean isRoot) in C:\projects\Snowflaqe\src\Program.fs:line 222
at Program.deleteFilesAndFolders(String directory, Boolean isRoot) in C:\projects\Snowflaqe\src\Program.fs:line 222
at Program.deleteFilesAndFolders(String directory, Boolean isRoot) in C:\projects\Snowflaqe\src\Program.fs:line 222
at Program.deleteFilesAndFolders(String directory, Boolean isRoot) in C:\projects\Snowflaqe\src\Program.fs:line 222
at Program.deleteFilesAndFolders(String directory, Boolean isRoot) in C:\projects\Snowflaqe\src\Program.fs:line 222
at Program.generate(String configFile) in C:\projects\Snowflaqe\src\Program.fs:line 280
at Program.main(String[] argv) in C:\projects\Snowflaqe\src\Program.fs:line 435
PS C:\repo\GitHubGraphQL>`

Generate props file with referenced fs files

It would be nice to have a props file generated. This will allow scenario:

client.fsproj: <Import Project="output/generated.props" />

/output
<generated F# files>
<project name>.props

    <ItemGroup>
        <Compile Include="Query1.fs" />
        ...
    </ItemGroup>

Problems when using generated input types from Hasura backend

I'm facing several issues trying to execute mutation on a F# backend towards Hasura, sending instances of the generated input types, such as

type Example_insert_input =
    { AnotherType: Option<AnotherType_obj_rel_insert_input>
      anotherType: Option<string>
      id: Option<string>
    }

When using default serializer Newtonsoft.Json, it throws an exception A member with the name 'anotherType' already exists on 'KrevS2Graphql.SkillModelVersion_insert_input'. Use the JsonPropertyAttribute to specify another name.

When using System.Text.Json ("system") serializer, Hasura return an error due to a mismatch between discriminated union names. Upon further investigation, this is caused by the fact that the CompiledName attributed is not respected.
Next step is to set "normalizeEnumCases" to false.
This solves the previous issues, but another error is given back by Hasura: expected an object for type 'Example_bool_exp', but found null. So, it looks like properties set to None on the various types generated by Snowflaqe are not serialized in a way that Hasura correctly receive them - I think those properties should be excluded, instead of defined with a null value.

How to use the generated code?

The readme shows how to generate the code, but not how to actually send the graphql queries and use the generated types. Also, do I have to manually include the generated code in the project file?
Really appreciate some instructions on this!

The tool works flawlessly otherwise ๐Ÿ™‚

Issue with query validation?

My schema:

type Foo {
  id: ID!
  name: String!
}

type Query {
  foo(id: ID!): Foo
}

My query:

query($id: ID!) {
  foo(id: $id) {
    id
    name
  }
}

But I get a validation error:

Field foo contains type mismatch for argument id of type ID! which references variable $id of type ID!

This is unexpected since id and $id have the same type (ID!).

Merge GraphQL support efforts

Currently we have your amaring client as generator and github.com/FSProjecrs/FSharp.Data.GraphQL as server and type provider.
Maybe it worse to merge efforts and integrate both projects together?

Add comment to indicate auto-generated code

Hi, would be good if the auto-generated code would contain some comment header to indicate it was auto-generated by snowflaqe, maybe even including the date / GraphQL URI / name of query file, and a link to the snowflaqe repo of course.
This would avoid people manually editing these files.

Discussion: annotation / auto-conversion / post-processing GraphQL responses into domain types?

This is more of a question and discussion rather than a bug.

Let's say I have a custom type in my model like this:

open System

type ProductID = 
  | Apple of Guid
  | Banana of Guid
  | Cherry of Guid
  with 
    member this.ToString () =
      match this with
      | Apple g -> sprintf "apple/%A" g
      | Banana g -> sprintf "banana/%A" g
      | Cherry g -> sprintf "cherry/%A" g

module ProductID = 

  let tryParse (x : string) : ProductID option = 
    // match x.Split([| '/' |]) with
    // etc ...

All good idiomatic F#.

Now, in my GraphQL endpoint, the product ID is encoded as GraphQL ID, which is just a string.

So for a query like this:

query {
  products {
    id
    price
    # ...
  }
}

GraphQL might return this:

{
  "products": [
    {
      "id": "apple/157be5aa-d3c8-4894-befb-a739251a57bb",
      "price": 60
    },
    {
      "id": "banana/11751f22-7908-44c0-bb3b-59d9eddd0457",
      "price": 70
    }
  ]
}

Snowflaqe will (correctly) generate F# code that is something like this:

type Product = 
  {
    id : string
    price : int
    // ...
  }

type Query = 
  {
     products : Product list
  }

I want to consume this response in a Fable app, but now I have a choice to make. The generated Snowflaqe code has product.id as a string, but ideally I would have a ProductID.

Right now, the options I can see are:

  • Use the "stringy" ID, but miss out on features of the F# type system. This gives similar safety to JS code, so perhaps not much is lost.
  • Parse the ID on the fly when I need to match on it, like this id |> ProductID.tryParse |> Option.get. This might fail at run-time, but only in predictable places.
  • Write another type for Query that has everything properly parsed and do the parsing immediately after the fetch step:
asyncResult {
  let client = GraphQL.GraphqlClient "/graphql"
  let! resultUntyped = client.Query.products ()
  let! resultTyped = parseResult resultUntyped

  return resultTyped
}

This give ultimate safety, but it's lots of extra code.

The ProductID is just an example of this problem, but there are many other cases where it appears. I am currently using the "parse on-the-fly" approach in my own code.

Perhaps there should be some way of configuring Snowflaqe to integrate the parsing in the fetch process? Or maybe I am missing a simple pattern for solving this? I'm not sure what this would look like, so I thought it would be best to start a discussion here.

It might also be interesting to leverage Thoth.Json decoders here, since I usually already have those for other purposes.

Bigint fields generated as string

Thanks for sharing another great project!
I was playing around with this and noticed that bigint fields are translated to String in the generated code. Is this by design ?

Field id is a bigint in the schema and a string in the generated code.

Relevant portion of schema.graphql.

type lab_samples {
  ...
  id: bigint!
  notes: String
  ...
  workorder_id: bigint
}

From query.gql

query MyQuery($workorderId: bigint!) {
  lab_samples(where: {deleted: {_eq: false}, workorder_id: {_eq: $workorderId}}) {
      notes
      ...
      id
      ...
    }
}

In Types.fs

type lab_samples_insert_input =
    { ...
      id: Option<string>
      notes: Option<string>
      ...
      workorder_id: Option<string> }

From generates Query.fs

type lab_samples =
    { notes: Option<string>
     ...
      id: string
      ... }

Validate inline fragments on GraphQL objects

Using an inline fragment with type condition should be validated when used as part of the selection set of a GraphQL object. Sample incorrect query (see this comment)

query GetPullRequests($org: String!) {
  organization(login: $org) {
    name
    url
    repositories(first: 100) {
      nodes {
        name
        pullRequests(first: 100, states: OPEN) {
          nodes {
            number
            title
            url
            body
            author {
              login
            }
            reviews(last: 10, states: APPROVED) {
              nodes {
                author {
                  login
                }
                ... on User {
                    __typename
                    bio
                    id
                  }
              }
            }
          }
        }
      }
    }
  }
}

Readme instructions don't work

The first thing to do according to the instructions is dotnet install snowflaqe -g, which fails.
The correct one is dotnet tool install snowflaqe -g, found it here.

Any chance the readme could be corrected?

Query with parameters gives 400: Bad Request

When I'm using a query with parameters, I'm getting an error: { message = "400: Bad Request" }

// with params
query getDataByImo ($imo: Imo!){
                  ships(imo: [$imo]) {
                    pageInfo {
                      hasNextPage
                      endCursor
                    }
                    totalCount{ relation value }

When I'm not using parameters, that errors is gone, test is green.

// without params
query getDataByImo {
                  ships(imo: [9758428]) {
                    pageInfo {
                      hasNextPage
                      endCursor
                    }
                    totalCount{ relation value }

So maybe the resulting query text is not correctly generated.
From Fiddler I see that the generated query looks like in the attached file:
query.txt

Control over GraphqlClient type name?

Suppose I have a config like this:

{
  "schema": "./introspection.json",
  "queries": "./queries",
  "project": "My.GraphQL.Project",
  "output": "./graphql",
  "target": "fable"
}

Then my generated project will have a type like this:

namespace My.GraphQL.Project

// ...

type My.GraphQL.ProjectGraphqlClient(url: string, headers: Header list) =
    new(url: string) = My.GraphQL.ProjectGraphqlClient(url, [ ])

    member _.LatestEntities() =
        async {

// ...

The namespace is what I expected but the type name of the client is a bit cumbersome.

Is there a way to control the type name of the client?

For example:

namespace My.GraphQL.Project

// ...

type GraphqlClient(url: string, headers: Header list) =
    new(url: string) = My.GraphQL.ProjectGraphqlClient(url, [ ])

    member _.LatestEntities() =
        async {

// ...

MSBuild task

With regard to #34 it would be nice to have an MSBuild task that can be invoked on-demand within a client project

Send more compact query

It seems that Snowflaqe currently sends the exact GraphQL queries and mutations as the ones defined in the GraphQL files. So given this query:

query Countries {
  name
}

This code will be generated in the GraphqlClient module:

member _.Countries() =
  async {
    let query = """
      query Countries {
        name
      }
      """

  // ...

Which has 2 drawbacks:

  1. The sent payload will be heavier (since it includes line returns and spaces)
  2. Some servers' libraries (like Elixir's Absinthe) don't support queries containing line returns (natively)

Is there an option to generate "compact" queries and mutations?

Cant build project with SAFE template (3.0.0-beta004) template

Hey!

I was following the github-api example and able to generate the files and run a dotnet restore on the output.
When I do dotnet run the build fails with:

client: C:/code/snowflaque-demo/src/output/Github.GraphqlClient.fs(6,17): (6,21) error FSHARP: The namespace 'Http' is not defined. (code 39)
client: C:/code/snowflaque-demo/src/output/Github.GraphqlClient.fs(13,51): (13,61) error FSHARP: The type 'HttpClient' is not
defined. (code 39)
client: C:/code/snowflaque-demo/src/output/Github.GraphqlClient.fs(17,53): (17,63) error FSHARP: The type 'HttpClient' is not
defined. (code 39)
client: C:/code/snowflaque-demo/src/output/Github.GraphqlClient.fs(77,17): (77,37) error FSHARP: Lookup on object of indeterminate type based on information prior to this program point. A type annotation may be needed prior to this program point to constrain the type of the obj
ect. This may allow the lookup to be resolved. (code 72)
client: C:/code/snowflaque-demo/src/output/Github.GraphqlClient.fs(80,52): (80,86) error FSHARP: Lookup on object of indeterminate type based on information prior to this program point. A type annotation may be needed prior to this program point to constrain the type of the obj
ect. This may allow the lookup to be resolved. (code 72)
client: C:/code/snowflaque-demo/src/output/Github.GraphqlClient.fs(83,19): (83,47) error FSHARP: Lookup on object of indeterminate type based on information prior to this program point. A type annotation may be needed prior to this program point to constrain the type of the obj
ect. This may allow the lookup to be resolved. (code 72)
client: C:/code/snowflaque-demo/src/output/Github.GraphqlClient.fs(86,21): (86,45) error FSHARP: A reference to the type 'Syst
em.ComponentModel.INotifyPropertyChanged' in assembly 'netstandard' was found, but the type could not be found in that assembly (code 1109)
client: C:/code/snowflaque-demo/src/output/Github.GraphqlClient.fs(87,24): (87,47) error FSHARP: A reference to the type 'Syst
em.ComponentModel.INotifyPropertyChanged' in assembly 'netstandard' was found, but the type could not be found in that assembly (code 1109)
client: C:/code/snowflaque-demo/src/output/Github.GraphqlClient.fs(88,24): (88,47) error FSHARP: A reference to the type 'Syst
em.ComponentModel.INotifyPropertyChanged' in assembly 'netstandard' was found, but the type could not be found in that assembly (code 1109)
client: C:/code/snowflaque-demo/src/output/Github.GraphqlClient.fs(91,36): (91,57) error FSHARP: A reference to the type 'Syst
em.ComponentModel.INotifyPropertyChanged' in assembly 'netstandard' was found, but the type could not be found in that assembly (code 1109)
client: C:/code/snowflaque-demo/src/output/Github.GraphqlClient.fs(94,36): (94,57) error FSHARP: A reference to the type 'Syst
em.ComponentModel.INotifyPropertyChanged' in assembly 'netstandard' was found, but the type could not be found in that assembly (code 1109)
client: C:/code/snowflaque-demo/src/output/Github.GraphqlClient.fs(98,32): (98,53) error FSHARP: A reference to the type 'Syst
em.ComponentModel.INotifyPropertyChanged' in assembly 'netstandard' was found, but the type could not be found in that assembly (code 1109)
client: C:/code/snowflaque-demo/src/output/Github.GraphqlClient.fs(134,17): (134,37) error FSHARP: Lookup on object of indeterminate type based on information prior to this program point. A type annotation may be needed prior to this program point to constrain the type of the object. This may allow the lookup to be resolved. (code 72)

Find the example code base
https://github.com/Akash-Mair/snowflaq-demo

query DSL

Hello,

not a problem, just questions.
I've run this against a hot chocolate server, and worked a treat...but the generated project is hard wired into Fable? What if I just want a vanilla core application?

I'm also interested to know, is there a DSL or maybe there's a DSL available in another library (hmmm..not sure how that would work), but I don't want to write Graphql and then generate/validate it, I want to write queries in F# that are consistent with the schema, and then have them generate graphql invisibly.

Reusing fragments between several queries

It would be nice to be able to define fragments in separate .gql files and then use them across several queries and mutations. However, if a .gql file contains only fragment, I get an error "No query was provided".

Is there a way to use fragments with snowflaqe so that it can generate a shared type in F# and then use it in other types for queries?

Tags is not a valid name for a DU case

The F# compiler does not allow a union case called Tags as it's a member used internally. Aa related issue with some discussion can be found here

Snowflaqe generates a Tags DU member if the GraphQL endpoint has a corresponding name (I stumbled upon it for the monday.com api).

This will cause Snowflaqe to generate a project that cannot be compiled.

A work around is to rename the generated member, but this has to be done after every project regeneration.

Handling of double-quoted Descriptions in SDL

Hi! Maybe I am missing something, but I am getting errors when trying to run snowflaqe --generate off of a more-or-less standard HotChocolate schema.graphql file. There are a number of directives and scalars at the end of the schema file, preceded with comments:

"The `@defer` directive may be provided for fragment spreads and inline fragments to inform the executor to delay the execution of the current fragment to indicate deprioritization of the current fragment. A query with `@defer` directive will cause the request to potentially return multiple responses, where non-deferred data is delivered in the initial response and data deferred is delivered in a subsequent response. `@include` and `@skip` take precedence over `@defer`."
directive @defer("If this argument label has a value other than null, it will be passed on to the result of this defer directive. This label is intended to give client applications a way to identify to which fragment a deferred result belongs to." label: String "Deferred when true." if: Boolean) on FRAGMENT_SPREAD | INLINE_FRAGMENT

"The `@specifiedBy` directive is used within the type system definition language to provide a URL for specifying the behavior of custom scalar definitions."
directive @specifiedBy("The specifiedBy URL points to a human-readable specification. This field will only read a result for scalar types." url: String!) on SCALAR

"The `@stream` directive may be provided for a field of `List` type so that the backend can leverage technology such as asynchronous iterators to provide a partial list in the initial response, and additional list items in subsequent responses. `@include` and `@skip` take precedence over `@stream`."
directive @stream("If this argument label has a value other than null, it will be passed on to the result of this stream directive. This label is intended to give client applications a way to identify to which fragment a streamed result belongs to." label: String "The initial elements that shall be send down to the consumer." initialCount: Int! "Streamed when true." if: Boolean!) on FIELD

"The `DateTime` scalar represents an ISO-8601 compliant date time type."
scalar DateTime @specifiedBy(url: "https:\/\/www.graphql-scalars.com\/date-time")

"The `Long` scalar type represents non-fractional signed whole 64-bit numeric values. Long can represent values between -(2^63) and 2^63 - 1."
scalar Long

The first error I get is

Syntax Error GraphQL (628:1) Unexpected String "The `@defer` directive..."

As far as I understand, those are just Descriptions according to the spec. If I remove them, however, I get the following error:

Expected Name, found String "If this argument label has a value other than null, it will be passed ...

From what I can see in this thread: ChilliCream/graphql-platform#4147 (comment), this SDL is as expected.

Exception when using custom scalars inside .graphql schema

Type generation fails with Unable to resolve reference to type MyCustomScalar if the schema contains custom scalars. This happens only if using .graphql schema when calling Introspection.fromSchemaDefinition. The reason for this is that when using schema first in graphql-dotnet you have to register your custom scalars directly after parsing the schema, here is the docs and here is how it looks like:

var schema = Schema.For(...);
schema.RegisterType(new MyBooleanGraphType());

I was working on a fix for this and my idea was to parse the schema to get the custom scalars out of it and register the types, which would look like this (pseudocode):

let graphqlServer = GraphQL.Types.Schema.For(definition, configureSchemaBuilder)
let types = GraphQLParser.Parser.Parse(definition) // parse the schema into a graphql document to filter scalars

getCustomScalars types |> buildGraphTypes |> |> Seq.iter graphqlServer.RegisterType
let schemaJson = graphqlServer.ExecuteAsync(...)

This seems to work, however it does seem pretty inefficient since the schema is parsed many times - first graphq-dotnet parses it in GraphQL.Types.Schema.For, then I parse it again to get the scalars, then Snowflaqe parses it once more to convert from json to its internal representation.

So I started thinking that maybe we could parse the result of GraphQLParser.Parser.Parse(definition) directly into the internal data structures Snowflaqe uses for type generation? This of course would only be necessary for schemas defined in .graphql. Any thoughts on this, @Zaid-Ajaj ?

FYI, @Yakimych @danielstarck ๐Ÿ™‚

[MSBuild] Smart way to generate code only if changed

I suppose we need to create a cache file with the MSBuild task version and a map of the file name and its hash.
In the next generation, the task will read the cache and compare the MSBuild task version. If it is different then remove all and generate.
Then compare files lists, if they are equivalent then hash each file and compare hashes.
If lists are different or any GraphQL file is different, then remove all and generate.

@dim-37 @Zaid-Ajaj what do you think?

Mapping of custom scalars

I am using hasura graphql api and for dates it generates custom scalars like date and timestamptz, which get mapped to the same types in F#, but there are no such types in F# ๐Ÿ™‚

Is there any way to provide custom mapping for those scalar types to F# types? I would happy even with strings there, but for now nothing compiles ๐Ÿ™‚

Thanks!

Complex object for errorType?

Is there a way to configure the errorType to be a more complex object?
Like an array/list of error objects.

"errors": [
    {
      "message": "Invalid cursor provided for the after parameter: a",
      "locations": [{ "line": 2, "column": 3 }],
      "path": ["vessels"],
      "extensions": {
        "code": "BAD_USER_INPUT"
      }
    }
  ]

Implement error handling option: "results" | "exceptions" for the fsharp target

Right now, the client methods generated per query come in two flavors and both return Result:

  • Asynchronous call returns Async<Result<Query, ErrorType list>>
  • Synchronous call returns Result<Query, ErrorType list>

Sometimes, using result can be tricky where users don't necessarily care about handling errors each request. A try-catch block might more suitable.

To improve the situation, add an option

{
  ["errorHandling"]: < "results" | "exceptions" >
}

where results is the default. When exceptions is used then the generated functions will use exceptions for error handling instead

  • Asynchronous call returns Async<Query>
  • Synchronous call returns Query

An exception type is generated

exception GraphqlError of ErrorType list

which is thrown when the GraphQL backend returns a non-OK response

Schema with unions throws NullReferenceException

Hi!
Looks like it is currently not possible to generate a client from a schema that contains Union types. In Snowflaqe we get a Object reference not set to an instance of an object, but we (@MargaretKrutikova and @danielstarck) tracked it down to the following line in graphql-dotnet. The problem is that when called like this, both union.ResolveType and union.IsTypeOf are null.

Not sure if we should open an issue there, since it's not entirely clear to me whether the way it is called from Snowflaqe is the intended usage. The examples in graphql-dotnet docs provide the actual types when calling Schema.For, while Snowflaqe only provides the shcema string. Please advice.

I've opened a PR with a simple repro here: #69

Create an F# client using just a HttpClient / Interop with HttpClientFactory

Hi,

I've only had a brief go with Snowflaqe so far and might be jumping in a bit quickly, but in case this counts a an interesting or reasonable thought:

I have an existing REST API using ASPCore and NSwag/OpenApi, and am having a play with GraphQl to see if it can simplify some things.
The consumers of that are using NSwag to generate C# client classes, and use HttpClientFactory with typed clients to generate class instances (that allows centralised configuration of HttpClient settings, plus supports wiring the clients into Polly and the host logging setup).
The NSwag client generator can generate classes with several different approaches of providing HttpClients and the server base URL, and we have it set up so that the generated classes only get given a pre-configured HttpClient and not the URL (which is nice when using DI as injecting plain strings into classes doesn't really work).

I had a go at wiring the Snowflaqe generated clients into the same DI setup and it doesn't work on its own because of the constructors taking strings (and/or having multiple constructors available).
This isn't a major problem because I can always register things in the DI container using factory delegates (and manually create a client class instance from a HttpClient I make myself and a null URL), but I wonder if it would be considered reasonable or useful to be able to configure it to generate clients with a single constructor that takes a HttpClient, and nothing else?

i.e., this is a wordy and roundabout request for the ability to generate clients whose constructor is just

type %s(httpClient: HttpClient) =

:-)

Implement async handling option: "async" | "task" for the fsharp target

Basically:

{
   ["asyncReturnType"]: < "async" | "task" >
}

Where async is the default. To make the generated functions either return Async<'T> or Task<'T>

For the latter you would need to include Ply as a dependency to the generated F# project.

Related to #29

Might need a better API for generating the projects and their packages.

GraphQLParser.dll is missing from Snowflaqe.Tasks nugget package

When I try to build a client as a separate project, I'm getting the following error:

error MSB4018: System.IO.FileNotFoundException: Could not load file or assembly 'GraphQLParser, Version=8.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified. 

My .fsproj file:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
  </PropertyGroup>

  <PropertyGroup>
    <SnowflaqeProjectName>Test.Client</SnowflaqeProjectName>
    <SnowflaqeQueriesFolder>../../graphql</SnowflaqeQueriesFolder>
    <SnowflaqeSchemaPath>../../graphql/schema.json</SnowflaqeSchemaPath>
    <SnowflaqeTargetProjectType>fsharp</SnowflaqeTargetProjectType>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="FSharp.SystemTextJson" Version="1.*" />
    <PackageReference Include="Snowflaqe.Tasks" Version="1.*">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
  </ItemGroup>
</Project>

To me it seems like some packaging issue, but I'm new to F#, so maybe I'm doing something wrong. Please advice.

Thank you!

Extensions of a response

Can you make the GraphqlSuccessResponse have the extensions of a response? It only has the data so far.
In our case we need to read the extensions to know how much quota we still have available for the API.

image

Similar to your comment about unions not be implemented yet, what about interfaces?

This works when querying github, though snowflaq cannot validate it w/"unknown field author"

{
	"name": "author",
	"description": "The actor who authored the comment.",
	"args": [],
	"type": {
		"kind": "INTERFACE",
		"name": "Actor",
		"ofType": null
	},
	"isDeprecated": false,
	"deprecationReason": null
},

query GetPullRequests($org: String!) {
  organization(login: $org) {
    name
    url
    repositories(first: 100) {
      nodes {
        name
        pullRequests(first: 100, states: OPEN) {
          nodes {
            number
            title
            url
            body
            author {
              login
            }
            reviews(last: 10, states: APPROVED) {
              nodes {
                author {
                  login
                }
              }
            }
          }
        }
      }
    }
  }
}

** this could just be me not knowing much about graphql...

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.