feat: release nullable mappings and split mapping configuration
This commit is contained in:
170
cmd/go-omni/apigen/testdata/nullable/consumer_test.go
vendored
Normal file
170
cmd/go-omni/apigen/testdata/nullable/consumer_test.go
vendored
Normal file
@@ -0,0 +1,170 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"example.com/nullable/internal/convert"
|
||||
"example.com/nullable/internal/domain"
|
||||
"example.com/nullable/internal/handler"
|
||||
"example.com/nullable/internal/mapping"
|
||||
"example.com/nullable/internal/svc"
|
||||
"example.com/nullable/internal/types"
|
||||
"git.yuntuo.hk/k2safe/go-omni/logx"
|
||||
)
|
||||
|
||||
func TestNullableMappings(t *testing.T) {
|
||||
for _, src := range []types.Parent{{}, {Children: []*types.Child{}}, {Child: &types.Child{}, Children: []*types.Child{nil, {Name: "two"}}}} {
|
||||
out, err := mapping.ToParent(src)
|
||||
if err != nil || (src.Child == nil) != (out.Child == nil) || (src.Children == nil) != (out.Children == nil) || len(src.Children) != len(out.Children) {
|
||||
t.Fatal(src, out, err)
|
||||
}
|
||||
if src.Child != nil && out.Child.Name != src.Child.Name {
|
||||
t.Fatal("zero child changed")
|
||||
}
|
||||
if len(out.Children) > 0 && (out.Children[0] != nil || out.Children[1].Name != "two") {
|
||||
t.Fatal(out)
|
||||
}
|
||||
}
|
||||
src := types.Parent{Child: &types.Child{Name: "one"}, Children: []*types.Child{{Name: "two"}}}
|
||||
out, err := mapping.ToParentPtr(src)
|
||||
if err != nil || out == nil || out.Child.Name != "one" {
|
||||
t.Fatal(out, err)
|
||||
}
|
||||
out.Child.Name = "changed"
|
||||
out.Children[0].Name = "changed"
|
||||
if src.Child.Name != "one" || src.Children[0].Name != "two" {
|
||||
t.Fatal("nested mapped values alias input")
|
||||
}
|
||||
plain := domain.Parent{Child: &domain.Child{Name: "shared"}}
|
||||
copy, err := mapping.ShallowCopy(plain)
|
||||
if err != nil || copy.Child != plain.Child {
|
||||
t.Fatal("ordinary assignment semantics changed", err)
|
||||
}
|
||||
zero, err := mapping.RequiredZero(types.Parent{})
|
||||
if err != nil || zero.Child.Name != "" {
|
||||
t.Fatal(zero, err)
|
||||
}
|
||||
for _, fn := range []func(types.Parent) (domain.Required, error){mapping.RequiredZero, mapping.RequiredError} {
|
||||
out, err := fn(types.Parent{Child: &types.Child{Name: "required"}})
|
||||
if err != nil || out.Child.Name != "required" {
|
||||
t.Fatal(out, err)
|
||||
}
|
||||
}
|
||||
_, err = mapping.RequiredError(types.Parent{})
|
||||
if err == nil || !strings.Contains(err.Error(), "RequiredError.Child") {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = mapping.RequirePointer(types.Parent{})
|
||||
if err == nil || !strings.Contains(err.Error(), "RequirePointer.Child") {
|
||||
t.Fatal(err)
|
||||
}
|
||||
lift, err := mapping.Lift(domain.ValueHolder{Child: domain.Child{Name: "lift"}})
|
||||
if err != nil || lift.Child == nil || lift.Child.Name != "lift" {
|
||||
t.Fatal(lift, err)
|
||||
}
|
||||
batch, err := mapping.Batch(domain.InputBatch{Fixed: [2]*domain.Child{nil, {Name: "array"}}})
|
||||
if err != nil || batch.Fixed[0] != nil || batch.Fixed[1].Name != "array" {
|
||||
t.Fatal(batch, err)
|
||||
}
|
||||
_, err = mapping.Batch(domain.InputBatch{Fixed: [2]*domain.Child{nil, {Name: "bad"}}})
|
||||
if !errors.Is(err, convert.ErrValue) || !strings.Contains(err.Error(), "Batch.Fixed[1]") {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, input := range []types.Parent{{Child: &types.Child{Name: "bad"}}, {Children: []*types.Child{nil, {Name: "bad"}}}} {
|
||||
failed, err := mapping.ToParentPtr(input)
|
||||
if failed != nil || !errors.Is(err, convert.ErrValue) || !strings.Contains(err.Error(), "ToChild.Name") {
|
||||
t.Fatal(failed, err)
|
||||
}
|
||||
if len(input.Children) > 0 && !strings.Contains(err.Error(), "ToParent.Children[1]") {
|
||||
t.Fatal(err)
|
||||
}
|
||||
value, err := mapping.ToParent(input)
|
||||
if value.Child != nil || value.Children != nil || !errors.Is(err, convert.ErrValue) {
|
||||
t.Fatal("partial result escaped", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExplicitNilAndConcurrentCalls(t *testing.T) {
|
||||
zero, err := mapping.ScalarZero(domain.ScalarInput{})
|
||||
if err != nil || zero != (domain.ScalarOutput{}) {
|
||||
t.Fatal(zero, err)
|
||||
}
|
||||
_, err = mapping.ScalarError(domain.ScalarInput{})
|
||||
if err == nil {
|
||||
t.Fatal("nil accepted")
|
||||
}
|
||||
text, count, enabled := "", int64(0), false
|
||||
out, err := mapping.ScalarError(domain.ScalarInput{Text: &text, Count: &count, Enabled: &enabled})
|
||||
if err != nil || out != (domain.ScalarOutput{}) {
|
||||
t.Fatal("non-nil zero rejected", out, err)
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 32; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
out, err := mapping.ToParent(types.Parent{Child: &types.Child{Name: "parallel"}})
|
||||
if err != nil || out.Child == nil || out.Child.Name != "parallel" {
|
||||
t.Error(out, err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestHTTPNullableMapping(t *testing.T) {
|
||||
cfg := logx.Default()
|
||||
cfg.Writer = io.Discard
|
||||
logger, err := logx.New(cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer logger.Close()
|
||||
deps, err := svc.New(context.Background(), logger)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer deps.Close()
|
||||
app, err := handler.NewApp(deps)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer app.Close()
|
||||
for _, body := range []string{`{}`, `{"child":null}`, `{"child":{"name":""}}`, `{"child":{"name":"one"},"children":[null,{"name":"two"}]}`, `{"children":[]}`} {
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("POST", "/", strings.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
app.Engine().ServeHTTP(w, req)
|
||||
if w.Code != 200 {
|
||||
t.Fatal(w.Code, w.Body.String())
|
||||
}
|
||||
var input, output types.Parent
|
||||
if err := json.Unmarshal([]byte(body), &input); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &output); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if (input.Child == nil) != (output.Child == nil) || (input.Children == nil) != (output.Children == nil) || len(input.Children) != len(output.Children) {
|
||||
t.Fatal(body, w.Body.String())
|
||||
}
|
||||
if input.Child != nil && input.Child.Name != output.Child.Name {
|
||||
t.Fatal(w.Body.String())
|
||||
}
|
||||
}
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("POST", "/", strings.NewReader(`{"child":{"name":"bad"}}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
app.Engine().ServeHTTP(w, req)
|
||||
if w.Code != 500 || strings.Contains(w.Body.String(), convert.ErrValue.Error()) || strings.Contains(w.Body.String(), "ToChild") {
|
||||
t.Fatal("private cause leaked", w.Code, w.Body.String())
|
||||
}
|
||||
}
|
||||
15
cmd/go-omni/apigen/testdata/nullable/convert.go
vendored
Normal file
15
cmd/go-omni/apigen/testdata/nullable/convert.go
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
package convert
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var ErrValue = errors.New("private conversion cause")
|
||||
|
||||
func Name(value string) (string, error) {
|
||||
if value == "bad" {
|
||||
return "", fmt.Errorf("conversion refused: %w", ErrValue)
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
23
cmd/go-omni/apigen/testdata/nullable/domain.go
vendored
Normal file
23
cmd/go-omni/apigen/testdata/nullable/domain.go
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
package domain
|
||||
|
||||
type Child struct{ Name string }
|
||||
type ChildDTO struct{ Name string }
|
||||
type Parent struct {
|
||||
Child *Child
|
||||
Children []*Child
|
||||
}
|
||||
type Required struct{ Child Child }
|
||||
type ValueHolder struct{ Child Child }
|
||||
type PointerHolder struct{ Child *ChildDTO }
|
||||
type InputBatch struct{ Fixed [2]*Child }
|
||||
type OutputBatch struct{ Fixed [2]*ChildDTO }
|
||||
type ScalarInput struct {
|
||||
Text *string
|
||||
Count *int64
|
||||
Enabled *bool
|
||||
}
|
||||
type ScalarOutput struct {
|
||||
Text string
|
||||
Count int64
|
||||
Enabled bool
|
||||
}
|
||||
15
cmd/go-omni/apigen/testdata/nullable/service.api
vendored
Normal file
15
cmd/go-omni/apigen/testdata/nullable/service.api
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
type Child {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
type Parent {
|
||||
Child *Child `json:"child,optional"`
|
||||
Children []*Child `json:"children,optional"`
|
||||
}
|
||||
@server (
|
||||
group: probe
|
||||
response: raw
|
||||
)
|
||||
service probe {
|
||||
@handler roundTrip
|
||||
post / (Parent) returns (Parent)
|
||||
}
|
||||
Reference in New Issue
Block a user