Skip to content

Commit

Permalink
improve cmd/ndc-go-sdk docs and template (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
hgiasac authored Mar 5, 2024
1 parent ad8e71b commit 033f448
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 149 deletions.
54 changes: 38 additions & 16 deletions cmd/ndc-go-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,15 @@ The `init` command generates a boilerplate project for connector development fro
- `README.md`: the index README file.
- `Dockerfile`: the build template for Docker image.

The command requires names of connector and module. By default, the tool creates a new folder with the connector name. If you want to customize the path, or generate files in the current folder. use `--output` (`-o`) argument.
The command requires names of the connector and module. By default, the tool creates a new folder with the connector name. If you want to customize the path or generate files in the current folder. use `--output` (`-o`) argument.

```bash
ndc-go-sdk init -n example -m github.com/foo/example -o .
```

### Generate queries and mutations

The `generate` command parses code in the `functions` folder, finds functions and types that are allowed to expose and generates following files:
The `generate` command parses code in the `functions` folder, finds functions and types that are allowed to be exposed and generates the following files:

- `schema.generated.json`: the generated connector schema in JSON format.
- `connector.generated.go`: implement `GetSchema`, `Query` and `Mutation` methods with exposed functions.
Expand All @@ -72,7 +72,7 @@ ndc-go-sdk generate

### Functions

Functions which are allowed to expose as queries or mutations need to have `Function` or `Procedure` prefix in name. For example:
Functions that are allowed to be exposed as queries or mutations need to have a `Function` or `Procedure` prefix in the name. For example:

```go
// FunctionHello sends a hello message
Expand Down Expand Up @@ -102,9 +102,9 @@ func Foo(ctx context.Context, state *types.State) (*FooResult, error)

Function and Procedure names will be formatted to `camelCase` by default.

> The generator detects comments by the nearby code position. It isn't perfectly accurate in some use cases. Prefix name in function is highly recommended.
> The generator detects comments by the nearby code position. It isn't perfectly accurate in some use cases. Prefixing name in the function is highly recommended.

A function must have 2 (no argument) or 3 parameters. `Context` and `State` are always present as 2 first parameters. The result is a tuple with a expected output and `error`.
A function must have 2 (no argument) or 3 parameters. `Context` and `State` are always present as 2 first parameters. The result is a tuple with an expected output and `error`.

> [Function](https://hasura.github.io/ndc-spec/specification/schema/functions.html) is a type of Query and [Procedure](https://hasura.github.io/ndc-spec/specification/schema/procedures.html) is a type of mutation. [Collection](https://hasura.github.io/ndc-spec/specification/schema/collections.html) is usually used for database queries so it isn't used for business logic.

Expand All @@ -113,7 +113,7 @@ A function must have 2 (no argument) or 3 parameters. `Context` and `State` are
The tool only infers arguments and result types of exposed functions to generate object type schemas:

- Argument type must be a struct with serializable properties.
- Result type can be a scalar, slice or struct.
- Result type can be a scalar, slice, or struct.

#### Object Types

Expand All @@ -125,7 +125,7 @@ type CreateAuthorResult struct {
Name string `json:"name"`
}

// auto generated
// auto-generated
// func (j CreateAuthorResult) ToMap() map[string]any {
// return map[string]any{
// "id": j.ID,
Expand Down Expand Up @@ -157,6 +157,17 @@ the schema will be:
}
```

#### Arguments

Arguments must be defined as struct types. The generator automatically infers argument types in functions to generate schemas and Encoder and Decoder methods. Value fields are treated as required and pointer fields are optional.

```go
type HelloArguments struct {
Greeting string `json:"greeting"` // value argument will be required
Count *int `json:"count"` // pointer arguments are optional
}
```

#### Scalar Types

**Supported types**
Expand All @@ -168,24 +179,24 @@ The basic scalar types supported are:
- `float32`, `float64` (NDC scalar type: `Float`)
- `bool` (NDC scalar type: `Boolean`)
- `time.Time` (NDC scalar type: `DateTime`, represented as an [ISO formatted](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) string in JSON)
- `time.Duration` (NDC scalar type: `Duration`, represented as a duration string in JSON)
- `github.com/google/uuid.UUID` (NDC scalar type: `UUID`, represented as an UUID string in JSON)
- `time.Duration` (NDC scalar type: `Duration`, represented as an int64 nanosecond duration in JSON)
- `github.com/google/uuid.UUID` (NDC scalar type: `UUID`, represented as a UUID string in JSON)

Alias scalar types will be inferred to the origin type in schema.
Alias scalar types will be inferred to the origin type in the schema.

```go
// the scalar type in schema is still a `String`.
type Text string
```

If you want to define a custom scalar type, the type name must have a `Scalar` prefix or `@scalar` tag the comment. The generator doesn't care about the underlying type even it is a struct.
If you want to define a custom scalar type, the type name must have a `Scalar` prefix or `@scalar` tag in the comment. The generator doesn't care about the underlying type even if it is a struct.

```go
type ScalarFoo struct {
Bar string
bar string
}
// output: Foo
// auto generated
// auto-generated
// func (j ScalarFoo) ScalarName() string {
// return "Foo"
// }
Expand All @@ -202,20 +213,31 @@ type Foo struct {}
// output: Bar
```

> The generator detects comments by the nearby position. It isn't perfectly accurate in some use cases. Prefix name in function is highly recommended.
> The generator detects comments by the nearby position. It isn't perfectly accurate in some use cases. Prefix name in the function is highly recommended.
For custom scalar, you must implement a method to decode `any` value so its data can be set when resolving request query arguments. `UnmarshalJSON` is used when encoding results.
For custom scalar, you must implement a method to decode `any` value so its data can be set when resolving request query arguments. `UnmarshalJSON` is also used when encoding results.

```go
func (c *ScalarFoo) FromValue(value any) (err error) {
c.Bar, err = utils.DecodeString(value)
return
}

func (c *ScalarFoo) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}

c.bar = s

return nil
}
```

### Documentation

The tool parses comments of functions and types by the nearby code position to description properties in the schema. For example:
The tool parses comments of functions and types by the nearby code position to describe properties in the schema. For example:

```go
// Creates an author
Expand Down
78 changes: 72 additions & 6 deletions cmd/ndc-go-sdk/templates/new/functions/hello.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,62 @@ import (
"{{.Module}}/types"
)

// The generator infers the argument type in FunctionHello to generate codes
// Encoder and decoder methods will be generated to types.generated.go and connector.generated.go

// A hello argument
type HelloArguments struct {
Num int `json:"num"`
Str string `json:"str"`
Greeting string `json:"greeting"` // value argument will be required
Count *int `json:"count"` // pointer arguments are optional
}

// A hello result
type HelloResult struct {
Num int `json:"num"`
Str string `json:"str"`
Reply string `json:"reply"`
Count int `json:"count"`
}

// FunctionHello sends a hello message
// Function is an operation type of query, the name of function will be `hello`
//
// Example:
//
// curl http://localhost:8080/query -H 'content-type: application/json' -d \
// '{
// "collection": "hello",
// "arguments": {
// "greeting": {
// "type": "literal",
// "value": "Hello world!"
// }
// },
// "collection_relationships": {},
// "query": {
// "fields": {
// "reply": {
// "type": "column",
// "column": "reply"
// },
// "count": {
// "type": "column",
// "column": "count"
// }
// }
// }
// }'
func FunctionHello(ctx context.Context, state *types.State, arguments *HelloArguments) (*HelloResult, error) {
count := 1
if arguments.Count != nil {
count = *arguments.Count + 1
}
return &HelloResult{
Num: 1,
Str: "world",
Reply: fmt.Sprintf("Hi! %s", arguments.Greeting),
Count: count,
}, nil
}

// Procedure is similar to function. However the data structure of arguments are simpler and can be decode directly with JSON

// A create author argument
type CreateAuthorArguments struct {
Name string `json:"name"`
Expand All @@ -38,6 +74,36 @@ type CreateAuthorResult struct {
}

// ProcedureCreateAuthor creates an author
// Procedure is an operation type of mutation, the name of function will be `createAuthor`
//
// Example:
//
// curl http://localhost:8080/mutation -H 'content-type: application/json' -d \
// '{
// "operations": [
// {
// "type": "procedure",
// "name": "createAuthor",
// "arguments": {
// "name": "John"
// },
// "fields": {
// "type": "object",
// "fields": {
// "id": {
// "type": "column",
// "column": "id"
// },
// "name": {
// "type": "column",
// "column": "name"
// }
// }
// }
// }
// ],
// "collection_relationships": {}
// }'
func ProcedureCreateAuthor(ctx context.Context, state *types.State, arguments *CreateAuthorArguments) (*CreateAuthorResult, error) {
return &CreateAuthorResult{
ID: 1,
Expand Down
Loading

0 comments on commit 033f448

Please sign in to comment.