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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions cli/daemon/run/call.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,16 +178,27 @@ func handleResponse(md *v1.Data, p *ApiCallParams, headers http.Header, body []b
members := make([]hujson.ObjectMember, 0)
if rpcEncoding.ResponseEncoding != nil {
for i, m := range rpcEncoding.ResponseEncoding.HeaderParameters {
value := headers.Get(m.Name)
values := headers.Values(m.Name)

var beforeExtra []byte
if i == 0 {
beforeExtra = []byte("\n // HTTP Headers\n ")
}

var val hujson.Value
if len(values) == 1 {
val = hujson.Value{Value: hujson.String(values[0])}
} else {
arr := &hujson.Array{}
for _, v := range values {
arr.Elements = append(arr.Elements, hujson.Value{Value: hujson.String(v)})
}
val = hujson.Value{Value: arr}
}

members = append(members, hujson.ObjectMember{
Name: hujson.Value{Value: hujson.String(m.Name), BeforeExtra: beforeExtra},
Value: hujson.Value{Value: hujson.String(value)},
Value: val,
})
}

Expand Down
2 changes: 2 additions & 0 deletions cli/daemon/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ func (r *schemaRenderer) renderType(typ *schema.Type) {
r.renderNamed(typ.Named)
case *schema.Type_Pointer:
r.renderType(typ.Pointer.Base)
case *schema.Type_Option:
r.WriteNil()
case *schema.Type_Union:
r.renderType(typ.Union.Types[0])
case *schema.Type_Literal:
Expand Down
33 changes: 20 additions & 13 deletions docs/go/primitives/defining-apis.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,22 +284,29 @@ type CreateBlogPost struct {
}
```

### Optional types

Encore supports optional types using the `option.Option[T]` type from the `encore.dev/types/option` package.
This can be used in request and response schemas to indicate that the value is not always set.

See the [package documentation](https://pkg.go.dev/encore.dev/types/option) for more information on usage.

### Supported types
The table below lists the data types supported by each HTTP message location.

| Type | Header | Path | Query | Body |
| --------------- | ------ | ---- | ----- | ---- |
| bool | X | X | X | X |
| numeric | X | X | X | X |
| string | X | X | X | X |
| time.Time | X | X | X | X |
| uuid.UUID | X | X | X | X |
| json.RawMessage | X | X | X | X |
| list | | | X | X |
| struct | | | | X |
| map | | | | X |
| pointer | | | | X |

| Type | Header | Path | Query | Body |
| ---------------- | ------ | ---- | ----- | ---- |
| bool | X | X | X | X |
| numeric | X | X | X | X |
| string | X | X | X | X |
| time.Time | X | X | X | X |
| uuid.UUID | X | X | X | X |
| json.RawMessage | X | X | X | X |
| option.Option[T] | X | | X | X |
| pointer | X | | X | X |
| list | X | | X | X |
| struct | | | | X |
| map | | | | X |

## Sensitive data

Expand Down
64 changes: 35 additions & 29 deletions e2e-tests/testdata/echo/endtoend/endtoend.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@ import (
"encoding/json"
"fmt"
"math/rand"

// "net/http"
// "net/http/httptest"
"os"
"reflect"

// "strings"
"time"

"encore.app/test"
"encore.dev/beta/errs"
"encore.dev/rlog"
"encore.dev/types/option"
"encore.dev/types/uuid"
)

Expand Down Expand Up @@ -92,35 +95,38 @@ func GeneratedWrappersEndToEndTest(ctx context.Context) (err error) {
r.Read(queryBytes)
r.Read(bodyBytes)
params := &test.MarshallerTest[int]{
HeaderBoolean: r.Float32() > 0.5,
HeaderInt: r.Int(),
HeaderFloat: r.Float64(),
HeaderString: "header string",
HeaderBytes: headerBytes,
HeaderTime: time.Now().Truncate(time.Second),
HeaderJson: json.RawMessage("{\"hello\":\"world\"}"),
HeaderUUID: newUUID(),
HeaderUserID: "432",
QueryBoolean: r.Float32() > 0.5,
QueryInt: r.Int(),
QueryFloat: r.Float64(),
QueryString: "query string",
QueryBytes: headerBytes,
QueryTime: time.Now().Add(time.Duration(rand.Intn(1024)) * time.Hour).Truncate(time.Second),
QueryJson: json.RawMessage("true"),
QueryUUID: newUUID(),
QueryUserID: "9udfa",
QuerySlice: []int{r.Int(), r.Int(), r.Int(), r.Int()},
BodyBoolean: r.Float32() > 0.5,
BodyInt: r.Int(),
BodyFloat: r.Float64(),
BodyString: "body string",
BodyBytes: bodyBytes,
BodyTime: time.Now().Add(time.Duration(rand.Intn(1024)) * time.Hour).Truncate(time.Second),
BodyJson: json.RawMessage("null"),
BodyUUID: newUUID(),
BodyUserID: "✉️",
BodySlice: []int{r.Int(), r.Int(), r.Int(), r.Int(), r.Int(), r.Int()},
HeaderBoolean: r.Float32() > 0.5,
HeaderInt: r.Int(),
HeaderFloat: r.Float64(),
HeaderString: "header string",
HeaderBytes: headerBytes,
HeaderTime: time.Now().Truncate(time.Second),
HeaderJson: json.RawMessage("{\"hello\":\"world\"}"),
HeaderUUID: newUUID(),
HeaderUserID: "432",
HeaderOption: option.Some("test"),
QueryBoolean: r.Float32() > 0.5,
QueryInt: r.Int(),
QueryFloat: r.Float64(),
QueryString: "query string",
QueryBytes: headerBytes,
QueryTime: time.Now().Add(time.Duration(rand.Intn(1024)) * time.Hour).Truncate(time.Second),
QueryJson: json.RawMessage("true"),
QueryUUID: newUUID(),
QueryUserID: "9udfa",
QuerySlice: []int{r.Int(), r.Int(), r.Int(), r.Int()},
QuerySliceOptions: []option.Option[int]{option.Some(r.Int()), option.None[int](), option.Some(r.Int())},
BodyBoolean: r.Float32() > 0.5,
BodyInt: r.Int(),
BodyFloat: r.Float64(),
BodyString: "body string",
BodyBytes: bodyBytes,
BodyTime: time.Now().Add(time.Duration(rand.Intn(1024)) * time.Hour).Truncate(time.Second),
BodyJson: json.RawMessage("null"),
BodyUUID: newUUID(),
BodyUserID: "✉️",
BodySlice: []int{r.Int(), r.Int(), r.Int(), r.Int(), r.Int(), r.Int()},
BodyOption: option.Some(r.Int()),
}
mResp, err := test.MarshallerTestHandler(ctx, params)
assert(err, nil, "Expected no error from the marshaller test")
Expand Down
62 changes: 33 additions & 29 deletions e2e-tests/testdata/echo/test/endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
encore "encore.dev"
"encore.dev/beta/auth"
"encore.dev/beta/errs"
"encore.dev/types/option"
"encore.dev/types/uuid"
)

Expand Down Expand Up @@ -98,35 +99,38 @@ func RestStyleAPI(ctx context.Context, objType int, name string, params *RestPar
}

type MarshallerTest[A any] struct {
HeaderBoolean bool `header:"x-boolean"`
HeaderInt int `header:"x-int"`
HeaderFloat float64 `header:"x-float"`
HeaderString string `header:"x-string"`
HeaderBytes []byte `header:"x-bytes"`
HeaderTime time.Time `header:"x-time"`
HeaderJson json.RawMessage `header:"x-json"`
HeaderUUID uuid.UUID `header:"x-uuid"`
HeaderUserID auth.UID `header:"x-user-id"`
QueryBoolean bool `qs:"boolean"`
QueryInt int `qs:"int"`
QueryFloat float64 `qs:"float"`
QueryString string `qs:"string"`
QueryBytes []byte `qs:"bytes"`
QueryTime time.Time `qs:"time"`
QueryJson json.RawMessage `qs:"json"`
QueryUUID uuid.UUID `qs:"uuid"`
QueryUserID auth.UID `qs:"user-id"`
QuerySlice []A `qs:"slice"`
BodyBoolean bool `json:"boolean"`
BodyInt int `json:"int"`
BodyFloat float64 `json:"float"`
BodyString string `json:"string"`
BodyBytes []byte `json:"bytes"`
BodyTime time.Time `json:"time"`
BodyJson json.RawMessage `json:"json"`
BodyUUID uuid.UUID `json:"uuid"`
BodyUserID auth.UID `json:"user-id"`
BodySlice []A `json:"slice"`
HeaderBoolean bool `header:"x-boolean"`
HeaderInt int `header:"x-int"`
HeaderFloat float64 `header:"x-float"`
HeaderString string `header:"x-string"`
HeaderBytes []byte `header:"x-bytes"`
HeaderTime time.Time `header:"x-time"`
HeaderJson json.RawMessage `header:"x-json"`
HeaderUUID uuid.UUID `header:"x-uuid"`
HeaderUserID auth.UID `header:"x-user-id"`
HeaderOption option.Option[string] `header:"x-option"`
QueryBoolean bool `qs:"boolean"`
QueryInt int `qs:"int"`
QueryFloat float64 `qs:"float"`
QueryString string `qs:"string"`
QueryBytes []byte `qs:"bytes"`
QueryTime time.Time `qs:"time"`
QueryJson json.RawMessage `qs:"json"`
QueryUUID uuid.UUID `qs:"uuid"`
QueryUserID auth.UID `qs:"user-id"`
QuerySlice []A `qs:"slice"`
QuerySliceOptions []option.Option[A] `qs:"slice-options"`
BodyBoolean bool `json:"boolean"`
BodyInt int `json:"int"`
BodyFloat float64 `json:"float"`
BodyString string `json:"string"`
BodyBytes []byte `json:"bytes"`
BodyTime time.Time `json:"time"`
BodyJson json.RawMessage `json:"json"`
BodyUUID uuid.UUID `json:"uuid"`
BodyUserID auth.UID `json:"user-id"`
BodySlice []A `json:"slice"`
BodyOption option.Option[A] `json:"option"`
}

// MarshallerTestHandler allows us to test marshalling of all the inbuilt types in all
Expand Down
3 changes: 3 additions & 0 deletions internal/gocodegen/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ func ConvertSchemaToJenType(typ *schema.Type, md *meta.Data) *Statement {
case *schema.Type_Pointer:
return Op("*").Add(ConvertSchemaToJenType(typ.Pointer.Base, md))

case *schema.Type_Option:
return Qual("encore.dev/types/option", "Option").Types(ConvertSchemaToJenType(typ.Option.Value, md))

case *schema.Type_TypeParameter:
return Id(md.Decls[typ.TypeParameter.DeclId].TypeParams[typ.TypeParameter.ParamIdx].Name)

Expand Down
Loading
Loading