From 3f1a2e00c0fddc72deb24759c86057113ce647a8 Mon Sep 17 00:00:00 2001 From: widmogrod Date: Tue, 12 Dec 2023 00:50:25 +0100 Subject: [PATCH 01/14] x/shape json serialisation with shorter types, improvements to other packages affected --- example/ast/ast_test.go | 16 +- example/my-app/go.mod | 4 +- example/my-app/server.go | 536 ++++++++++-------- example/my-app/src/App.tsx | 203 +++++-- example/my-app/src/Chat.tsx | 5 +- ...b_com_widmogrod_mkunion_exammple_my-app.ts | 55 ++ .../github_com_widmogrod_mkunion_x_schema.ts | 52 +- ...ithub_com_widmogrod_mkunion_x_workflow.ts} | 330 +++-------- example/my-app/src/workflow/time.ts | 1 - x/generators/deser_json_generator.go | 111 +++- x/generators/deser_json_generator.go.tmpl | 8 +- .../deser_json_generator_test_tree.go.asset | 60 +- x/generators/shape_generator.go | 8 +- .../derive_func_match_alphabet_gen.go | 30 +- .../testutils/derive_func_match_number_gen.go | 21 +- x/generators/testutils/tree_tree_gen.go | 60 +- x/schema/location_location_gen.go | 30 +- x/schema/model.go | 39 ++ x/schema/model_schema_gen.go | 105 ++-- x/shape/shape_guard_gen.go | 6 +- x/shape/shape_shape_gen.go | 18 +- x/shape/testasset/type_to_json_test.go | 14 +- x/shape/totypescript.go | 149 +++-- x/shape/totypescript_test.go | 33 +- x/shared/jsonparseobject.go | 92 ++- x/workflow/workflow_deser_test.go | 238 ++++++++ x/workflow/workflow_other.go | 5 +- 27 files changed, 1367 insertions(+), 862 deletions(-) create mode 100644 example/my-app/src/workflow/github_com_widmogrod_mkunion_exammple_my-app.ts rename example/my-app/src/workflow/{workflow.ts => github_com_widmogrod_mkunion_x_workflow.ts} (58%) delete mode 100644 example/my-app/src/workflow/time.ts create mode 100644 x/workflow/workflow_deser_test.go diff --git a/example/ast/ast_test.go b/example/ast/ast_test.go index ca2fa92d..ec9a2b7c 100644 --- a/example/ast/ast_test.go +++ b/example/ast/ast_test.go @@ -10,8 +10,8 @@ func TestAstJsonConversionOnSimpleType(t *testing.T) { jsonData, err := ValueToJSON(in) assert.NoError(t, err) assert.JSONEq(t, `{ - "$type": "github.com/widmogrod/mkunion/example/ast.Lit", - "github.com/widmogrod/mkunion/example/ast.Lit": { + "$type": "ast.Lit", + "ast.Lit": { "Value": 12 } }`, string(jsonData)) @@ -26,19 +26,19 @@ func TestAstToJSONOnSumTypes(t *testing.T) { assert.NoError(t, err) t.Log(string(jsonData)) assert.JSONEq(t, `{ - "$type": "github.com/widmogrod/mkunion/example/ast.Eq", - "github.com/widmogrod/mkunion/example/ast.Eq": { + "$type": "ast.Eq", + "ast.Eq": { "L": { - "$type": "github.com/widmogrod/mkunion/example/ast.Accessor", - "github.com/widmogrod/mkunion/example/ast.Accessor": { + "$type": "ast.Accessor", + "ast.Accessor": { "Path": [ "foo" ] } }, "R": { - "$type": "github.com/widmogrod/mkunion/example/ast.Lit", - "github.com/widmogrod/mkunion/example/ast.Lit": { + "$type": "ast.Lit", + "ast.Lit": { "Value": "baz" } } diff --git a/example/my-app/go.mod b/example/my-app/go.mod index 44080d64..781fa4cc 100644 --- a/example/my-app/go.mod +++ b/example/my-app/go.mod @@ -8,6 +8,7 @@ require ( github.com/labstack/echo/v4 v4.11.2 github.com/sashabaranov/go-openai v1.17.5 github.com/sirupsen/logrus v1.9.3 + github.com/stretchr/testify v1.8.4 github.com/widmogrod/mkunion v1.19.0 golang.org/x/image v0.13.0 ) @@ -25,6 +26,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/sqs v1.24.5 // indirect github.com/aws/smithy-go v1.14.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fatih/structtag v1.2.0 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/labstack/gommon v0.4.0 // indirect @@ -33,11 +35,11 @@ require ( github.com/opensearch-project/opensearch-go/v2 v2.3.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect - github.com/stretchr/testify v1.8.4 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect golang.org/x/crypto v0.14.0 // indirect golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect + golang.org/x/mod v0.12.0 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect diff --git a/example/my-app/server.go b/example/my-app/server.go index d8144a87..b8098ea7 100644 --- a/example/my-app/server.go +++ b/example/my-app/server.go @@ -194,170 +194,199 @@ func main() { e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ AllowOrigins: []string{"*"}, })) - e.POST("/message", TypedRequest(func(x ChatCMD) (ChatResult, error) { - ctx := context.Background() - - model := openai.GPT3Dot5Turbo1106 - tools := []openai.Tool{ - { - Type: openai.ToolTypeFunction, - Function: shape.ToOpenAIFunctionDefinition( - "count_words", - "count number of valid words in sentence", - shape.FromGo(ListWorkflowsFn{}), - ), - }, - { - Type: openai.ToolTypeFunction, - Function: shape.ToOpenAIFunctionDefinition( - "refresh_flows", - "refresh list of workflows visible to user on UI", - shape.FromGo(RefreshFlows{}), - ), - }, - { - Type: openai.ToolTypeFunction, - Function: shape.ToOpenAIFunctionDefinition( - "refresh_states", - "refresh list of states visible to user on UI", - shape.FromGo(RefreshStates{}), - ), - }, - { - Type: openai.ToolTypeFunction, - Function: shape.ToOpenAIFunctionDefinition( - "generate_image", - "generate image", - shape.FromGo(GenerateImage{}), - ), - }, - } - - var history []openai.ChatCompletionMessage - history = append(history, openai.ChatCompletionMessage{ - Role: openai.ChatMessageRoleUser, - Content: x.(*UserMessage).Message, - }) - - result, err := oaic.CreateChatCompletion(ctx, openai.ChatCompletionRequest{ - Model: model, - Messages: history, - Tools: tools, - }) - - if err != nil { - log.Errorf("failed to create chat completion: %v", err) - return nil, err - } - - history = append(history, result.Choices[0].Message) - - response := &ChatResponses{} - response.Responses = append(response.Responses, &SystemResponse{ - Message: result.Choices[0].Message.Content, - ToolCalls: result.Choices[0].Message.ToolCalls, - }) - - for _, tool := range result.Choices[0].Message.ToolCalls { - switch tool.Function.Name { - case "refresh_states": - records, err := statesRepo.FindingRecords(schemaless.FindingRecords[schemaless.Record[workflow.State]]{ - RecordType: "process", - }) - if err != nil { - return nil, err - } - - schemed := schema.FromGo(records) - result, err := schema.ToJSON(schemed) - - history = append(history, openai.ChatCompletionMessage{ - Role: openai.ChatMessageRoleTool, - Content: string(result), - ToolCallID: tool.ID, - }) - - default: - history = append(history, openai.ChatCompletionMessage{ - Role: openai.ChatMessageRoleTool, - Content: "not implemented", - ToolCallID: tool.ID, - }) + e.POST("/message", TypedJSONRequest( + ChatCMDFromJSON, + ChatResultToJSON, + func(x ChatCMD) (ChatResult, error) { + ctx := context.Background() + + model := openai.GPT3Dot5Turbo1106 + tools := []openai.Tool{ + { + Type: openai.ToolTypeFunction, + Function: shape.ToOpenAIFunctionDefinition( + "count_words", + "count number of valid words in sentence", + shape.FromGo(ListWorkflowsFn{}), + ), + }, + { + Type: openai.ToolTypeFunction, + Function: shape.ToOpenAIFunctionDefinition( + "refresh_flows", + "refresh list of workflows visible to user on UI", + shape.FromGo(RefreshFlows{}), + ), + }, + { + Type: openai.ToolTypeFunction, + Function: shape.ToOpenAIFunctionDefinition( + "refresh_states", + "refresh list of states visible to user on UI", + shape.FromGo(RefreshStates{}), + ), + }, + { + Type: openai.ToolTypeFunction, + Function: shape.ToOpenAIFunctionDefinition( + "generate_image", + "generate image", + shape.FromGo(GenerateImage{}), + ), + }, } - } + var history []openai.ChatCompletionMessage + history = append(history, openai.ChatCompletionMessage{ + Role: openai.ChatMessageRoleUser, + Content: x.(*UserMessage).Message, + }) - if len(result.Choices[0].Message.ToolCalls) > 0 { - result2, err2 := oaic.CreateChatCompletion(ctx, openai.ChatCompletionRequest{ + result, err := oaic.CreateChatCompletion(ctx, openai.ChatCompletionRequest{ Model: model, Messages: history, Tools: tools, }) - if err2 != nil { - log.Errorf("failed to create chat completion2: %v", err2) - for _, h := range history { - log.Infof("history: %#+v \n", h) - } - return nil, err2 + if err != nil { + log.Errorf("failed to create chat completion: %v", err) + return nil, err } + history = append(history, result.Choices[0].Message) + + response := &ChatResponses{} response.Responses = append(response.Responses, &SystemResponse{ - Message: result2.Choices[0].Message.Content, - ToolCalls: result2.Choices[0].Message.ToolCalls, + Message: result.Choices[0].Message.Content, + ToolCalls: result.Choices[0].Message.ToolCalls, }) - } - log.Infof("result: %+v", result) - return response, nil - })) + for _, tool := range result.Choices[0].Message.ToolCalls { + switch tool.Function.Name { + case "refresh_states": + records, err := statesRepo.FindingRecords(schemaless.FindingRecords[schemaless.Record[workflow.State]]{ + RecordType: "process", + }) + if err != nil { + return nil, err + } + + schemed := schema.FromGo(records) + result, err := schema.ToJSON(schemed) + + history = append(history, openai.ChatCompletionMessage{ + Role: openai.ChatMessageRoleTool, + Content: string(result), + ToolCallID: tool.ID, + }) + + default: + history = append(history, openai.ChatCompletionMessage{ + Role: openai.ChatMessageRoleTool, + Content: "not implemented", + ToolCallID: tool.ID, + }) + } - e.POST("/func", TypedRequest(func(x *workflow.FunctionInput) (*workflow.FunctionOutput, error) { - fn, err := di.FindFunction(x.Name) - if err != nil { - return nil, err - } + } - return fn(x) - })) + if len(result.Choices[0].Message.ToolCalls) > 0 { + result2, err2 := oaic.CreateChatCompletion(ctx, openai.ChatCompletionRequest{ + Model: model, + Messages: history, + Tools: tools, + }) - e.POST("/flow", func(c echo.Context) error { - data, err := io.ReadAll(c.Request().Body) - if err != nil { - log.Errorf("failed to read request body: %v", err) - return err - } + if err2 != nil { + log.Errorf("failed to create chat completion2: %v", err2) + for _, h := range history { + log.Infof("history: %#+v \n", h) + } + return nil, err2 + } - schemed, err := schema.FromJSON(data) - if err != nil { - log.Errorf("failed to parse request body: %v", err) - return err - } + response.Responses = append(response.Responses, &SystemResponse{ + Message: result2.Choices[0].Message.Content, + ToolCalls: result2.Choices[0].Message.ToolCalls, + }) + } - program, err := schema.ToGoG[workflow.Worflow](schemed) - if err != nil { - log.Errorf("failed to convert to command: %v", err) - return err - } + log.Infof("result: %+v", result) + return response, nil + })) - flow, ok := program.(*workflow.Flow) - if !ok { - return errors.New("expected *workflow.Flow") - } + e.POST("/func", TypedJSONRequest( + workflow.FunctionInputFromJSON, + workflow.FunctionOutputToJSON, + func(x *workflow.FunctionInput) (*workflow.FunctionOutput, error) { + fn, err := di.FindFunction(x.Name) + if err != nil { + return nil, err + } - err = flowsRepo.UpdateRecords(schemaless.Save(schemaless.Record[workflow.Flow]{ - ID: flow.Name, - Type: "flow", - Data: *flow, + return fn(x) })) - if err != nil { - log.Errorf("failed to save state: %v", err) - return err - } + e.POST("/flow", TypedJSONRequest( + workflow.WorflowFromJSON, + workflow.WorflowToJSON, + func(x workflow.Worflow) (workflow.Worflow, error) { + flow, ok := x.(*workflow.Flow) + if !ok { + return nil, errors.New("expected *workflow.Flow") + } - return c.JSONBlob(http.StatusOK, data) - }) + err := flowsRepo.UpdateRecords(schemaless.Save(schemaless.Record[workflow.Flow]{ + ID: flow.Name, + Type: "flow", + Data: *flow, + })) + + if err != nil { + log.Errorf("POST /flow: failed to save flow: %v", err) + return nil, err + } + + return flow, nil + }, + )) + //e.POST("/flow", func(c echo.Context) error { + // data, err := io.ReadAll(c.Request().Body) + // if err != nil { + // log.Errorf("failed to read request body: %v", err) + // return err + // } + // + // schemed, err := schema.FromJSON(data) + // if err != nil { + // log.Errorf("failed to parse request body: %v", err) + // return err + // } + // + // program, err := schema.ToGoG[workflow.Worflow](schemed) + // if err != nil { + // log.Errorf("failed to convert to command: %v", err) + // return err + // } + // + // flow, ok := program.(*workflow.Flow) + // if !ok { + // return errors.New("expected *workflow.Flow") + // } + // + // err = flowsRepo.UpdateRecords(schemaless.Save(schemaless.Record[workflow.Flow]{ + // ID: flow.Name, + // Type: "flow", + // Data: *flow, + // })) + // + // if err != nil { + // log.Errorf("failed to save state: %v", err) + // return err + // } + // + // return c.JSONBlob(http.StatusOK, data) + //}) e.GET("/flow/:id", func(c echo.Context) error { record, err := flowsRepo.Get(c.Param("id"), "flow") if err != nil { @@ -365,10 +394,11 @@ func main() { return err } - schemed := schema.FromGo(record) - result, err := schema.ToJSON(schemed) + result, err := workflow.WorflowToJSON(&record.Data) + //schemed := schema.FromGo(record) + //result, err := schema.ToJSON(schemed) if err != nil { - if errors.As(err, &schemaless.ErrNotFound) { + if errors.Is(err, schemaless.ErrNotFound) { return c.JSONBlob(http.StatusNotFound, []byte(`{"error": "not found"}`)) } @@ -423,28 +453,31 @@ func main() { return c.JSONBlob(http.StatusOK, result) }) - e.POST("/", TypedRequest(func(cmd workflow.Command) (workflow.State, error) { - return srv.CreateOrUpdate(cmd) - //work := workflow.NewMachine(di, nil) - //err := work.Handle(cmd) - //if err != nil { - // log.Errorf("failed to handle command: %v", err) - // return nil, err - //} - // - //newState := work.State() - //err = repo.UpdateRecords(schemaless.Save(schemaless.Record[workflow.State]{ - // ID: workflow.GetRunID(newState), - // Type: "process", - // Data: newState, - //})) - //if err != nil { - // log.Errorf("failed to save state: %v", err) - // return nil, err - //} - // - //return newState, nil - })) + e.POST("/", TypedJSONRequest( + workflow.CommandFromJSON, + workflow.StateToJSON, + func(cmd workflow.Command) (workflow.State, error) { + return srv.CreateOrUpdate(cmd) + //work := workflow.NewMachine(di, nil) + //err := work.Handle(cmd) + //if err != nil { + // log.Errorf("failed to handle command: %v", err) + // return nil, err + //} + // + //newState := work.State() + //err = repo.UpdateRecords(schemaless.Save(schemaless.Record[workflow.State]{ + // ID: workflow.GetRunID(newState), + // Type: "process", + // Data: newState, + //})) + //if err != nil { + // log.Errorf("failed to save state: %v", err) + // return nil, err + //} + // + //return newState, nil + })) e.POST("/workflow-to-str", func(c echo.Context) error { data, err := io.ReadAll(c.Request().Body) @@ -453,13 +486,7 @@ func main() { return err } - schemed, err := schema.FromJSON(data) - if err != nil { - log.Errorf("failed to parse request body: %v", err) - return err - } - - program, err := schema.ToGoG[workflow.Worflow](schemed) + program, err := workflow.WorflowFromJSON(data) if err != nil { log.Errorf("failed to convert to workflow: %v", err) return err @@ -468,61 +495,64 @@ func main() { return c.String(http.StatusOK, workflow.ToStrWorkflow(program, 0)) }) - e.POST("/callback", TypedRequest(func(cmd workflow.Command) (workflow.State, error) { - callbackCMD, ok := cmd.(*workflow.Callback) - if !ok { - log.Errorf("expected callback command") - return nil, errors.New("expected callback command") - } + e.POST("/callback", TypedJSONRequest( + workflow.CommandFromJSON, + workflow.StateToJSON, + func(cmd workflow.Command) (workflow.State, error) { + callbackCMD, ok := cmd.(*workflow.Callback) + if !ok { + log.Errorf("expected callback command") + return nil, errors.New("expected callback command") + } - // find callback id in database - records, err := statesRepo.FindingRecords(schemaless.FindingRecords[schemaless.Record[workflow.State]]{ - //RecordType: "process", - Where: predicate.MustWhere( - `Type = :type AND Data["workflow.Await"].CallbackID = :callbackID`, - predicate.ParamBinds{ - ":type": schema.MkString("process"), - ":callbackID": schema.MkString(callbackCMD.CallbackID), - }, - ), - Limit: 1, - }) - if err != nil { - log.Errorf("failed to find callback id: %v", err) - return nil, err - } + // find callback id in database + records, err := statesRepo.FindingRecords(schemaless.FindingRecords[schemaless.Record[workflow.State]]{ + //RecordType: "process", + Where: predicate.MustWhere( + `Type = :type AND Data["workflow.Await"].CallbackID = :callbackID`, + predicate.ParamBinds{ + ":type": schema.MkString("process"), + ":callbackID": schema.MkString(callbackCMD.CallbackID), + }, + ), + Limit: 1, + }) + if err != nil { + log.Errorf("failed to find callback id: %v", err) + return nil, err + } - if len(records.Items) == 0 { - log.Errorf("state, with callbackID not found") - return nil, errors.New("state, with callbackID not found") - } + if len(records.Items) == 0 { + log.Errorf("state, with callbackID not found") + return nil, errors.New("state, with callbackID not found") + } - state := records.Items[0] - log.Infof("state: %+v", state) + state := records.Items[0] + log.Infof("state: %+v", state) - // apply command - work := workflow.NewMachine(di, state.Data) - err = work.Handle(cmd) - if err != nil { - log.Errorf("failed to handle command: %v", err) - return nil, err - } + // apply command + work := workflow.NewMachine(di, state.Data) + err = work.Handle(cmd) + if err != nil { + log.Errorf("failed to handle command: %v", err) + return nil, err + } - // save state - newState := work.State() - err = statesRepo.UpdateRecords(schemaless.Save(schemaless.Record[workflow.State]{ - ID: workflow.GetRunID(newState), - Type: "process", - Data: newState, - Version: state.Version, - })) - if err != nil { - log.Errorf("failed to save state: %v", err) - return nil, err - } + // save state + newState := work.State() + err = statesRepo.UpdateRecords(schemaless.Save(schemaless.Record[workflow.State]{ + ID: workflow.GetRunID(newState), + Type: "process", + Data: newState, + Version: state.Version, + })) + if err != nil { + log.Errorf("failed to save state: %v", err) + return nil, err + } - return newState, nil - })) + return newState, nil + })) proc := &taskqueue.FunctionProcessor[workflow.State]{ F: func(task taskqueue.Task[schemaless.Record[workflow.State]]) { @@ -626,23 +656,17 @@ AND Data[*]["workflow.Scheduled"].ExpectedRunTimestamp > 0 } -func TypedRequest[A, B any](handle func(x A) (B, error)) func(c echo.Context) error { +func TypedJSONRequest[A, B any](des func([]byte) (A, error), ser func(B) ([]byte, error), handle func(x A) (B, error)) func(c echo.Context) error { return func(c echo.Context) error { data, err := io.ReadAll(c.Request().Body) if err != nil { - log.Errorf("failed to read request body: %v", err) - return err - } - - schemed, err := schema.FromJSON(data) - if err != nil { - log.Errorf("failed to parse request body: %v", err) + log.Errorf("TypedJSONRequest: failed to read request body: %v", err) return err } - in, err := schema.ToGoG[A](schemed) + in, err := des(data) if err != nil { - log.Errorf("failed to convert to command: %v", err) + log.Errorf("TypedJSONRequest: failed to parse request body: %v", err) return err } @@ -653,13 +677,12 @@ func TypedRequest[A, B any](handle func(x A) (B, error)) func(c echo.Context) er if _, ok := any(out).(B); !ok { var b B - return fmt.Errorf("TypedRequest: expected %T, got %T", b, out) + return fmt.Errorf("TypedJSONRequest: TypedRequest: expected %T, got %T", b, out) } - schemed = schema.FromGo(out) - result, err := schema.ToJSON(schemed) + result, err := ser(out) if err != nil { - log.Errorf("failed to convert to json: %v", err) + log.Errorf("TypedJSONRequest: failed to convert to json: %v", err) return err } @@ -667,6 +690,47 @@ func TypedRequest[A, B any](handle func(x A) (B, error)) func(c echo.Context) er } } +//func TypedRequest[A, B any](handle func(x A) (B, error)) func(c echo.Context) error { +// return func(c echo.Context) error { +// data, err := io.ReadAll(c.Request().Body) +// if err != nil { +// log.Errorf("failed to read request body: %v", err) +// return err +// } +// +// schemed, err := schema.FromJSON(data) +// if err != nil { +// log.Errorf("failed to parse request body: %v", err) +// return err +// } +// +// in, err := schema.ToGoG[A](schemed) +// if err != nil { +// log.Errorf("failed to convert to command: %v", err) +// return err +// } +// +// out, err := handle(in) +// if err != nil { +// return err +// } +// +// if _, ok := any(out).(B); !ok { +// var b B +// return fmt.Errorf("TypedRequest: expected %T, got %T", b, out) +// } +// +// schemed = schema.FromGo(out) +// result, err := schema.ToJSON(schemed) +// if err != nil { +// log.Errorf("failed to convert to json: %v", err) +// return err +// } +// +// return c.JSONBlob(http.StatusOK, result) +// } +//} + func NewService[CMD any, State any]( recordType string, statesRepo *typedful.TypedRepoWithAggregator[State, any], diff --git a/example/my-app/src/App.tsx b/example/my-app/src/App.tsx index 15731c0f..e8517768 100644 --- a/example/my-app/src/App.tsx +++ b/example/my-app/src/App.tsx @@ -1,10 +1,9 @@ import React, {useEffect, useState} from 'react'; import './App.css'; -import * as workflow from './workflow/workflow' -import {dediscriminateCommand} from './workflow/workflow' +import * as workflow from './workflow/github_com_widmogrod_mkunion_x_workflow' import * as schema from "./workflow/github_com_widmogrod_mkunion_x_schema"; import {Chat} from "./Chat"; -import {GenerateImage, ListWorkflowsFn} from "./workflow/main"; +import {GenerateImage, ListWorkflowsFn} from "./workflow/github_com_widmogrod_mkunion_exammple_my-app"; function flowCreate(flow: workflow.Flow) { console.log("save-flow", flow) @@ -58,8 +57,10 @@ function listFlows(onData: (data: { Items: recordFlow[] }) => void) { function runFlow(flowID: string, input: string, onData?: (data: workflow.State) => void) { const cmd: workflow.Command = { + "$type": "workflow.Run", "workflow.Run": { Flow: { + "$type": "workflow.FlowRef", "workflow.FlowRef": { FlowID: flowID, } @@ -71,7 +72,7 @@ function runFlow(flowID: string, input: string, onData?: (data: workflow.State) } fetch('http://localhost:8080/', { method: 'POST', - body: JSON.stringify(dediscriminateCommand(cmd)), + body: JSON.stringify(cmd), }) .then(res => res.json()) .then(data => { @@ -82,24 +83,30 @@ function runFlow(flowID: string, input: string, onData?: (data: workflow.State) function runHelloWorldWorkflow(input: string, onData?: (data: workflow.State) => void) { const cmd: workflow.Command = { + "$type": "workflow.Run", "workflow.Run": { Flow: { + "$type": "workflow.Flow", "workflow.Flow": { Name: "hello_world", Arg: "input", Body: [ { + "$type": "workflow.Choose", "workflow.Choose": { ID: "choose1", If: { + "$type": "workflow.Compare", "workflow.Compare": { Operation: "=", Left: { + "$type": "workflow.GetValue", "workflow.GetValue": { Path: "input", } }, Right: { + "$type": "workflow.SetValue", "workflow.SetValue": { Value: { "schema.String": "666", @@ -110,9 +117,11 @@ function runHelloWorldWorkflow(input: string, onData?: (data: workflow.State) => }, Then: [ { + "$type": "workflow.End", "workflow.End": { ID: "end2", Result: { + "$type": "workflow.SetValue", "workflow.SetValue": { Value: { "schema.String": "Do no evil", @@ -125,15 +134,19 @@ function runHelloWorldWorkflow(input: string, onData?: (data: workflow.State) => } }, { + "$type": "workflow.Assign", "workflow.Assign": { ID: "assign1", VarOk: "res", + VarErr: "", Val: { + "$type": "workflow.Apply", "workflow.Apply": { ID: "apply1", Name: "concat", Args: [ { + "$type": "workflow.SetValue", "workflow.SetValue": { Value: { "schema.String": "hello ", @@ -141,6 +154,7 @@ function runHelloWorldWorkflow(input: string, onData?: (data: workflow.State) => } }, { + "$type": "workflow.GetValue", "workflow.GetValue": { Path: "input", } @@ -151,9 +165,11 @@ function runHelloWorldWorkflow(input: string, onData?: (data: workflow.State) => }, }, { + "$type": "workflow.End", "workflow.End": { ID: "end1", Result: { + "$type": "workflow.GetValue", "workflow.GetValue": { Path: "res", } @@ -175,7 +191,7 @@ function runHelloWorldWorkflow(input: string, onData?: (data: workflow.State) => fetch('http://localhost:8080/', { method: 'POST', - body: JSON.stringify(dediscriminateCommand(cmd)), + body: JSON.stringify(cmd), }) .then(res => res.json()) .then(data => onData && onData(data)) @@ -183,22 +199,28 @@ function runHelloWorldWorkflow(input: string, onData?: (data: workflow.State) => function generateImage(imageWidth: number, imageHeight: number, onData?: (data: workflow.State) => void) { const cmd: workflow.Command = { + "$type": "workflow.Run", "workflow.Run": { Flow: { + "$type": "workflow.Flow", "workflow.Flow": { Name: "generateandresizeimage", Arg: "input", Body: [ { + "$type": "workflow.Assign", "workflow.Assign": { ID: "assign1", VarOk: "res", + VarErr: "", Val: { + "$type": "workflow.Apply", "workflow.Apply": { ID: "apply1", Name: "genimageb64", Args: [ { + "$type": "workflow.GetValue", "workflow.GetValue": { Path: "input.prompt", } @@ -209,25 +231,31 @@ function generateImage(imageWidth: number, imageHeight: number, onData?: (data: }, }, { + "$type": "workflow.Assign", "workflow.Assign": { ID: "assign2", VarOk: "res_small", + VarErr: "", Val: { + "$type": "workflow.Apply", "workflow.Apply": { ID: "apply2", Name: "resizeimgb64", Args: [ { + "$type": "workflow.GetValue", "workflow.GetValue": { Path: "res", } }, { + "$type": "workflow.GetValue", "workflow.GetValue": { Path: "input.width", } }, { + "$type": "workflow.GetValue", "workflow.GetValue": { Path: "input.height", } @@ -238,9 +266,11 @@ function generateImage(imageWidth: number, imageHeight: number, onData?: (data: }, }, { + "$type": "workflow.End", "workflow.End": { ID: "end1", Result: { + "$type": "workflow.GetValue", "workflow.GetValue": { Path: "res_small", } @@ -252,10 +282,27 @@ function generateImage(imageWidth: number, imageHeight: number, onData?: (data: }, Input: { "schema.Map": { - "prompt": "no text", - "width": imageWidth, - "height": imageHeight, - }, + Field: [ + { + Name: "prompt", + Value: { + "schema.String": "no text", + } + }, + { + Name: "width", + Value: { + "schema.Number": imageWidth, + } + }, + { + Name: "height", + Value: { + "schema.Number": imageHeight, + } + }, + ] + } as schema.Map, }, } } @@ -266,7 +313,7 @@ function generateImage(imageWidth: number, imageHeight: number, onData?: (data: fetch('http://localhost:8080/', { method: 'POST', - body: JSON.stringify(dediscriminateCommand(cmd)), + body: JSON.stringify(cmd), }) .then(res => res.json()) .then((data: workflow.State) => { @@ -277,22 +324,28 @@ function generateImage(imageWidth: number, imageHeight: number, onData?: (data: function runContactAwait(imageWidth: number, imageHeight: number, onData?: (data: workflow.State) => void) { const cmd: workflow.Command = { + "$type": "workflow.Run", "workflow.Run": { Flow: { + "$type": "workflow.Flow", "workflow.Flow": { Name: "concat_await", Arg: "input", Body: [ { + "$type": "workflow.Assign", "workflow.Assign": { ID: "assign1", VarOk: "res", + VarErr: "", Val: { + "$type": "workflow.Apply", "workflow.Apply": { ID: "apply1", Name: "concat", Args: [ { + "$type": "workflow.SetValue", "workflow.SetValue": { Value: { "schema.String": "await hello ", @@ -300,6 +353,7 @@ function runContactAwait(imageWidth: number, imageHeight: number, onData?: (data } }, { + "$type": "workflow.GetValue", "workflow.GetValue": { Path: "input.prompt", } @@ -313,9 +367,11 @@ function runContactAwait(imageWidth: number, imageHeight: number, onData?: (data }, }, { + "$type": "workflow.End", "workflow.End": { ID: "end1", Result: { + "$type": "workflow.GetValue", "workflow.GetValue": { Path: "res", } @@ -327,9 +383,26 @@ function runContactAwait(imageWidth: number, imageHeight: number, onData?: (data }, Input: { "schema.Map": { - "prompt": "no text", - "width": imageWidth, - "height": imageHeight, + Field: [ + { + Name: "prompt", + Value: { + "schema.String": "no text", + } + }, + { + Name: "width", + Value: { + "schema.Number": imageWidth, + } + }, + { + Name: "height", + Value: { + "schema.Number": imageHeight, + } + }, + ] }, }, } @@ -341,7 +414,7 @@ function runContactAwait(imageWidth: number, imageHeight: number, onData?: (data fetch('http://localhost:8080/', { method: 'POST', - body: JSON.stringify(dediscriminateCommand(cmd)), + body: JSON.stringify(cmd), }) .then(res => res.json()) .then((data: workflow.State) => { @@ -351,6 +424,7 @@ function runContactAwait(imageWidth: number, imageHeight: number, onData?: (data function submitCallbackResult(onData?: (data: workflow.State) => void) { const cmd: workflow.Command = { + "$type": "workflow.Callback", "workflow.Callback": { CallbackID: "callback_id", Result: { @@ -361,7 +435,7 @@ function submitCallbackResult(onData?: (data: workflow.State) => void) { fetch('http://localhost:8080/callback', { method: 'POST', - body: JSON.stringify(dediscriminateCommand(cmd)), + body: JSON.stringify(cmd), }) .then(res => res.json()) .then((data: workflow.State) => { @@ -386,6 +460,21 @@ function App() { const [flows, setFlows] = React.useState({Items: [] as recordFlow[]}); + const setImageFromState = (data: workflow.State) => { + if ("workflow.Done" in data) { + if (data["workflow.Done"].Result) { + let result = data["workflow.Done"].Result + if ("schema.Binary" in result) { + if (typeof result["schema.Binary"]?.B === "string") { + setImage(result["schema.Binary"]?.B) + } + } + } + } else if ("workflow.Error" in data) { + console.log(data["workflow.Error"]) + } + } + return (
@@ -411,11 +500,7 @@ function App() { onSubmit={(e) => { e.preventDefault() generateImage(imageWidth, imageHeight, (data) => { - if ("workflow.Done" in data) { - setImage(data["workflow.Done"].Result["schema.Binary"]); - } else if ("workflow.Error" in data) { - console.log(data["workflow.Error"]) - } + setImageFromState(data) }) }} > @@ -457,11 +542,7 @@ function App() { onSubmit={(e) => { e.preventDefault() runFlow(selectedFlow, input, (data) => { - if ("workflow.Done" in data) { - setImage(data["workflow.Done"].Result["schema.Binary"]); - } else if ("workflow.Error" in data) { - console.log(data["workflow.Error"]) - } + setImageFromState(data) }) }} > @@ -485,11 +566,7 @@ function App() {
- ); + ) + ; } export default App; @@ -703,7 +775,7 @@ function ListVariables(props: { data: workflow.BaseState }) { ); } -function SchemaValue(props: { data: schema.Schema }) { +function SchemaValue(props: { data?: schema.Schema }) { // check if props.data is an object if (typeof props.data !== 'object') { return <>{JSON.stringify(props.data)} @@ -715,9 +787,9 @@ function SchemaValue(props: { data: schema.Schema }) { return <>binary } else if ("schema.Map" in props.data) { const mapData = props.data["schema.Map"]; - const keys = Object.keys(mapData); + const keys = mapData.Field - if (keys.length === 0) { + if (keys && keys.length === 0) { return null; // If the map is empty, return null (no table to display) } @@ -730,11 +802,11 @@ function SchemaValue(props: { data: schema.Schema }) { - {keys.map((key) => ( - - {key} + {keys && keys.map((key) => ( + + {key.Name} - + ))} @@ -816,23 +888,29 @@ function SchedguledRun(props: { input: string }) { * } */ const cmd: workflow.Command = { + "$type": "workflow.Run", "workflow.Run": { Flow: { + "$type": "workflow.Flow", "workflow.Flow": { Name: "create_attachment", Arg: "input", Body: [ { + "$type": "workflow.Choose", "workflow.Choose": { If: { + "$type": "workflow.Compare", "workflow.Compare": { Operation: "=", Left: { + "$type": "workflow.GetValue", "workflow.GetValue": { Path: "input", } }, Right: { + "$type": "workflow.SetValue", "workflow.SetValue": { Value: { "schema.String": "666", @@ -843,8 +921,10 @@ function SchedguledRun(props: { input: string }) { }, Then: [ { + "$type": "workflow.End", "workflow.End": { Result: { + "$type": "workflow.SetValue", "workflow.SetValue": { Value: { "schema.String": "Do no evil", @@ -857,13 +937,16 @@ function SchedguledRun(props: { input: string }) { } }, { + "$type": "workflow.Assign", "workflow.Assign": { VarOk: "res", Val: { + "$type": "workflow.Apply", "workflow.Apply": { Name: "concat", Args: [ { + "$type": "workflow.SetValue", "workflow.SetValue": { Value: { "schema.String": "hello ", @@ -871,6 +954,7 @@ function SchedguledRun(props: { input: string }) { } }, { + "$type": "workflow.GetValue", "workflow.GetValue": { Path: "input", } @@ -881,8 +965,10 @@ function SchedguledRun(props: { input: string }) { }, }, { + "$type": "workflow.End", "workflow.End": { Result: { + "$type": "workflow.GetValue", "workflow.GetValue": { Path: "res", } @@ -896,6 +982,7 @@ function SchedguledRun(props: { input: string }) { "schema.String": props.input, }, RunOption: { + "$type": "workflow.ScheduleRun", "workflow.ScheduleRun": { Interval: "@every 10s" }, @@ -913,7 +1000,7 @@ function SchedguledRun(props: { input: string }) { fetch('http://localhost:8080/', { method: 'POST', - body: JSON.stringify(dediscriminateCommand(cmd)), + body: JSON.stringify(cmd), }) .then(res => res.json()) // .then(data => setState(data)) @@ -927,6 +1014,7 @@ function SchedguledRun(props: { input: string }) { function stopSchedule(parentRunID: string) { const cmd: workflow.Command = { + "$type": "workflow.StopSchedule", "workflow.StopSchedule": { ParentRunID: parentRunID, } @@ -934,7 +1022,7 @@ function stopSchedule(parentRunID: string) { return fetch('http://localhost:8080/', { method: 'POST', - body: JSON.stringify(dediscriminateCommand(cmd)), + body: JSON.stringify(cmd), }) .then(res => res.json()) .then(data => data as workflow.State) @@ -943,6 +1031,7 @@ function stopSchedule(parentRunID: string) { function resumeSchedule(parentRunID: string) { const cmd: workflow.Command = { + "$type": "workflow.ResumeSchedule", "workflow.ResumeSchedule": { ParentRunID: parentRunID, } @@ -950,7 +1039,7 @@ function resumeSchedule(parentRunID: string) { return fetch('http://localhost:8080/', { method: 'POST', - body: JSON.stringify(dediscriminateCommand(cmd)), + body: JSON.stringify(cmd), }) .then(res => res.json()) .then(data => data as workflow.State) diff --git a/example/my-app/src/Chat.tsx b/example/my-app/src/Chat.tsx index 09ed573f..da191dbb 100644 --- a/example/my-app/src/Chat.tsx +++ b/example/my-app/src/Chat.tsx @@ -1,6 +1,6 @@ import {useEffect, useState} from "react"; import './Chat.css'; -import {ChatCMD, ChatResult, dediscriminateChatCMD} from "./workflow/main"; +import {ChatCMD, ChatResult} from "./workflow/github_com_widmogrod_mkunion_exammple_my-app"; type Message = { text: string, @@ -65,6 +65,7 @@ export function Chat({props}: ChatParams) { } let cmd: ChatCMD = { + "$type": "main.UserMessage", "main.UserMessage": { "Message": lastMessage.text, }, @@ -75,7 +76,7 @@ export function Chat({props}: ChatParams) { headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify(dediscriminateChatCMD(cmd)), + body: JSON.stringify(cmd), }) .then(res => res.json() as Promise) .then(data => { diff --git a/example/my-app/src/workflow/github_com_widmogrod_mkunion_exammple_my-app.ts b/example/my-app/src/workflow/github_com_widmogrod_mkunion_exammple_my-app.ts new file mode 100644 index 00000000..eb934747 --- /dev/null +++ b/example/my-app/src/workflow/github_com_widmogrod_mkunion_exammple_my-app.ts @@ -0,0 +1,55 @@ +//generated by mkunion +export type ChatCMD = { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "main.UserMessage", + "main.UserMessage": UserMessage +} + +export type UserMessage = { + Message?: string, +} + +//generated by mkunion +export type ChatResult = { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "main.SystemResponse", + "main.SystemResponse": SystemResponse +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "main.UserResponse", + "main.UserResponse": UserResponse +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "main.ChatResponses", + "main.ChatResponses": ChatResponses +} + +export type SystemResponse = { + Message?: string, + ToolCalls?: any[], +} + +export type UserResponse = { + Message?: string, +} + +export type ChatResponses = { + Responses?: ChatResult[], +} + +export type Service = { +} +export type ListWorkflowsFn = { + Count?: number, + Words?: string[], + EnumTest?: string, +} +export type RefreshStates = { +} +export type RefreshFlows = { +} +export type GenerateImage = { + Width?: number, + Height?: number, +} + diff --git a/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_schema.ts b/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_schema.ts index cc9b7dc7..ab3b78f8 100644 --- a/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_schema.ts +++ b/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_schema.ts @@ -1 +1,51 @@ -export type Schema = any \ No newline at end of file +//generated by mkunion +export type Schema = { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "schema.None", + "schema.None": None +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "schema.Bool", + "schema.Bool": boolean +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "schema.Number", + "schema.Number": number +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "schema.String", + "schema.String": string +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "schema.Binary", + "schema.Binary": Binary +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "schema.List", + "schema.List": List +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "schema.Map", + "schema.Map": Map +} + +export type None = { +} + +export type Binary = { + B?: string, +} + +export type List = { + Items?: Schema[], +} + +export type Map = { + Field?: Field[], +} + +export type Field = { + Name?: string, + Value?: Schema, +} + diff --git a/example/my-app/src/workflow/workflow.ts b/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_workflow.ts similarity index 58% rename from example/my-app/src/workflow/workflow.ts rename to example/my-app/src/workflow/github_com_widmogrod_mkunion_x_workflow.ts index 72d8ce0c..2fa5f02b 100644 --- a/example/my-app/src/workflow/workflow.ts +++ b/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_workflow.ts @@ -1,31 +1,68 @@ //generated by mkunion +export type RunOption = { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.ScheduleRun", + "workflow.ScheduleRun": ScheduleRun +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.DelayRun", + "workflow.DelayRun": DelayRun +} + +export type ScheduleRun = { + Interval?: string, +} + +export type DelayRun = { + DelayBySeconds?: number, +} -/** -* This function is used to remove $type field from State, so it can be understood by mkunion, -* that is assuming unions have one field in object, that is used to discriminate between variants. -*/ -export function dediscriminateState(x: State): State { - if (x["$type"] !== undefined) { - delete x["$type"] - return x - } - return x -} - -/** -* This function is used to populate $type field in State, so it can be used as discriminative switch statement -* @example https://www.typescriptlang.org/play#example/discriminate-types -*/ -export function discriminateState(x: State): State { - if (x["$type"] === undefined) { - let keyx = Object.keys(x) - if (keyx.length === 1) { - x["$type"] = keyx[0] as any - } - } - return x +//generated by mkunion +export type Command = { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.Run", + "workflow.Run": Run +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.Callback", + "workflow.Callback": Callback +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.TryRecover", + "workflow.TryRecover": TryRecover +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.StopSchedule", + "workflow.StopSchedule": StopSchedule +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.ResumeSchedule", + "workflow.ResumeSchedule": ResumeSchedule } +export type Run = { + Flow?: Worflow, + Input?: schema.Schema, + RunOption?: RunOption, +} + +export type Callback = { + CallbackID?: string, + Result?: schema.Schema, +} + +export type TryRecover = { +} + +export type StopSchedule = { + ParentRunID?: string, +} + +export type ResumeSchedule = { + ParentRunID?: string, +} + +//generated by mkunion export type State = { // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema "$type"?: "workflow.NextOperation", @@ -87,33 +124,6 @@ export type ScheduleStopped = { } //generated by mkunion - -/** -* This function is used to remove $type field from Worflow, so it can be understood by mkunion, -* that is assuming unions have one field in object, that is used to discriminate between variants. -*/ -export function dediscriminateWorflow(x: Worflow): Worflow { - if (x["$type"] !== undefined) { - delete x["$type"] - return x - } - return x -} - -/** -* This function is used to populate $type field in Worflow, so it can be used as discriminative switch statement -* @example https://www.typescriptlang.org/play#example/discriminate-types -*/ -export function discriminateWorflow(x: Worflow): Worflow { - if (x["$type"] === undefined) { - let keyx = Object.keys(x) - if (keyx.length === 1) { - x["$type"] = keyx[0] as any - } - } - return x -} - export type Worflow = { // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema "$type"?: "workflow.Flow", @@ -135,33 +145,6 @@ export type FlowRef = { } //generated by mkunion - -/** -* This function is used to remove $type field from Expr, so it can be understood by mkunion, -* that is assuming unions have one field in object, that is used to discriminate between variants. -*/ -export function dediscriminateExpr(x: Expr): Expr { - if (x["$type"] !== undefined) { - delete x["$type"] - return x - } - return x -} - -/** -* This function is used to populate $type field in Expr, so it can be used as discriminative switch statement -* @example https://www.typescriptlang.org/play#example/discriminate-types -*/ -export function discriminateExpr(x: Expr): Expr { - if (x["$type"] === undefined) { - let keyx = Object.keys(x) - if (keyx.length === 1) { - x["$type"] = keyx[0] as any - } - } - return x -} - export type Expr = { // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema "$type"?: "workflow.End", @@ -207,33 +190,6 @@ export type Choose = { } //generated by mkunion - -/** -* This function is used to remove $type field from Reshaper, so it can be understood by mkunion, -* that is assuming unions have one field in object, that is used to discriminate between variants. -*/ -export function dediscriminateReshaper(x: Reshaper): Reshaper { - if (x["$type"] !== undefined) { - delete x["$type"] - return x - } - return x -} - -/** -* This function is used to populate $type field in Reshaper, so it can be used as discriminative switch statement -* @example https://www.typescriptlang.org/play#example/discriminate-types -*/ -export function discriminateReshaper(x: Reshaper): Reshaper { - if (x["$type"] === undefined) { - let keyx = Object.keys(x) - if (keyx.length === 1) { - x["$type"] = keyx[0] as any - } - } - return x -} - export type Reshaper = { // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema "$type"?: "workflow.GetValue", @@ -253,33 +209,6 @@ export type SetValue = { } //generated by mkunion - -/** -* This function is used to remove $type field from Predicate, so it can be understood by mkunion, -* that is assuming unions have one field in object, that is used to discriminate between variants. -*/ -export function dediscriminatePredicate(x: Predicate): Predicate { - if (x["$type"] !== undefined) { - delete x["$type"] - return x - } - return x -} - -/** -* This function is used to populate $type field in Predicate, so it can be used as discriminative switch statement -* @example https://www.typescriptlang.org/play#example/discriminate-types -*/ -export function discriminatePredicate(x: Predicate): Predicate { - if (x["$type"] === undefined) { - let keyx = Object.keys(x) - if (keyx.length === 1) { - x["$type"] = keyx[0] as any - } - } - return x -} - export type Predicate = { // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema "$type"?: "workflow.And", @@ -316,127 +245,6 @@ export type Compare = { Right?: Reshaper, } -//generated by mkunion - -/** -* This function is used to remove $type field from RunOption, so it can be understood by mkunion, -* that is assuming unions have one field in object, that is used to discriminate between variants. -*/ -export function dediscriminateRunOption(x: RunOption): RunOption { - if (x["$type"] !== undefined) { - delete x["$type"] - return x - } - return x -} - -/** -* This function is used to populate $type field in RunOption, so it can be used as discriminative switch statement -* @example https://www.typescriptlang.org/play#example/discriminate-types -*/ -export function discriminateRunOption(x: RunOption): RunOption { - if (x["$type"] === undefined) { - let keyx = Object.keys(x) - if (keyx.length === 1) { - x["$type"] = keyx[0] as any - } - } - return x -} - -export type RunOption = { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.ScheduleRun", - "workflow.ScheduleRun": ScheduleRun -} | { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.DelayRun", - "workflow.DelayRun": DelayRun -} - -export type ScheduleRun = { - Interval?: string, -} - -export type DelayRun = { - DelayBySeconds?: number, -} - -//generated by mkunion - -/** -* This function is used to remove $type field from Command, so it can be understood by mkunion, -* that is assuming unions have one field in object, that is used to discriminate between variants. -*/ -export function dediscriminateCommand(x: Command): Command { - if (x["$type"] !== undefined) { - delete x["$type"] - return x - } - return x -} - -/** -* This function is used to populate $type field in Command, so it can be used as discriminative switch statement -* @example https://www.typescriptlang.org/play#example/discriminate-types -*/ -export function discriminateCommand(x: Command): Command { - if (x["$type"] === undefined) { - let keyx = Object.keys(x) - if (keyx.length === 1) { - x["$type"] = keyx[0] as any - } - } - return x -} - -export type Command = { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.Run", - "workflow.Run": Run -} | { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.Callback", - "workflow.Callback": Callback -} | { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.TryRecover", - "workflow.TryRecover": TryRecover -} | { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.StopSchedule", - "workflow.StopSchedule": StopSchedule -} | { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.ResumeSchedule", - "workflow.ResumeSchedule": ResumeSchedule -} - -export type Run = { - Flow?: Worflow, - Input?: schema.Schema, - RunOption?: RunOption, -} - -export type Callback = { - CallbackID?: string, - Result?: schema.Schema, -} - -export type TryRecover = { -} - -export type StopSchedule = { - ParentRunID?: string, -} - -export type ResumeSchedule = { - ParentRunID?: string, -} - -export type ApplyAwaitOptions = { - Timeout?: number, -} export type BaseState = { Flow?: Worflow, RunID?: string, @@ -446,6 +254,9 @@ export type BaseState = { DefaultMaxRetries?: number, RunOption?: RunOption, } +export type ApplyAwaitOptions = { + Timeout?: number, +} export type ResumeOptions = { Timeout?: number, } @@ -457,14 +268,27 @@ export type Execution = { EndTime?: number, Variables?: {[key: string]: any}, } +//generated by mkunion +export type FunctionDSL = { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.FunctionInput", + "workflow.FunctionInput": FunctionInput +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.FunctionOutput", + "workflow.FunctionOutput": FunctionOutput +} + export type FunctionInput = { Name?: string, CallbackID?: string, Args?: any[], } + export type FunctionOutput = { Result?: schema.Schema, } + //eslint-disable-next-line import * as schema from './github_com_widmogrod_mkunion_x_schema' diff --git a/example/my-app/src/workflow/time.ts b/example/my-app/src/workflow/time.ts deleted file mode 100644 index 02d4cf04..00000000 --- a/example/my-app/src/workflow/time.ts +++ /dev/null @@ -1 +0,0 @@ -export type Duration = number \ No newline at end of file diff --git a/x/generators/deser_json_generator.go b/x/generators/deser_json_generator.go index f94d7243..e9b213cd 100644 --- a/x/generators/deser_json_generator.go +++ b/x/generators/deser_json_generator.go @@ -63,43 +63,76 @@ func (g *DeSerJSONGenerator) JSONVariantName(x shape.Shape) string { panic(fmt.Errorf("generators.JSONVariantName: %T not suported", y)) }, func(y *shape.RefName) string { - return fmt.Sprintf("%s.%s", y.PkgImportName, y.Name) + return fmt.Sprintf("%s.%s", y.PkgName, y.Name) }, func(y *shape.BooleanLike) string { if shape.IsNamed(y) { - return fmt.Sprintf("%s.%s", y.Named.PkgImportName, y.Named.Name) + return fmt.Sprintf("%s.%s", y.Named.PkgName, y.Named.Name) } panic(fmt.Errorf("generators.JSONVariantName: must be named %T", y)) }, func(y *shape.StringLike) string { if shape.IsNamed(y) { - return fmt.Sprintf("%s.%s", y.Named.PkgImportName, y.Named.Name) + return fmt.Sprintf("%s.%s", y.Named.PkgName, y.Named.Name) } panic(fmt.Errorf("generators.JSONVariantName: must be named %T", y)) }, func(y *shape.NumberLike) string { if shape.IsNamed(y) { - return fmt.Sprintf("%s.%s", y.Named.PkgImportName, y.Named.Name) + return fmt.Sprintf("%s.%s", y.Named.PkgName, y.Named.Name) } panic(fmt.Errorf("generators.JSONVariantName: must be named %T", y)) }, func(y *shape.ListLike) string { if shape.IsNamed(y) { - return fmt.Sprintf("%s.%s", y.Named.PkgImportName, y.Named.Name) + return fmt.Sprintf("%s.%s", y.Named.PkgName, y.Named.Name) } panic(fmt.Errorf("generators.JSONVariantName: must be named %T", y)) }, func(y *shape.MapLike) string { if shape.IsNamed(y) { - return fmt.Sprintf("%s.%s", y.Named.PkgImportName, y.Named.Name) + return fmt.Sprintf("%s.%s", y.Named.PkgName, y.Named.Name) } panic(fmt.Errorf("generators.JSONVariantName: must be named %T", y)) }, func(y *shape.StructLike) string { - return fmt.Sprintf("%s.%s", y.PkgImportName, y.Name) + return fmt.Sprintf("%s.%s", y.PkgName, y.Name) }, func(y *shape.UnionLike) string { - return fmt.Sprintf("%s.%s", y.PkgImportName, y.Name) + return fmt.Sprintf("%s.%s", y.PkgName, y.Name) + }, + ) +} + +func (g *DeSerJSONGenerator) SupportNativeJSONMarshal(x shape.Shape) bool { + return shape.MustMatchShape( + x, + func(y *shape.Any) bool { + return false + }, + func(y *shape.RefName) bool { + return false + }, + func(y *shape.BooleanLike) bool { + return false + }, + func(y *shape.StringLike) bool { + return false + }, + func(y *shape.NumberLike) bool { + return false + }, + func(y *shape.ListLike) bool { + return false + }, + func(y *shape.MapLike) bool { + return false + }, + func(y *shape.StructLike) bool { + return true + }, + func(y *shape.UnionLike) bool { + return false }, ) } @@ -125,12 +158,12 @@ func (g *DeSerJSONGenerator) UnmarshalTemplate(field *shape.FieldLike, depth int y, _ := shape.LookupShape(x) z, ok := y.(*shape.UnionLike) if ok { - result := bytes.Buffer{} pkgName := g.pkgNameAndImport(z) + result := bytes.Buffer{} result.WriteString(fmt.Sprintf("res, err := %s%sFromJSON(value)\n", pkgName, z.Name)) result.WriteString(fmt.Sprintf("if err != nil {\n")) - result.WriteString(fmt.Sprintf("\treturn fmt.Errorf(\"%s.%sFromJSON: %%w\", err)\n", g.Union.PkgName, z.Name)) + result.WriteString(fmt.Sprintf("\treturn fmt.Errorf(\"%s._FromJSON: field %s %%w\", err)\n", g.Union.PkgName, z.Name)) result.WriteString(fmt.Sprintf("}\n")) result.WriteString(fmt.Sprintf("result.%s = res\n", field.Name)) result.WriteString(fmt.Sprintf("return nil")) @@ -150,9 +183,47 @@ func (g *DeSerJSONGenerator) UnmarshalTemplate(field *shape.FieldLike, depth int return g.padLeft(depth, fmt.Sprintf("return json.Unmarshal(value, &result.%s)", field.Name)) }, func(x *shape.ListLike) string { + ref, ok := x.Element.(*shape.RefName) + if ok { + y, _ := shape.LookupShape(ref) + z, ok := y.(*shape.UnionLike) + if ok { + pkgName := g.pkgNameAndImport(z) + + result := bytes.Buffer{} + result.WriteString(fmt.Sprintf("res, err := shared.JSONToListWithDeserializer(value, result.%s, %s%sFromJSON)\n", field.Name, pkgName, z.Name)) + result.WriteString(fmt.Sprintf("if err != nil {\n")) + result.WriteString(fmt.Sprintf("\treturn fmt.Errorf(\"%s._FromJSON: field %s %%w\", err)\n", g.Union.PkgName, z.Name)) + result.WriteString(fmt.Sprintf("}\n")) + result.WriteString(fmt.Sprintf("result.%s = res\n", field.Name)) + result.WriteString(fmt.Sprintf("return nil")) + + return g.padLeft(depth+1, result.String()) + } + } + return g.padLeft(depth, fmt.Sprintf("return json.Unmarshal(value, &result.%s)", field.Name)) }, func(x *shape.MapLike) string { + ref, ok := x.Val.(*shape.RefName) + if ok { + y, _ := shape.LookupShape(ref) + z, ok := y.(*shape.UnionLike) + if ok { + pkgName := g.pkgNameAndImport(z) + + result := bytes.Buffer{} + result.WriteString(fmt.Sprintf("res, err := shared.JSONToMapWithDeserializer(value, result.%s, %s%sFromJSON)\n", field.Name, pkgName, z.Name)) + result.WriteString(fmt.Sprintf("if err != nil {\n")) + result.WriteString(fmt.Sprintf("\treturn fmt.Errorf(\"%s._FromJSON: field %s %%w\", err)\n", g.Union.PkgName, z.Name)) + result.WriteString(fmt.Sprintf("}\n")) + result.WriteString(fmt.Sprintf("result.%s = res\n", field.Name)) + result.WriteString(fmt.Sprintf("return nil")) + + return g.padLeft(depth+1, result.String()) + } + } + return g.padLeft(depth, fmt.Sprintf("return json.Unmarshal(value, &result.%s)", field.Name)) }, func(x *shape.StructLike) string { @@ -190,9 +261,29 @@ func (g *DeSerJSONGenerator) MarshalTemplate(field *shape.FieldLike, depth int) return g.padLeft(depth, fmt.Sprintf("json.Marshal(x.%s)", field.Name)) }, func(x *shape.ListLike) string { + ref, ok := x.Element.(*shape.RefName) + if ok { + y, _ := shape.LookupShape(ref) + z, ok := y.(*shape.UnionLike) + if ok { + pkgName := g.pkgNameAndImport(z) + return g.padLeft(depth+1, fmt.Sprintf("shared.JSONListFromSerializer(x.%s, %s%sToJSON)", field.Name, pkgName, z.Name)) + } + } + return g.padLeft(depth, fmt.Sprintf("json.Marshal(x.%s)", field.Name)) }, func(x *shape.MapLike) string { + ref, ok := x.Val.(*shape.RefName) + if ok { + y, _ := shape.LookupShape(ref) + z, ok := y.(*shape.UnionLike) + if ok { + pkgName := g.pkgNameAndImport(z) + return g.padLeft(depth+1, fmt.Sprintf("shared.JSONMapFromSerializer(x.%s, %s%sToJSON)", field.Name, pkgName, z.Name)) + } + } + return g.padLeft(depth, fmt.Sprintf("json.Marshal(x.%s)", field.Name)) }, func(x *shape.StructLike) string { diff --git a/x/generators/deser_json_generator.go.tmpl b/x/generators/deser_json_generator.go.tmpl index af18fc3f..bad0e363 100644 --- a/x/generators/deser_json_generator.go.tmpl +++ b/x/generators/deser_json_generator.go.tmpl @@ -36,6 +36,9 @@ func {{ .Union.Name }}FromJSON(x []byte) ({{ .Union.Name }}, error) { } func {{ .Union.Name }}ToJSON(x {{ .Union.Name }}) ([]byte, error) { + if x == nil { + return nil, nil + } return MustMatch{{ .Union.Name }}R2( x, {{- range $index, $variant := .Union.Variant }} @@ -59,7 +62,7 @@ func {{ $self.VariantName $variant }}FromJSON(x []byte) (*{{ $self.VariantName $ {{- if $self.IsStruct $variant }} // if is Struct - err := shared.JsonParseObject(x, func(key string, value []byte) error { + err := shared.JSONParseObject(x, func(key string, value []byte) error { switch key { {{- range $index, $field := $variant.Fields }} case "{{ $self.JSONFieldName $field }}": @@ -93,7 +96,7 @@ func {{ $self.VariantName $variant }}ToJSON(x *{{ $self.VariantName $variant }}) return json.Marshal(x) {{- end }} } - +{{- if $self.SupportNativeJSONMarshal $variant }} func (self *{{ $self.VariantName $variant }}) MarshalJSON() ([]byte, error) { return {{ $self.VariantName $variant }}ToJSON(self) } @@ -106,4 +109,5 @@ func (self *{{ $self.VariantName $variant }}) UnmarshalJSON(x []byte) error { *self = *n return nil } +{{- end}} {{end}} \ No newline at end of file diff --git a/x/generators/deser_json_generator_test_tree.go.asset b/x/generators/deser_json_generator_test_tree.go.asset index 21e2624e..553099d6 100644 --- a/x/generators/deser_json_generator_test_tree.go.asset +++ b/x/generators/deser_json_generator_test_tree.go.asset @@ -9,9 +9,9 @@ import "fmt" type TreeUnionJSON struct { Type string `json:"$type,omitempty"` - Branch json.RawMessage `json:"github.com/widmogrod/mkunion/x/generators/testutils.Branch,omitempty"` - Leaf json.RawMessage `json:"github.com/widmogrod/mkunion/x/generators/testutils.Leaf,omitempty"` - K json.RawMessage `json:"github.com/widmogrod/mkunion/x/generators/testutils.K,omitempty"` + Branch json.RawMessage `json:"testutils.Branch,omitempty"` + Leaf json.RawMessage `json:"testutils.Leaf,omitempty"` + K json.RawMessage `json:"testutils.K,omitempty"` } func TreeFromJSON(x []byte) (Tree, error) { @@ -22,11 +22,11 @@ func TreeFromJSON(x []byte) (Tree, error) { } switch data.Type { - case "github.com/widmogrod/mkunion/x/generators/testutils.Branch": + case "testutils.Branch": return BranchFromJSON(data.Branch) - case "github.com/widmogrod/mkunion/x/generators/testutils.Leaf": + case "testutils.Leaf": return LeafFromJSON(data.Leaf) - case "github.com/widmogrod/mkunion/x/generators/testutils.K": + case "testutils.K": return KFromJSON(data.K) } @@ -42,6 +42,9 @@ func TreeFromJSON(x []byte) (Tree, error) { } func TreeToJSON(x Tree) ([]byte, error) { + if x == nil { + return nil, nil + } return MustMatchTreeR2( x, func(x *Branch) ([]byte, error) { @@ -51,7 +54,7 @@ func TreeToJSON(x Tree) ([]byte, error) { } return json.Marshal(TreeUnionJSON{ - Type: "github.com/widmogrod/mkunion/x/generators/testutils.Branch", + Type: "testutils.Branch", Branch: body, }) }, @@ -62,7 +65,7 @@ func TreeToJSON(x Tree) ([]byte, error) { } return json.Marshal(TreeUnionJSON{ - Type: "github.com/widmogrod/mkunion/x/generators/testutils.Leaf", + Type: "testutils.Leaf", Leaf: body, }) }, @@ -73,7 +76,7 @@ func TreeToJSON(x Tree) ([]byte, error) { } return json.Marshal(TreeUnionJSON{ - Type: "github.com/widmogrod/mkunion/x/generators/testutils.K", + Type: "testutils.K", K: body, }) }, @@ -83,19 +86,29 @@ func TreeToJSON(x Tree) ([]byte, error) { func BranchFromJSON(x []byte) (*Branch, error) { var result *Branch = new(Branch) // if is Struct - err := shared.JsonParseObject(x, func(key string, value []byte) error { + err := shared.JSONParseObject(x, func(key string, value []byte) error { switch key { case "Lit": res, err := TreeFromJSON(value) if err != nil { - return fmt.Errorf("testutils.TreeFromJSON: %w", err) + return fmt.Errorf("testutils._FromJSON: field Tree %w", err) } result.Lit = res return nil case "List": - return json.Unmarshal(value, &result.List) + res, err := shared.JSONToListWithDeserializer(value, result.List, TreeFromJSON) + if err != nil { + return fmt.Errorf("testutils._FromJSON: field Tree %w", err) + } + result.List = res + return nil case "Map": - return json.Unmarshal(value, &result.Map) + res, err := shared.JSONToMapWithDeserializer(value, result.Map, TreeFromJSON) + if err != nil { + return fmt.Errorf("testutils._FromJSON: field Tree %w", err) + } + result.Map = res + return nil } return fmt.Errorf("testutils.BranchFromJSON: unknown key %s", key) @@ -109,11 +122,11 @@ func BranchToJSON(x *Branch) ([]byte, error) { if err != nil { return nil, err } - field_List, err := json.Marshal(x.List) + field_List, err := shared.JSONListFromSerializer(x.List, TreeToJSON) if err != nil { return nil, err } - field_Map, err := json.Marshal(x.Map) + field_Map, err := shared.JSONMapFromSerializer(x.Map, TreeToJSON) if err != nil { return nil, err } @@ -123,7 +136,6 @@ func BranchToJSON(x *Branch) ([]byte, error) { "Map": field_Map, }) } - func (self *Branch) MarshalJSON() ([]byte, error) { return BranchToJSON(self) } @@ -140,7 +152,7 @@ func (self *Branch) UnmarshalJSON(x []byte) error { func LeafFromJSON(x []byte) (*Leaf, error) { var result *Leaf = new(Leaf) // if is Struct - err := shared.JsonParseObject(x, func(key string, value []byte) error { + err := shared.JSONParseObject(x, func(key string, value []byte) error { switch key { case "Value": return json.Unmarshal(value, &result.Value) @@ -161,7 +173,6 @@ func LeafToJSON(x *Leaf) ([]byte, error) { "Value": field_Value, }) } - func (self *Leaf) MarshalJSON() ([]byte, error) { return LeafToJSON(self) } @@ -185,16 +196,3 @@ func KFromJSON(x []byte) (*K, error) { func KToJSON(x *K) ([]byte, error) { return json.Marshal(x) } - -func (self *K) MarshalJSON() ([]byte, error) { - return KToJSON(self) -} - -func (self *K) UnmarshalJSON(x []byte) error { - n, err := KFromJSON(x) - if err != nil { - return err - } - *self = *n - return nil -} diff --git a/x/generators/shape_generator.go b/x/generators/shape_generator.go index 05e2fc78..bc811075 100644 --- a/x/generators/shape_generator.go +++ b/x/generators/shape_generator.go @@ -26,13 +26,6 @@ type ShapeGenerator struct { template *template.Template } -func (g *ShapeGenerator) ImportPkg() []string { - return []string{ - "encoding/json", - "fmt", - } -} - func (g *ShapeGenerator) ident(d int) string { return strings.Repeat("\t", d) } @@ -93,6 +86,7 @@ func TemplateHelperShapeVariantToName(x shape.Shape) string { }, ) } + func (g *ShapeGenerator) ShapeToString(x shape.Shape, depth int) string { return shape.MustMatchShape( x, diff --git a/x/generators/testutils/derive_func_match_alphabet_gen.go b/x/generators/testutils/derive_func_match_alphabet_gen.go index 0dcefb79..e80d58ec 100644 --- a/x/generators/testutils/derive_func_match_alphabet_gen.go +++ b/x/generators/testutils/derive_func_match_alphabet_gen.go @@ -131,9 +131,9 @@ func C3Shape() shape.Shape { // mkunion-extension:json type AlphabetUnionJSON struct { Type string `json:"$type,omitempty"` - A1 json.RawMessage `json:"github.com/widmogrod/mkunion/x/generators/testutils.A1,omitempty"` - B2 json.RawMessage `json:"github.com/widmogrod/mkunion/x/generators/testutils.B2,omitempty"` - C3 json.RawMessage `json:"github.com/widmogrod/mkunion/x/generators/testutils.C3,omitempty"` + A1 json.RawMessage `json:"testutils.A1,omitempty"` + B2 json.RawMessage `json:"testutils.B2,omitempty"` + C3 json.RawMessage `json:"testutils.C3,omitempty"` } func AlphabetFromJSON(x []byte) (Alphabet, error) { @@ -144,11 +144,11 @@ func AlphabetFromJSON(x []byte) (Alphabet, error) { } switch data.Type { - case "github.com/widmogrod/mkunion/x/generators/testutils.A1": + case "testutils.A1": return A1FromJSON(data.A1) - case "github.com/widmogrod/mkunion/x/generators/testutils.B2": + case "testutils.B2": return B2FromJSON(data.B2) - case "github.com/widmogrod/mkunion/x/generators/testutils.C3": + case "testutils.C3": return C3FromJSON(data.C3) } @@ -164,6 +164,9 @@ func AlphabetFromJSON(x []byte) (Alphabet, error) { } func AlphabetToJSON(x Alphabet) ([]byte, error) { + if x == nil { + return nil, nil + } return MustMatchAlphabetR2( x, func(x *A1) ([]byte, error) { @@ -173,7 +176,7 @@ func AlphabetToJSON(x Alphabet) ([]byte, error) { } return json.Marshal(AlphabetUnionJSON{ - Type: "github.com/widmogrod/mkunion/x/generators/testutils.A1", + Type: "testutils.A1", A1: body, }) }, @@ -184,7 +187,7 @@ func AlphabetToJSON(x Alphabet) ([]byte, error) { } return json.Marshal(AlphabetUnionJSON{ - Type: "github.com/widmogrod/mkunion/x/generators/testutils.B2", + Type: "testutils.B2", B2: body, }) }, @@ -195,7 +198,7 @@ func AlphabetToJSON(x Alphabet) ([]byte, error) { } return json.Marshal(AlphabetUnionJSON{ - Type: "github.com/widmogrod/mkunion/x/generators/testutils.C3", + Type: "testutils.C3", C3: body, }) }, @@ -205,7 +208,7 @@ func AlphabetToJSON(x Alphabet) ([]byte, error) { func A1FromJSON(x []byte) (*A1, error) { var result *A1 = new(A1) // if is Struct - err := shared.JsonParseObject(x, func(key string, value []byte) error { + err := shared.JSONParseObject(x, func(key string, value []byte) error { switch key { } @@ -218,7 +221,6 @@ func A1FromJSON(x []byte) (*A1, error) { func A1ToJSON(x *A1) ([]byte, error) { return json.Marshal(map[string]json.RawMessage{}) } - func (self *A1) MarshalJSON() ([]byte, error) { return A1ToJSON(self) } @@ -235,7 +237,7 @@ func (self *A1) UnmarshalJSON(x []byte) error { func B2FromJSON(x []byte) (*B2, error) { var result *B2 = new(B2) // if is Struct - err := shared.JsonParseObject(x, func(key string, value []byte) error { + err := shared.JSONParseObject(x, func(key string, value []byte) error { switch key { } @@ -248,7 +250,6 @@ func B2FromJSON(x []byte) (*B2, error) { func B2ToJSON(x *B2) ([]byte, error) { return json.Marshal(map[string]json.RawMessage{}) } - func (self *B2) MarshalJSON() ([]byte, error) { return B2ToJSON(self) } @@ -265,7 +266,7 @@ func (self *B2) UnmarshalJSON(x []byte) error { func C3FromJSON(x []byte) (*C3, error) { var result *C3 = new(C3) // if is Struct - err := shared.JsonParseObject(x, func(key string, value []byte) error { + err := shared.JSONParseObject(x, func(key string, value []byte) error { switch key { } @@ -278,7 +279,6 @@ func C3FromJSON(x []byte) (*C3, error) { func C3ToJSON(x *C3) ([]byte, error) { return json.Marshal(map[string]json.RawMessage{}) } - func (self *C3) MarshalJSON() ([]byte, error) { return C3ToJSON(self) } diff --git a/x/generators/testutils/derive_func_match_number_gen.go b/x/generators/testutils/derive_func_match_number_gen.go index b8f13217..77fe4ed3 100644 --- a/x/generators/testutils/derive_func_match_number_gen.go +++ b/x/generators/testutils/derive_func_match_number_gen.go @@ -113,8 +113,8 @@ func N1Shape() shape.Shape { // mkunion-extension:json type NumberUnionJSON struct { Type string `json:"$type,omitempty"` - N0 json.RawMessage `json:"github.com/widmogrod/mkunion/x/generators/testutils.N0,omitempty"` - N1 json.RawMessage `json:"github.com/widmogrod/mkunion/x/generators/testutils.N1,omitempty"` + N0 json.RawMessage `json:"testutils.N0,omitempty"` + N1 json.RawMessage `json:"testutils.N1,omitempty"` } func NumberFromJSON(x []byte) (Number, error) { @@ -125,9 +125,9 @@ func NumberFromJSON(x []byte) (Number, error) { } switch data.Type { - case "github.com/widmogrod/mkunion/x/generators/testutils.N0": + case "testutils.N0": return N0FromJSON(data.N0) - case "github.com/widmogrod/mkunion/x/generators/testutils.N1": + case "testutils.N1": return N1FromJSON(data.N1) } @@ -141,6 +141,9 @@ func NumberFromJSON(x []byte) (Number, error) { } func NumberToJSON(x Number) ([]byte, error) { + if x == nil { + return nil, nil + } return MustMatchNumberR2( x, func(x *N0) ([]byte, error) { @@ -150,7 +153,7 @@ func NumberToJSON(x Number) ([]byte, error) { } return json.Marshal(NumberUnionJSON{ - Type: "github.com/widmogrod/mkunion/x/generators/testutils.N0", + Type: "testutils.N0", N0: body, }) }, @@ -161,7 +164,7 @@ func NumberToJSON(x Number) ([]byte, error) { } return json.Marshal(NumberUnionJSON{ - Type: "github.com/widmogrod/mkunion/x/generators/testutils.N1", + Type: "testutils.N1", N1: body, }) }, @@ -171,7 +174,7 @@ func NumberToJSON(x Number) ([]byte, error) { func N0FromJSON(x []byte) (*N0, error) { var result *N0 = new(N0) // if is Struct - err := shared.JsonParseObject(x, func(key string, value []byte) error { + err := shared.JSONParseObject(x, func(key string, value []byte) error { switch key { } @@ -184,7 +187,6 @@ func N0FromJSON(x []byte) (*N0, error) { func N0ToJSON(x *N0) ([]byte, error) { return json.Marshal(map[string]json.RawMessage{}) } - func (self *N0) MarshalJSON() ([]byte, error) { return N0ToJSON(self) } @@ -201,7 +203,7 @@ func (self *N0) UnmarshalJSON(x []byte) error { func N1FromJSON(x []byte) (*N1, error) { var result *N1 = new(N1) // if is Struct - err := shared.JsonParseObject(x, func(key string, value []byte) error { + err := shared.JSONParseObject(x, func(key string, value []byte) error { switch key { } @@ -214,7 +216,6 @@ func N1FromJSON(x []byte) (*N1, error) { func N1ToJSON(x *N1) ([]byte, error) { return json.Marshal(map[string]json.RawMessage{}) } - func (self *N1) MarshalJSON() ([]byte, error) { return N1ToJSON(self) } diff --git a/x/generators/testutils/tree_tree_gen.go b/x/generators/testutils/tree_tree_gen.go index 2766365d..610b9dea 100644 --- a/x/generators/testutils/tree_tree_gen.go +++ b/x/generators/testutils/tree_tree_gen.go @@ -405,9 +405,9 @@ func KShape() shape.Shape { // mkunion-extension:json type TreeUnionJSON struct { Type string `json:"$type,omitempty"` - Branch json.RawMessage `json:"github.com/widmogrod/mkunion/x/generators/testutils.Branch,omitempty"` - Leaf json.RawMessage `json:"github.com/widmogrod/mkunion/x/generators/testutils.Leaf,omitempty"` - K json.RawMessage `json:"github.com/widmogrod/mkunion/x/generators/testutils.K,omitempty"` + Branch json.RawMessage `json:"testutils.Branch,omitempty"` + Leaf json.RawMessage `json:"testutils.Leaf,omitempty"` + K json.RawMessage `json:"testutils.K,omitempty"` } func TreeFromJSON(x []byte) (Tree, error) { @@ -418,11 +418,11 @@ func TreeFromJSON(x []byte) (Tree, error) { } switch data.Type { - case "github.com/widmogrod/mkunion/x/generators/testutils.Branch": + case "testutils.Branch": return BranchFromJSON(data.Branch) - case "github.com/widmogrod/mkunion/x/generators/testutils.Leaf": + case "testutils.Leaf": return LeafFromJSON(data.Leaf) - case "github.com/widmogrod/mkunion/x/generators/testutils.K": + case "testutils.K": return KFromJSON(data.K) } @@ -438,6 +438,9 @@ func TreeFromJSON(x []byte) (Tree, error) { } func TreeToJSON(x Tree) ([]byte, error) { + if x == nil { + return nil, nil + } return MustMatchTreeR2( x, func(x *Branch) ([]byte, error) { @@ -447,7 +450,7 @@ func TreeToJSON(x Tree) ([]byte, error) { } return json.Marshal(TreeUnionJSON{ - Type: "github.com/widmogrod/mkunion/x/generators/testutils.Branch", + Type: "testutils.Branch", Branch: body, }) }, @@ -458,7 +461,7 @@ func TreeToJSON(x Tree) ([]byte, error) { } return json.Marshal(TreeUnionJSON{ - Type: "github.com/widmogrod/mkunion/x/generators/testutils.Leaf", + Type: "testutils.Leaf", Leaf: body, }) }, @@ -469,7 +472,7 @@ func TreeToJSON(x Tree) ([]byte, error) { } return json.Marshal(TreeUnionJSON{ - Type: "github.com/widmogrod/mkunion/x/generators/testutils.K", + Type: "testutils.K", K: body, }) }, @@ -479,19 +482,29 @@ func TreeToJSON(x Tree) ([]byte, error) { func BranchFromJSON(x []byte) (*Branch, error) { var result *Branch = new(Branch) // if is Struct - err := shared.JsonParseObject(x, func(key string, value []byte) error { + err := shared.JSONParseObject(x, func(key string, value []byte) error { switch key { case "Lit": res, err := TreeFromJSON(value) if err != nil { - return fmt.Errorf("testutils.TreeFromJSON: %w", err) + return fmt.Errorf("testutils._FromJSON: field Tree %w", err) } result.Lit = res return nil case "List": - return json.Unmarshal(value, &result.List) + res, err := shared.JSONToListWithDeserializer(value, result.List, TreeFromJSON) + if err != nil { + return fmt.Errorf("testutils._FromJSON: field Tree %w", err) + } + result.List = res + return nil case "Map": - return json.Unmarshal(value, &result.Map) + res, err := shared.JSONToMapWithDeserializer(value, result.Map, TreeFromJSON) + if err != nil { + return fmt.Errorf("testutils._FromJSON: field Tree %w", err) + } + result.Map = res + return nil } return fmt.Errorf("testutils.BranchFromJSON: unknown key %s", key) @@ -505,11 +518,11 @@ func BranchToJSON(x *Branch) ([]byte, error) { if err != nil { return nil, err } - field_List, err := json.Marshal(x.List) + field_List, err := shared.JSONListFromSerializer(x.List, TreeToJSON) if err != nil { return nil, err } - field_Map, err := json.Marshal(x.Map) + field_Map, err := shared.JSONMapFromSerializer(x.Map, TreeToJSON) if err != nil { return nil, err } @@ -519,7 +532,6 @@ func BranchToJSON(x *Branch) ([]byte, error) { "Map": field_Map, }) } - func (self *Branch) MarshalJSON() ([]byte, error) { return BranchToJSON(self) } @@ -536,7 +548,7 @@ func (self *Branch) UnmarshalJSON(x []byte) error { func LeafFromJSON(x []byte) (*Leaf, error) { var result *Leaf = new(Leaf) // if is Struct - err := shared.JsonParseObject(x, func(key string, value []byte) error { + err := shared.JSONParseObject(x, func(key string, value []byte) error { switch key { case "Value": return json.Unmarshal(value, &result.Value) @@ -557,7 +569,6 @@ func LeafToJSON(x *Leaf) ([]byte, error) { "Value": field_Value, }) } - func (self *Leaf) MarshalJSON() ([]byte, error) { return LeafToJSON(self) } @@ -581,16 +592,3 @@ func KFromJSON(x []byte) (*K, error) { func KToJSON(x *K) ([]byte, error) { return json.Marshal(x) } - -func (self *K) MarshalJSON() ([]byte, error) { - return KToJSON(self) -} - -func (self *K) UnmarshalJSON(x []byte) error { - n, err := KFromJSON(x) - if err != nil { - return err - } - *self = *n - return nil -} diff --git a/x/schema/location_location_gen.go b/x/schema/location_location_gen.go index 0c8426c3..9b57aea8 100644 --- a/x/schema/location_location_gen.go +++ b/x/schema/location_location_gen.go @@ -290,9 +290,9 @@ func (t *LocationDefaultVisitor[A]) VisitLocationAnything(v *LocationAnything) a // mkunion-extension:json type LocationUnionJSON struct { Type string `json:"$type,omitempty"` - LocationField json.RawMessage `json:"github.com/widmogrod/mkunion/x/schema.LocationField,omitempty"` - LocationIndex json.RawMessage `json:"github.com/widmogrod/mkunion/x/schema.LocationIndex,omitempty"` - LocationAnything json.RawMessage `json:"github.com/widmogrod/mkunion/x/schema.LocationAnything,omitempty"` + LocationField json.RawMessage `json:"schema.LocationField,omitempty"` + LocationIndex json.RawMessage `json:"schema.LocationIndex,omitempty"` + LocationAnything json.RawMessage `json:"schema.LocationAnything,omitempty"` } func LocationFromJSON(x []byte) (Location, error) { @@ -303,11 +303,11 @@ func LocationFromJSON(x []byte) (Location, error) { } switch data.Type { - case "github.com/widmogrod/mkunion/x/schema.LocationField": + case "schema.LocationField": return LocationFieldFromJSON(data.LocationField) - case "github.com/widmogrod/mkunion/x/schema.LocationIndex": + case "schema.LocationIndex": return LocationIndexFromJSON(data.LocationIndex) - case "github.com/widmogrod/mkunion/x/schema.LocationAnything": + case "schema.LocationAnything": return LocationAnythingFromJSON(data.LocationAnything) } @@ -323,6 +323,9 @@ func LocationFromJSON(x []byte) (Location, error) { } func LocationToJSON(x Location) ([]byte, error) { + if x == nil { + return nil, nil + } return MustMatchLocationR2( x, func(x *LocationField) ([]byte, error) { @@ -332,7 +335,7 @@ func LocationToJSON(x Location) ([]byte, error) { } return json.Marshal(LocationUnionJSON{ - Type: "github.com/widmogrod/mkunion/x/schema.LocationField", + Type: "schema.LocationField", LocationField: body, }) }, @@ -343,7 +346,7 @@ func LocationToJSON(x Location) ([]byte, error) { } return json.Marshal(LocationUnionJSON{ - Type: "github.com/widmogrod/mkunion/x/schema.LocationIndex", + Type: "schema.LocationIndex", LocationIndex: body, }) }, @@ -354,7 +357,7 @@ func LocationToJSON(x Location) ([]byte, error) { } return json.Marshal(LocationUnionJSON{ - Type: "github.com/widmogrod/mkunion/x/schema.LocationAnything", + Type: "schema.LocationAnything", LocationAnything: body, }) }, @@ -364,7 +367,7 @@ func LocationToJSON(x Location) ([]byte, error) { func LocationFieldFromJSON(x []byte) (*LocationField, error) { var result *LocationField = new(LocationField) // if is Struct - err := shared.JsonParseObject(x, func(key string, value []byte) error { + err := shared.JSONParseObject(x, func(key string, value []byte) error { switch key { case "Name": return json.Unmarshal(value, &result.Name) @@ -385,7 +388,6 @@ func LocationFieldToJSON(x *LocationField) ([]byte, error) { "Name": field_Name, }) } - func (self *LocationField) MarshalJSON() ([]byte, error) { return LocationFieldToJSON(self) } @@ -402,7 +404,7 @@ func (self *LocationField) UnmarshalJSON(x []byte) error { func LocationIndexFromJSON(x []byte) (*LocationIndex, error) { var result *LocationIndex = new(LocationIndex) // if is Struct - err := shared.JsonParseObject(x, func(key string, value []byte) error { + err := shared.JSONParseObject(x, func(key string, value []byte) error { switch key { case "Index": return json.Unmarshal(value, &result.Index) @@ -423,7 +425,6 @@ func LocationIndexToJSON(x *LocationIndex) ([]byte, error) { "Index": field_Index, }) } - func (self *LocationIndex) MarshalJSON() ([]byte, error) { return LocationIndexToJSON(self) } @@ -440,7 +441,7 @@ func (self *LocationIndex) UnmarshalJSON(x []byte) error { func LocationAnythingFromJSON(x []byte) (*LocationAnything, error) { var result *LocationAnything = new(LocationAnything) // if is Struct - err := shared.JsonParseObject(x, func(key string, value []byte) error { + err := shared.JSONParseObject(x, func(key string, value []byte) error { switch key { } @@ -453,7 +454,6 @@ func LocationAnythingFromJSON(x []byte) (*LocationAnything, error) { func LocationAnythingToJSON(x *LocationAnything) ([]byte, error) { return json.Marshal(map[string]json.RawMessage{}) } - func (self *LocationAnything) MarshalJSON() ([]byte, error) { return LocationAnythingToJSON(self) } diff --git a/x/schema/model.go b/x/schema/model.go index 13d5fcf0..1319825b 100644 --- a/x/schema/model.go +++ b/x/schema/model.go @@ -1,6 +1,9 @@ package schema import ( + "encoding/json" + "fmt" + "github.com/widmogrod/mkunion/x/shared" "reflect" ) @@ -107,6 +110,42 @@ type Field struct { Value Schema } +func (f *Field) MarshalJSON() ([]byte, error) { + field_Name, err := json.Marshal(f.Name) + if err != nil { + return nil, fmt.Errorf("schema.Field.MarshalJSON: Name; %w", err) + } + + field_Value, err := SchemaToJSON(f.Value) + if err != nil { + return nil, fmt.Errorf("schema.Field.MarshalJSON: Value; %w", err) + } + + return json.Marshal(map[string]json.RawMessage{ + "Name": field_Name, + "Value": field_Value, + }) +} + +func (f *Field) UnmarshalJSON(bytes []byte) error { + return shared.JSONParseObject(bytes, func(key string, value []byte) error { + switch key { + case "Name": + return json.Unmarshal(value, &f.Name) + case "Value": + field, err := SchemaFromJSON(value) + if err != nil { + return fmt.Errorf("schema.Field.UnmarshalJSON: Value; %w", err) + } + f.Value = field + } + return nil + }) +} + +var _ json.Unmarshaler = (*Field)(nil) +var _ json.Marshaler = (*Field)(nil) + type UnionInformationRule interface { UnionType() reflect.Type VariantsTypes() []reflect.Type diff --git a/x/schema/model_schema_gen.go b/x/schema/model_schema_gen.go index bbcb5d11..7f77479b 100644 --- a/x/schema/model_schema_gen.go +++ b/x/schema/model_schema_gen.go @@ -490,13 +490,13 @@ func (t *SchemaDefaultVisitor[A]) VisitMap(v *Map) any { // mkunion-extension:json type SchemaUnionJSON struct { Type string `json:"$type,omitempty"` - None json.RawMessage `json:"github.com/widmogrod/mkunion/x/schema.None,omitempty"` - Bool json.RawMessage `json:"github.com/widmogrod/mkunion/x/schema.Bool,omitempty"` - Number json.RawMessage `json:"github.com/widmogrod/mkunion/x/schema.Number,omitempty"` - String json.RawMessage `json:"github.com/widmogrod/mkunion/x/schema.String,omitempty"` - Binary json.RawMessage `json:"github.com/widmogrod/mkunion/x/schema.Binary,omitempty"` - List json.RawMessage `json:"github.com/widmogrod/mkunion/x/schema.List,omitempty"` - Map json.RawMessage `json:"github.com/widmogrod/mkunion/x/schema.Map,omitempty"` + None json.RawMessage `json:"schema.None,omitempty"` + Bool json.RawMessage `json:"schema.Bool,omitempty"` + Number json.RawMessage `json:"schema.Number,omitempty"` + String json.RawMessage `json:"schema.String,omitempty"` + Binary json.RawMessage `json:"schema.Binary,omitempty"` + List json.RawMessage `json:"schema.List,omitempty"` + Map json.RawMessage `json:"schema.Map,omitempty"` } func SchemaFromJSON(x []byte) (Schema, error) { @@ -507,19 +507,19 @@ func SchemaFromJSON(x []byte) (Schema, error) { } switch data.Type { - case "github.com/widmogrod/mkunion/x/schema.None": + case "schema.None": return NoneFromJSON(data.None) - case "github.com/widmogrod/mkunion/x/schema.Bool": + case "schema.Bool": return BoolFromJSON(data.Bool) - case "github.com/widmogrod/mkunion/x/schema.Number": + case "schema.Number": return NumberFromJSON(data.Number) - case "github.com/widmogrod/mkunion/x/schema.String": + case "schema.String": return StringFromJSON(data.String) - case "github.com/widmogrod/mkunion/x/schema.Binary": + case "schema.Binary": return BinaryFromJSON(data.Binary) - case "github.com/widmogrod/mkunion/x/schema.List": + case "schema.List": return ListFromJSON(data.List) - case "github.com/widmogrod/mkunion/x/schema.Map": + case "schema.Map": return MapFromJSON(data.Map) } @@ -543,6 +543,9 @@ func SchemaFromJSON(x []byte) (Schema, error) { } func SchemaToJSON(x Schema) ([]byte, error) { + if x == nil { + return nil, nil + } return MustMatchSchemaR2( x, func(x *None) ([]byte, error) { @@ -552,7 +555,7 @@ func SchemaToJSON(x Schema) ([]byte, error) { } return json.Marshal(SchemaUnionJSON{ - Type: "github.com/widmogrod/mkunion/x/schema.None", + Type: "schema.None", None: body, }) }, @@ -563,7 +566,7 @@ func SchemaToJSON(x Schema) ([]byte, error) { } return json.Marshal(SchemaUnionJSON{ - Type: "github.com/widmogrod/mkunion/x/schema.Bool", + Type: "schema.Bool", Bool: body, }) }, @@ -574,7 +577,7 @@ func SchemaToJSON(x Schema) ([]byte, error) { } return json.Marshal(SchemaUnionJSON{ - Type: "github.com/widmogrod/mkunion/x/schema.Number", + Type: "schema.Number", Number: body, }) }, @@ -585,7 +588,7 @@ func SchemaToJSON(x Schema) ([]byte, error) { } return json.Marshal(SchemaUnionJSON{ - Type: "github.com/widmogrod/mkunion/x/schema.String", + Type: "schema.String", String: body, }) }, @@ -596,7 +599,7 @@ func SchemaToJSON(x Schema) ([]byte, error) { } return json.Marshal(SchemaUnionJSON{ - Type: "github.com/widmogrod/mkunion/x/schema.Binary", + Type: "schema.Binary", Binary: body, }) }, @@ -607,7 +610,7 @@ func SchemaToJSON(x Schema) ([]byte, error) { } return json.Marshal(SchemaUnionJSON{ - Type: "github.com/widmogrod/mkunion/x/schema.List", + Type: "schema.List", List: body, }) }, @@ -618,7 +621,7 @@ func SchemaToJSON(x Schema) ([]byte, error) { } return json.Marshal(SchemaUnionJSON{ - Type: "github.com/widmogrod/mkunion/x/schema.Map", + Type: "schema.Map", Map: body, }) }, @@ -628,7 +631,7 @@ func SchemaToJSON(x Schema) ([]byte, error) { func NoneFromJSON(x []byte) (*None, error) { var result *None = new(None) // if is Struct - err := shared.JsonParseObject(x, func(key string, value []byte) error { + err := shared.JSONParseObject(x, func(key string, value []byte) error { switch key { } @@ -641,7 +644,6 @@ func NoneFromJSON(x []byte) (*None, error) { func NoneToJSON(x *None) ([]byte, error) { return json.Marshal(map[string]json.RawMessage{}) } - func (self *None) MarshalJSON() ([]byte, error) { return NoneToJSON(self) } @@ -666,19 +668,6 @@ func BoolToJSON(x *Bool) ([]byte, error) { return json.Marshal(x) } -func (self *Bool) MarshalJSON() ([]byte, error) { - return BoolToJSON(self) -} - -func (self *Bool) UnmarshalJSON(x []byte) error { - n, err := BoolFromJSON(x) - if err != nil { - return err - } - *self = *n - return nil -} - func NumberFromJSON(x []byte) (*Number, error) { var result *Number = new(Number) err := json.Unmarshal(x, result) @@ -690,19 +679,6 @@ func NumberToJSON(x *Number) ([]byte, error) { return json.Marshal(x) } -func (self *Number) MarshalJSON() ([]byte, error) { - return NumberToJSON(self) -} - -func (self *Number) UnmarshalJSON(x []byte) error { - n, err := NumberFromJSON(x) - if err != nil { - return err - } - *self = *n - return nil -} - func StringFromJSON(x []byte) (*String, error) { var result *String = new(String) err := json.Unmarshal(x, result) @@ -714,23 +690,10 @@ func StringToJSON(x *String) ([]byte, error) { return json.Marshal(x) } -func (self *String) MarshalJSON() ([]byte, error) { - return StringToJSON(self) -} - -func (self *String) UnmarshalJSON(x []byte) error { - n, err := StringFromJSON(x) - if err != nil { - return err - } - *self = *n - return nil -} - func BinaryFromJSON(x []byte) (*Binary, error) { var result *Binary = new(Binary) // if is Struct - err := shared.JsonParseObject(x, func(key string, value []byte) error { + err := shared.JSONParseObject(x, func(key string, value []byte) error { switch key { case "B": return json.Unmarshal(value, &result.B) @@ -751,7 +714,6 @@ func BinaryToJSON(x *Binary) ([]byte, error) { "B": field_B, }) } - func (self *Binary) MarshalJSON() ([]byte, error) { return BinaryToJSON(self) } @@ -768,10 +730,15 @@ func (self *Binary) UnmarshalJSON(x []byte) error { func ListFromJSON(x []byte) (*List, error) { var result *List = new(List) // if is Struct - err := shared.JsonParseObject(x, func(key string, value []byte) error { + err := shared.JSONParseObject(x, func(key string, value []byte) error { switch key { case "Items": - return json.Unmarshal(value, &result.Items) + res, err := shared.JSONToListWithDeserializer(value, result.Items, SchemaFromJSON) + if err != nil { + return fmt.Errorf("schema._FromJSON: field Schema %w", err) + } + result.Items = res + return nil } return fmt.Errorf("schema.ListFromJSON: unknown key %s", key) @@ -781,7 +748,7 @@ func ListFromJSON(x []byte) (*List, error) { } func ListToJSON(x *List) ([]byte, error) { - field_Items, err := json.Marshal(x.Items) + field_Items, err := shared.JSONListFromSerializer(x.Items, SchemaToJSON) if err != nil { return nil, err } @@ -789,7 +756,6 @@ func ListToJSON(x *List) ([]byte, error) { "Items": field_Items, }) } - func (self *List) MarshalJSON() ([]byte, error) { return ListToJSON(self) } @@ -806,7 +772,7 @@ func (self *List) UnmarshalJSON(x []byte) error { func MapFromJSON(x []byte) (*Map, error) { var result *Map = new(Map) // if is Struct - err := shared.JsonParseObject(x, func(key string, value []byte) error { + err := shared.JSONParseObject(x, func(key string, value []byte) error { switch key { case "Field": return json.Unmarshal(value, &result.Field) @@ -827,7 +793,6 @@ func MapToJSON(x *Map) ([]byte, error) { "Field": field_Field, }) } - func (self *Map) MarshalJSON() ([]byte, error) { return MapToJSON(self) } diff --git a/x/shape/shape_guard_gen.go b/x/shape/shape_guard_gen.go index eb6a6bc0..af744e6f 100644 --- a/x/shape/shape_guard_gen.go +++ b/x/shape/shape_guard_gen.go @@ -234,7 +234,7 @@ func EnumFromJSON(x []byte) (*Enum, error) { var result *Enum = &Enum{} // if is Struct - err := shared.JsonParseObject(x, func(key string, value []byte) error { + err := shared.JSONParseObject(x, func(key string, value []byte) error { switch key { case "Val": return json.Unmarshal(value, &result.Val) @@ -275,7 +275,7 @@ func RequiredFromJSON(x []byte) (*Required, error) { var result *Required = &Required{} // if is Struct - err := shared.JsonParseObject(x, func(key string, value []byte) error { + err := shared.JSONParseObject(x, func(key string, value []byte) error { switch key { } @@ -306,7 +306,7 @@ func AndGuardFromJSON(x []byte) (*AndGuard, error) { var result *AndGuard = &AndGuard{} // if is Struct - err := shared.JsonParseObject(x, func(key string, value []byte) error { + err := shared.JSONParseObject(x, func(key string, value []byte) error { switch key { case "L": return json.Unmarshal(value, &result.L) diff --git a/x/shape/shape_shape_gen.go b/x/shape/shape_shape_gen.go index 6f5b3d36..c106bfbc 100644 --- a/x/shape/shape_shape_gen.go +++ b/x/shape/shape_shape_gen.go @@ -538,7 +538,7 @@ func AnyFromJSON(x []byte) (*Any, error) { var result *Any = &Any{} // if is Struct - err := shared.JsonParseObject(x, func(key string, value []byte) error { + err := shared.JSONParseObject(x, func(key string, value []byte) error { switch key { } @@ -569,7 +569,7 @@ func RefNameFromJSON(x []byte) (*RefName, error) { var result *RefName = &RefName{} // if is Struct - err := shared.JsonParseObject(x, func(key string, value []byte) error { + err := shared.JSONParseObject(x, func(key string, value []byte) error { switch key { case "Name": return json.Unmarshal(value, &result.Name) @@ -628,7 +628,7 @@ func BooleanLikeFromJSON(x []byte) (*BooleanLike, error) { var result *BooleanLike = &BooleanLike{} // if is Struct - err := shared.JsonParseObject(x, func(key string, value []byte) error { + err := shared.JSONParseObject(x, func(key string, value []byte) error { switch key { } @@ -659,7 +659,7 @@ func StringLikeFromJSON(x []byte) (*StringLike, error) { var result *StringLike = &StringLike{} // if is Struct - err := shared.JsonParseObject(x, func(key string, value []byte) error { + err := shared.JSONParseObject(x, func(key string, value []byte) error { switch key { } @@ -690,7 +690,7 @@ func NumberLikeFromJSON(x []byte) (*NumberLike, error) { var result *NumberLike = &NumberLike{} // if is Struct - err := shared.JsonParseObject(x, func(key string, value []byte) error { + err := shared.JSONParseObject(x, func(key string, value []byte) error { switch key { } @@ -721,7 +721,7 @@ func ListLikeFromJSON(x []byte) (*ListLike, error) { var result *ListLike = &ListLike{} // if is Struct - err := shared.JsonParseObject(x, func(key string, value []byte) error { + err := shared.JSONParseObject(x, func(key string, value []byte) error { switch key { case "Element": res, err := ShapeFromJSON(value) @@ -776,7 +776,7 @@ func MapLikeFromJSON(x []byte) (*MapLike, error) { var result *MapLike = &MapLike{} // if is Struct - err := shared.JsonParseObject(x, func(key string, value []byte) error { + err := shared.JSONParseObject(x, func(key string, value []byte) error { switch key { case "Key": res, err := ShapeFromJSON(value) @@ -854,7 +854,7 @@ func StructLikeFromJSON(x []byte) (*StructLike, error) { var result *StructLike = &StructLike{} // if is Struct - err := shared.JsonParseObject(x, func(key string, value []byte) error { + err := shared.JSONParseObject(x, func(key string, value []byte) error { switch key { case "Name": return json.Unmarshal(value, &result.Name) @@ -922,7 +922,7 @@ func UnionLikeFromJSON(x []byte) (*UnionLike, error) { var result *UnionLike = &UnionLike{} // if is Struct - err := shared.JsonParseObject(x, func(key string, value []byte) error { + err := shared.JSONParseObject(x, func(key string, value []byte) error { switch key { case "Name": return json.Unmarshal(value, &result.Name) diff --git a/x/shape/testasset/type_to_json_test.go b/x/shape/testasset/type_to_json_test.go index b658c0da..06e19864 100644 --- a/x/shape/testasset/type_to_json_test.go +++ b/x/shape/testasset/type_to_json_test.go @@ -11,7 +11,7 @@ func TestExampleToJSON_A(t *testing.T) { Name: "not-angel", }) assert.NoError(t, err) - assert.JSONEq(t, `{"$type":"github.com/widmogrod/mkunion/x/shape/testasset.A", "github.com/widmogrod/mkunion/x/shape/testasset.A": {"name":"not-angel"}}`, string(result)) + assert.JSONEq(t, `{"$type":"testasset.A", "testasset.A": {"name":"not-angel"}}`, string(result)) example, err := ExampleFromJSON(result) assert.NoError(t, err) @@ -33,8 +33,8 @@ func TestExampleToJSON_B(t *testing.T) { }) assert.NoError(t, err) assert.JSONEq(t, `{ - "$type":"github.com/widmogrod/mkunion/x/shape/testasset.B", - "github.com/widmogrod/mkunion/x/shape/testasset.B": { + "$type":"testasset.B", + "testasset.B": { "age":123, "A": { "name":"not-angel" @@ -69,11 +69,11 @@ func TestOtherToJSON_A(t *testing.T) { }) assert.NoError(t, err) assert.JSONEq(t, `{ - "$type": "github.com/widmogrod/mkunion/x/shape/testasset.Explain", - "github.com/widmogrod/mkunion/x/shape/testasset.Explain": { + "$type": "testasset.Explain", + "testasset.Explain": { "example": { - "$type":"github.com/widmogrod/mkunion/x/shape/testasset.B", - "github.com/widmogrod/mkunion/x/shape/testasset.B": { + "$type":"testasset.B", + "testasset.B": { "age":123, "A": { "name":"not-angel" diff --git a/x/shape/totypescript.go b/x/shape/totypescript.go index 27f67c50..e7252be5 100644 --- a/x/shape/totypescript.go +++ b/x/shape/totypescript.go @@ -9,41 +9,15 @@ import ( type ( TypeScriptOptions struct { - currentPkgName string - imports map[packageName]packageImportName + currentPkgName string + currentPkgImportName packageImportName + imports map[packageName]packageImportName } packageImportName = string packageName = string ) -var helperTSFunctions = `/** -* This function is used to remove $type field from {name}, so it can be understood by mkunion, -* that is assuming unions have one field in object, that is used to discriminate between variants. -*/ -export function dediscriminate{name}(x: {name}): {name} { - if (x["$type"] !== undefined) { - delete x["$type"] - return x - } - return x -} - -/** -* This function is used to populate $type field in {name}, so it can be used as discriminative switch statement -* @example https://www.typescriptlang.org/play#example/discriminate-types -*/ -export function discriminate{name}(x: {name}): {name} { - if (x["$type"] === undefined) { - let keyx = Object.keys(x) - if (keyx.length === 1) { - x["$type"] = keyx[0] as any - } - } - return x -} -` - func (o *TypeScriptOptions) IsCurrentPkgName(pkgName string) bool { return o.currentPkgName == pkgName } @@ -99,11 +73,6 @@ func ToTypeScript(x Shape, option *TypeScriptOptions) string { }, func(x *UnionLike) string { result := &strings.Builder{} - // build helper functions for typescript - result.WriteString("\n") - _, _ = fmt.Fprintf(result, strings.ReplaceAll(helperTSFunctions, "{name}", x.Name)) - result.WriteString("\n") - // build union type in typescript _, _ = fmt.Fprintf(result, "export type %s = ", x.Name) result.WriteString(toTypeTypeScriptTypeName(x, option)) @@ -133,25 +102,27 @@ type TypeScriptRenderer struct { } func (r *TypeScriptRenderer) AddUnion(x *UnionLike) { - if _, ok := r.contents[x.PkgName]; !ok { - r.contents[x.PkgName] = &strings.Builder{} + key := x.PkgImportName + if _, ok := r.contents[key]; !ok { + r.contents[key] = &strings.Builder{} } - if _, ok := r.imports[x.PkgName]; !ok { - r.imports[x.PkgName] = &TypeScriptOptions{ - currentPkgName: x.PkgName, - imports: make(map[packageName]packageImportName), + if _, ok := r.imports[key]; !ok { + r.imports[key] = &TypeScriptOptions{ + currentPkgName: x.PkgName, + currentPkgImportName: x.PkgImportName, + imports: make(map[packageName]packageImportName), } } - res := ToTypeScript(x, r.imports[x.PkgName]) - r.contents[x.PkgName].WriteString("//generated by mkunion\n") - r.contents[x.PkgName].WriteString(res) + res := ToTypeScript(x, r.imports[key]) + r.contents[key].WriteString("//generated by mkunion\n") + r.contents[key].WriteString(res) } func (r *TypeScriptRenderer) WriteToDir(dir string) error { - for pkgName, content := range r.contents { - imports := r.imports[pkgName] + for pkgImportName, content := range r.contents { + imports := r.imports[pkgImportName] if imports == nil { continue } @@ -170,7 +141,7 @@ func (r *TypeScriptRenderer) WriteToDir(dir string) error { return fmt.Errorf("totypescript: WriteToDir failed to write imports: %w", err) } - err = r.writeToFile(dir, pkgName, content.String()) + err = r.writeToFile(dir, r.normaliseImport(pkgImportName), content.String()) if err != nil { return fmt.Errorf("totypescript: WriteToDir failed to write file %s: %w", dir, err) } @@ -195,19 +166,21 @@ func (r *TypeScriptRenderer) writeToFile(dir string, name packageName, content s } func (r *TypeScriptRenderer) AddStruct(like *StructLike) { - if _, ok := r.contents[like.PkgName]; !ok { - r.contents[like.PkgName] = &strings.Builder{} + key := like.PkgImportName + if _, ok := r.contents[key]; !ok { + r.contents[key] = &strings.Builder{} } - if _, ok := r.imports[like.PkgName]; !ok { - r.imports[like.PkgName] = &TypeScriptOptions{ - currentPkgName: like.PkgName, - imports: make(map[packageName]packageImportName), + if _, ok := r.imports[key]; !ok { + r.imports[key] = &TypeScriptOptions{ + currentPkgName: like.PkgName, + currentPkgImportName: like.PkgImportName, + imports: make(map[packageName]packageImportName), } } - res := ToTypeScript(like, r.imports[like.PkgName]) - r.contents[like.PkgName].WriteString(res) + res := ToTypeScript(like, r.imports[key]) + r.contents[key].WriteString(res) } func (r *TypeScriptRenderer) normaliseImport(imp packageImportName) string { @@ -227,18 +200,86 @@ func toTypeTypeScriptTypeName(variant Shape, option *TypeScriptOptions) string { return x.Name }, func(x *BooleanLike) string { + if IsNamed(x) { + typeName := "boolean" + typeNameFul := fmt.Sprintf("%s.%s", x.Named.PkgName, x.Named.Name) + + result := &strings.Builder{} + result.WriteString("{\n") + _, _ = fmt.Fprintf(result, "\t"+`// $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema`+"\n") + _, _ = fmt.Fprintf(result, "\t"+`"$type"?: "%s",`+"\n", typeNameFul) + _, _ = fmt.Fprintf(result, "\t"+`"%s": %s`, typeNameFul, typeName) + result.WriteString("\n}") + + return result.String() + } return "boolean" }, func(x *StringLike) string { + if IsNamed(x) { + typeName := "string" + typeNameFul := fmt.Sprintf("%s.%s", x.Named.PkgName, x.Named.Name) + + result := &strings.Builder{} + result.WriteString("{\n") + _, _ = fmt.Fprintf(result, "\t"+`// $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema`+"\n") + _, _ = fmt.Fprintf(result, "\t"+`"$type"?: "%s",`+"\n", typeNameFul) + _, _ = fmt.Fprintf(result, "\t"+`"%s": %s`, typeNameFul, typeName) + result.WriteString("\n}") + + return result.String() + } + return "string" }, func(x *NumberLike) string { + if IsNamed(x) { + typeName := "number" + typeNameFul := fmt.Sprintf("%s.%s", x.Named.PkgName, x.Named.Name) + + result := &strings.Builder{} + result.WriteString("{\n") + _, _ = fmt.Fprintf(result, "\t"+`// $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema`+"\n") + _, _ = fmt.Fprintf(result, "\t"+`"$type"?: "%s",`+"\n", typeNameFul) + _, _ = fmt.Fprintf(result, "\t"+`"%s": %s`, typeNameFul, typeName) + result.WriteString("\n}") + + return result.String() + } return "number" }, func(x *ListLike) string { + if IsNamed(x) { + typeName := fmt.Sprintf("%s[]", ToTypeScript(x.Element, option)) + typeNameFul := fmt.Sprintf("%s.%s", x.Named.PkgName, x.Named.Name) + + result := &strings.Builder{} + result.WriteString("{\n") + _, _ = fmt.Fprintf(result, "\t"+`// $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema`+"\n") + _, _ = fmt.Fprintf(result, "\t"+`"$type"?: "%s",`+"\n", typeNameFul) + _, _ = fmt.Fprintf(result, "\t"+`"%s": %s`, typeNameFul, typeName) + result.WriteString("\n}") + + return result.String() + } + return fmt.Sprintf("%s[]", ToTypeScript(x.Element, option)) }, func(x *MapLike) string { + if IsNamed(x) { + typeName := fmt.Sprintf("{[key: %s]: %s}", ToTypeScript(x.Key, option), ToTypeScript(x.Val, option)) + typeNameFul := fmt.Sprintf("%s.%s", x.Named.PkgName, x.Named.Name) + + result := &strings.Builder{} + result.WriteString("{\n") + _, _ = fmt.Fprintf(result, "\t"+`// $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema`+"\n") + _, _ = fmt.Fprintf(result, "\t"+`"$type"?: "%s",`+"\n", typeNameFul) + _, _ = fmt.Fprintf(result, "\t"+`"%s": %s`, typeNameFul, typeName) + result.WriteString("\n}") + + return result.String() + } + return fmt.Sprintf("{[key: %s]: %s}", ToTypeScript(x.Key, option), ToTypeScript(x.Val, option)) }, func(x *StructLike) string { diff --git a/x/shape/totypescript_test.go b/x/shape/totypescript_test.go index a440d2b3..4dee560f 100644 --- a/x/shape/totypescript_test.go +++ b/x/shape/totypescript_test.go @@ -12,7 +12,7 @@ func TestTypeScriptSchemaGeneration(t *testing.T) { tsr.AddUnion(&UnionLike{ Name: "Tree", PkgName: "test", - PkgImportName: "test", + PkgImportName: "go.import.test", Variant: []Shape{ &StructLike{ Name: "Branch", @@ -65,39 +65,12 @@ func TestTypeScriptSchemaGeneration(t *testing.T) { err := tsr.WriteToDir("_test/") assert.NoError(t, err) - assert.FileExists(t, "_test/test.ts") + assert.FileExists(t, "_test/go_import_test.ts") - contents, err := os.ReadFile("_test/test.ts") + contents, err := os.ReadFile("_test/go_import_test.ts") assert.NoError(t, err) expected := `//generated by mkunion - -/** -* This function is used to remove $type field from Tree, so it can be understood by mkunion, -* that is assuming unions have one field in object, that is used to discriminate between variants. -*/ -export function dediscriminateTree(x: Tree): Tree { - if (x["$type"] !== undefined) { - delete x["$type"] - return x - } - return x -} - -/** -* This function is used to populate $type field in Tree, so it can be used as discriminative switch statement -* @example https://www.typescriptlang.org/play#example/discriminate-types -*/ -export function discriminateTree(x: Tree): Tree { - if (x["$type"] === undefined) { - let keyx = Object.keys(x) - if (keyx.length === 1) { - x["$type"] = keyx[0] as any - } - } - return x -} - export type Tree = { // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema "$type"?: "test.Branch", diff --git a/x/shared/jsonparseobject.go b/x/shared/jsonparseobject.go index f17f021e..4152f7dd 100644 --- a/x/shared/jsonparseobject.go +++ b/x/shared/jsonparseobject.go @@ -1,19 +1,97 @@ package shared -import "encoding/json" +import ( + "encoding/json" + "fmt" +) -func JsonParseObject(x []byte, f func(key string, value []byte) error) error { - var data map[string]json.RawMessage - err := json.Unmarshal(x, &data) +func JSONParseObject(x []byte, onElement func(key string, value []byte) error) error { + var jsonMap map[string]json.RawMessage + err := json.Unmarshal(x, &jsonMap) if err != nil { return err } - for key, value := range data { - if err := f(key, value); err != nil { - return err + for key, value := range jsonMap { + if err := onElement(key, value); err != nil { + return fmt.Errorf("shared.JSONParseObject: onElement() for key=%s; %w", key, err) } } return nil } + +func JSONToListWithDeserializer[A any](x []byte, _ []A, deserialize func(value []byte) (A, error)) ([]A, error) { + var jsonList []json.RawMessage + err := json.Unmarshal(x, &jsonList) + if err != nil { + return nil, fmt.Errorf("shared.JSONToListWithDeserializer: %w", err) + } + + var result []A + for idx, value := range jsonList { + out, err := deserialize(value) + if err != nil { + return nil, fmt.Errorf("shared.JSONToListWithDeserializer: deserialize() for index=%d; %w", idx, err) + } + + result = append(result, out) + } + + return result, nil +} + +func JSONToMapWithDeserializer[K comparable, A any]( + x []byte, + _ map[K]A, + deserialize func(value []byte) (A, error), +) (map[K]A, error) { + var jsonMap map[K]json.RawMessage + err := json.Unmarshal(x, &jsonMap) + if err != nil { + return nil, fmt.Errorf("shared.JSONToMapWithDeserializer: %w", err) + } + + var result = make(map[K]A) + for key, value := range jsonMap { + out, err := deserialize(value) + if err != nil { + return nil, fmt.Errorf("shared.JSONToMapWithDeserializer: deserialize() for key=%v; %w", key, err) + } + + result[key] = out + } + + return result, nil +} + +func JSONListFromSerializer[A any](x []A, serialize func(x A) ([]byte, error)) ([]byte, error) { + var result []json.RawMessage + for _, value := range x { + out, err := serialize(value) + if err != nil { + return nil, fmt.Errorf("shared.JSONListFromSerializer: serialize() for value=%v; %w", value, err) + } + + result = append(result, out) + } + + return json.Marshal(result) +} + +func JSONMapFromSerializer[K comparable, A any]( + x map[K]A, + serialize func(x A) ([]byte, error), +) ([]byte, error) { + var result = make(map[K][]byte) + for key, value := range x { + out, err := serialize(value) + if err != nil { + return nil, fmt.Errorf("shared.JSONMapFromSerializer: serialize() for key=%v; %w", key, err) + } + + result[key] = out + } + + return json.Marshal(result) +} diff --git a/x/workflow/workflow_deser_test.go b/x/workflow/workflow_deser_test.go new file mode 100644 index 00000000..158428a9 --- /dev/null +++ b/x/workflow/workflow_deser_test.go @@ -0,0 +1,238 @@ +package workflow + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestUnmarshal(t *testing.T) { + val := `{ + "$type": "workflow.Flow", + "workflow.Flow": { + "Name": "hello_world", + "Arg": "input", + "Body": [ + { + "$type": "workflow.Choose", + "workflow.Choose": { + "ID": "choose1", + "If": { + "$type": "workflow.Compare", + "workflow.Compare": { + "Operation": "=", + "Left": { + "$type": "workflow.GetValue", + "workflow.GetValue": { + "Path": "input" + } + }, + "Right": { + "$type": "workflow.SetValue", + "workflow.SetValue": { + "Value": { + "$type": "schema.String", + "schema.String": "666" + } + } + } + } + }, + "Then": [ + { + "$type": "workflow.End", + "workflow.End": { + "ID": "end2", + "Result": { + "$type": "workflow.SetValue", + "workflow.SetValue": { + "Value": { + "$type": "schema.String", + "schema.String": "Do no evil" + } + } + } + } + } + ], + "Else": null + } + }, + { + "$type": "workflow.Assign", + "workflow.Assign": { + "ID": "assign1", + "VarOk": "res", + "VarErr": "", + "Val": { + "$type": "workflow.Apply", + "workflow.Apply": { + "ID": "apply1", + "Name": "concat", + "Args": [ + { + "$type": "workflow.SetValue", + "workflow.SetValue": { + "Value": { + "$type": "schema.String", + "schema.String": "hello " + } + } + }, + { + "$type": "workflow.GetValue", + "workflow.GetValue": { + "Path": "input" + } + } + ], + "Await": null + } + } + } + }, + { + "$type": "workflow.End", + "workflow.End": { + "ID": "end1", + "Result": { + "$type": "workflow.GetValue", + "workflow.GetValue": { + "Path": "res" + } + } + } + } + ] + } +}` + + res, err := WorflowFromJSON([]byte(val)) + assert.NoError(t, err) + + out, err := WorflowToJSON(res) + assert.NoError(t, err) + + t.Log(string(out)) + + assert.JSONEq(t, val, string(out)) +} + +func TestSecond(t *testing.T) { + val := `{ + "$type": "workflow.Run", + "workflow.Run": { + "Flow": { + "$type": "workflow.Flow", + "workflow.Flow": { + "Name": "generateandresizeimage", + "Arg": "input", + "Body": [ + { + "$type": "workflow.Assign", + "workflow.Assign": { + "ID": "assign1", + "VarOk": "res", + "VarErr": "", + "Val": { + "$type": "workflow.Apply", + "workflow.Apply": { + "ID": "apply1", + "Name": "genimageb64", + "Args": [ + { + "$type": "workflow.GetValue", + "workflow.GetValue": { + "Path": "input.prompt" + } + } + ] + } + } + } + }, + { + "$type": "workflow.Assign", + "workflow.Assign": { + "ID": "assign2", + "VarOk": "res_small", + "VarErr": "", + "Val": { + "$type": "workflow.Apply", + "workflow.Apply": { + "ID": "apply2", + "Name": "resizeimgb64", + "Args": [ + { + "$type": "workflow.GetValue", + "workflow.GetValue": { + "Path": "res" + } + }, + { + "$type": "workflow.GetValue", + "workflow.GetValue": { + "Path": "input.width" + } + }, + { + "$type": "workflow.GetValue", + "workflow.GetValue": { + "Path": "input.height" + } + } + ] + } + } + } + }, + { + "$type": "workflow.End", + "workflow.End": { + "ID": "end1", + "Result": { + "$type": "workflow.GetValue", + "workflow.GetValue": { + "Path": "res_small" + } + } + } + } + ] + } + }, + "Input": { + "schema.Map": { + "Field": [ + { + "Name": "prompt", + "Value": { + "schema.String": "no text" + } + }, + { + "Name": "width", + "Value": { + "schema.Number": 100 + } + }, + { + "Name": "height", + "Value": { + "schema.Number": 100 + } + } + ] + } + } + } +}` + + res, err := CommandFromJSON([]byte(val)) + assert.NoError(t, err) + + out, err := CommandToJSON(res) + assert.NoError(t, err) + + t.Log(string(out)) + +} diff --git a/x/workflow/workflow_other.go b/x/workflow/workflow_other.go index 61059d1f..64d119af 100644 --- a/x/workflow/workflow_other.go +++ b/x/workflow/workflow_other.go @@ -4,9 +4,10 @@ import ( "github.com/widmogrod/mkunion/x/schema" ) -type ( - Function func(args *FunctionInput) (*FunctionOutput, error) +type Function func(args *FunctionInput) (*FunctionOutput, error) +//go:generate go run ../../cmd/mkunion/main.go -name=FunctionDSL +type ( //FunctionDef struct { // Name string // Input schema.ShapeDef From b36e5903d6fa8e9d14d1384c91df21306b34bd2c Mon Sep 17 00:00:00 2001 From: widmogrod Date: Tue, 12 Dec 2023 01:48:17 +0100 Subject: [PATCH 02/14] x/schema Map now is map[string]Schema --- x/schema/dynamo_db.go | 64 ++++----- x/schema/go.go | 33 ++--- x/schema/go_test.go | 128 ++++------------- x/schema/json.go | 9 +- x/schema/model.go | 25 +++- x/schema/model_rule_union.go | 12 +- x/schema/model_rule_when_field.go | 4 +- x/schema/model_rule_wrap_in_field.go | 7 +- x/schema/model_schema_gen.go | 30 +--- x/schema/model_self_deser.go | 20 ++- x/schema/schema_test.go | 203 +++++++-------------------- x/schema/utils.go | 26 ++-- x/workflow/workflow_deser_test.go | 23 +-- 13 files changed, 189 insertions(+), 395 deletions(-) diff --git a/x/schema/dynamo_db.go b/x/schema/dynamo_db.go index 204399af..b8d1e6f6 100644 --- a/x/schema/dynamo_db.go +++ b/x/schema/dynamo_db.go @@ -47,8 +47,8 @@ func ToDynamoDB(x Schema) types.AttributeValue { result := &types.AttributeValueMemberM{ Value: map[string]types.AttributeValue{}, } - for _, item := range x.Field { - result.Value[item.Name] = ToDynamoDB(item.Value) + for key, value := range *x { + result.Value[key] = ToDynamoDB(value) } return result }, @@ -123,22 +123,17 @@ func FromDynamoDB(x types.AttributeValue) (Schema, error) { return result, nil case *types.AttributeValueMemberM: - result := &Map{ - Field: []Field{}, - } + result := make(Map) for name, item := range y.Value { v, err := FromDynamoDB(item) if err != nil { return nil, err } - result.Field = append(result.Field, Field{ - Name: name, - Value: v, - }) + result[name] = v } - return result, nil + return &result, nil } panic("unreachable") @@ -147,16 +142,16 @@ func FromDynamoDB(x types.AttributeValue) (Schema, error) { func UnwrapDynamoDB(data Schema) (Schema, error) { switch x := data.(type) { case *Map: - if len(x.Field) == 1 { - for _, field := range x.Field { - switch field.Name { + if len(*x) == 1 { + for name, value := range *x { + switch name { case "S": - value := AsDefault[string](field.Value, "") + value := AsDefault[string](value, "") return FromDynamoDB(&types.AttributeValueMemberS{ Value: value, }) case "SS": - switch y := field.Value.(type) { + switch y := value.(type) { case *List: result := &List{} for _, item := range y.Items { @@ -164,15 +159,15 @@ func UnwrapDynamoDB(data Schema) (Schema, error) { } return result, nil default: - return nil, fmt.Errorf("schema.UnwrapDynamoDB: unknown type (2): %s=%T", field.Name, field.Value) + return nil, fmt.Errorf("schema.UnwrapDynamoDB: unknown type (2): %s=%T", name, value) } case "N": - value := AsDefault[string](field.Value, "") + value := AsDefault[string](value, "") return FromDynamoDB(&types.AttributeValueMemberN{ Value: value, }) case "NS": - switch y := field.Value.(type) { + switch y := value.(type) { case *List: result := &List{} for _, item := range y.Items { @@ -180,7 +175,7 @@ func UnwrapDynamoDB(data Schema) (Schema, error) { } return result, nil default: - return nil, fmt.Errorf("schema.UnwrapDynamoDB: unknown type (2): %s=%T", field.Name, field.Value) + return nil, fmt.Errorf("schema.UnwrapDynamoDB: unknown type (2): %s=%T", name, value) } case "B": // Assumption is that here we have base64 encoded string from DynamoDB @@ -188,10 +183,10 @@ func UnwrapDynamoDB(data Schema) (Schema, error) { // pas it as is. This assumption, makse only sence, when it's used on values that // require unwrapping DynamoDB format. //Which may imply, that those values are ie from other medium than DynamoDB. - return field.Value, nil + return value, nil case "BS": - switch y := field.Value.(type) { + switch y := value.(type) { case *List: result := &List{} for _, item := range y.Items { @@ -199,11 +194,11 @@ func UnwrapDynamoDB(data Schema) (Schema, error) { } return result, nil default: - return nil, fmt.Errorf("schema.UnwrapDynamoDB: unknown type (2): %s=%T", field.Name, field.Value) + return nil, fmt.Errorf("schema.UnwrapDynamoDB: unknown type (2): %s=%T", name, value) } case "BOOL": - value := AsDefault[bool](field.Value, false) + value := AsDefault[bool](value, false) return FromDynamoDB(&types.AttributeValueMemberBOOL{ Value: value, }) @@ -211,15 +206,15 @@ func UnwrapDynamoDB(data Schema) (Schema, error) { return &None{}, nil case "M": - switch y := field.Value.(type) { + switch y := value.(type) { case *Map: return assumeMap(y) default: - return nil, fmt.Errorf("schema.UnwrapDynamoDB: unknown type (1): %s=%T", field.Name, field.Value) + return nil, fmt.Errorf("schema.UnwrapDynamoDB: unknown type (1): %s=%T", name, value) } case "L": - switch y := field.Value.(type) { + switch y := value.(type) { case *List: result := &List{} for _, item := range y.Items { @@ -231,11 +226,11 @@ func UnwrapDynamoDB(data Schema) (Schema, error) { } return result, nil default: - return nil, fmt.Errorf("schema.UnwrapDynamoDB: unknown type (2): %s=%T", field.Name, field.Value) + return nil, fmt.Errorf("schema.UnwrapDynamoDB: unknown type (2): %s=%T", name, value) } default: - return nil, fmt.Errorf("schema.UnwrapDynamoDB: unknown type (3): %s=%T", field.Name, field.Value) + return nil, fmt.Errorf("schema.UnwrapDynamoDB: unknown type (3): %s=%T", name, value) } } } else { @@ -247,16 +242,13 @@ func UnwrapDynamoDB(data Schema) (Schema, error) { } func assumeMap(x *Map) (Schema, error) { - result := &Map{} - for _, field := range x.Field { - value, err := UnwrapDynamoDB(field.Value) + result := make(Map) + for key, value := range *x { + value, err := UnwrapDynamoDB(value) if err != nil { return nil, err } - result.Field = append(result.Field, Field{ - Name: field.Name, - Value: value, - }) + result[key] = value } - return result, nil + return &result, nil } diff --git a/x/schema/go.go b/x/schema/go.go index 3625888b..eaac5d9a 100644 --- a/x/schema/go.go +++ b/x/schema/go.go @@ -379,14 +379,11 @@ func goToSchema(x any, c *goConfig) Schema { return r case map[string]any: - var r = &Map{} + var r = make(Map) for k, v := range y { - r.Field = append(r.Field, Field{ - Name: k, - Value: goToSchema(v, c), - }) + r[k] = goToSchema(v, c) } - return r + return &r case reflect.Value: return goToSchema(y.Interface(), c) @@ -406,18 +403,15 @@ func goToSchema(x any, c *goConfig) Schema { } if v.Kind() == reflect.Map { - var r = &Map{} + var r = make(Map) for _, k := range v.MapKeys() { - r.Field = append(r.Field, Field{ - Name: k.String(), - Value: goToSchema(v.MapIndex(k), c), - }) + r[k.String()] = goToSchema(v.MapIndex(k), c) } - return r + return &r } if v.Kind() == reflect.Struct { - var r = &Map{} + var r = make(Map) for i := 0; i < v.NumField(); i++ { if !v.Type().Field(i).IsExported() { continue @@ -428,13 +422,10 @@ func goToSchema(x any, c *goConfig) Schema { name = v.Type().Field(i).Name } - r.Field = append(r.Field, Field{ - Name: name, - Value: goToSchema(v.Field(i), c), - }) + r[name] = goToSchema(v.Field(i), c) } - return c.Transform(x, r) + return c.Transform(x, &r) } if v.Kind() == reflect.Slice { @@ -544,14 +535,14 @@ func schemaToGo(x Schema, c *goConfig, path []string) (any, error) { inject.WithWellDefinedTypesConversion(c.WellDefinedTypeToGo) } - for _, field := range x.Field { + for key, value := range *x { c.activeBuilder = build - value, err := schemaToGo(field.Value, c, append(path, field.Name)) + value, err := schemaToGo(value, c, append(path, key)) if err != nil { return nil, err } - err = build.Set(field.Name, value) + err = build.Set(key, value) if err != nil { return nil, fmt.Errorf("schema.schemaToGo: at path %s, at type %T, cause %w", strings.Join(path, "."), x, err) } diff --git a/x/schema/go_test.go b/x/schema/go_test.go index 7376e2bc..34a4a32e 100644 --- a/x/schema/go_test.go +++ b/x/schema/go_test.go @@ -12,15 +12,8 @@ func TestGoToSchema(t *testing.T) { Bar: 333, } expected := &Map{ - Field: []Field{ - { - Name: "Foo", - Value: MkInt(123), - }, { - Name: "Bar", - Value: MkInt(333), - }, - }, + "Foo": MkInt(123), + "Bar": MkInt(333), } schema := FromGo(data) @@ -64,98 +57,37 @@ func TestGoToSchemaComplex(t *testing.T) { }, } expected := &Map{ - Field: []Field{ - { - Name: "BStruct", - Value: &Map{ - Field: []Field{ - { - Name: "Foo", - Value: MkInt(123), - }, { - Name: "Bars", - Value: &List{ - Items: []Schema{ - MkString("bar"), - MkString("baz"), - }, - }, - }, { - Name: "Taz", - Value: &Map{ - Field: []Field{ - { - Name: "taz1", - Value: MkString("taz2"), - }, - }, - }, - }, { - Name: "BaseStruct", - Value: &Map{ - Field: []Field{ - { - Name: "Age", - Value: MkInt(123), - }, - }, - }, - }, { - Name: "S", - Value: MkString("some string"), - }, - { - Name: "List", - Value: &List{ - Items: []Schema{ - &Map{ - Field: []Field{ - { - Name: "Foo", - Value: MkInt(444), - }, - { - Name: "Bar", - Value: MkInt(0), - }, - }, - }, - }, - }, - }, - { - Name: "Ma", - Value: &Map{ - Field: []Field{ - { - Name: "key", - Value: &Map{ - Field: []Field{ - { - Name: "Foo", - Value: MkInt(666), - }, - { - Name: "Bar", - Value: MkInt(555), - }, - }, - }, - }, - }, - }, - }, - { - Name: "Bi", - Value: MkBinary([]byte("some bytes")), - }, - { - Name: "Bip", - Value: MkBinary([]byte{1, 2, 3, 4, 5}), - }, + "BStruct": &Map{ + "Foo": MkInt(123), + "Bars": &List{ + Items: []Schema{ + MkString("bar"), + MkString("baz"), + }, + }, + "Taz": &Map{ + "taz1": MkString("taz2"), + }, + "BaseStruct": &Map{ + "Age": MkInt(123), + }, + "S": MkString("some string"), + "List": &List{ + Items: []Schema{ + &Map{ + "Foo": MkInt(444), + "Bar": MkInt(0), }, }, }, + "Ma": &Map{ + "key": &Map{ + "Foo": MkInt(666), + "Bar": MkInt(555), + }, + }, + "Bi": MkBinary([]byte("some bytes")), + "Bip": MkBinary([]byte{1, 2, 3, 4, 5}), }, } schema := FromGo(data, WithOnlyTheseRules( diff --git a/x/schema/json.go b/x/schema/json.go index fe372a94..3d77fc21 100644 --- a/x/schema/json.go +++ b/x/schema/json.go @@ -83,15 +83,18 @@ func toJSON(schema Schema, res *bytes.Buffer) error { }, func(x *Map) error { res.WriteString("{") - for i, item := range x.Field { + var i int + for key, value := range *x { if i > 0 { res.WriteString(",") } - _, err := fmt.Fprintf(res, "%q:", item.Name) + i++ + + _, err := fmt.Fprintf(res, "%q:", key) if err != nil { return err } - err = toJSON(item.Value, res) + err = toJSON(value, res) if err != nil { return err } diff --git a/x/schema/model.go b/x/schema/model.go index 1319825b..00267688 100644 --- a/x/schema/model.go +++ b/x/schema/model.go @@ -40,9 +40,11 @@ func MkList(items ...Schema) *List { } } func MkMap(fields ...Field) *Map { - return &Map{ - Field: fields, + var result = make(Map) + for _, field := range fields { + result[field.Name] = field.Value } + return &result } func MkField(name string, value Schema) Field { @@ -90,11 +92,24 @@ type ( List struct { Items []Schema } - Map struct { - Field []Field - } + Map map[string]Schema ) +var _ json.Unmarshaler = (*Map)(nil) + +func (x *Map) UnmarshalJSON(bytes []byte) error { + *x = make(Map) + return shared.JSONParseObject(bytes, func(key string, value []byte) error { + val, err := SchemaFromJSON(value) + if err != nil { + return fmt.Errorf("schema.Map.UnmarshalJSON: %w", err) + } + + (*x)[key] = val + return nil + }) +} + type ( Marshaler interface { MarshalSchema() (*Map, error) diff --git a/x/schema/model_rule_union.go b/x/schema/model_rule_union.go index e5e88fc7..31429981 100644 --- a/x/schema/model_rule_union.go +++ b/x/schema/model_rule_union.go @@ -78,12 +78,7 @@ func (u *UnionVariants[A]) SchemaToUnionType(x any, schema Schema, config *goCon t := reflect.TypeOf(x) if t.Implements(u.UnionType()) { return &Map{ - Field: []Field{ - { - Name: config.variantName(t), - Value: schema, - }, - }, + config.variantName(t): schema, }, true } @@ -118,12 +113,13 @@ func (u *UnionVariants[A]) MapDefFor(x *Map, path []string, config *goConfig) (T } u.lock.RUnlock() - if len(x.Field) != 1 { + if len(*x) != 1 { return nil, false } for i := range u.variants { - if x.Field[0].Name == config.variantName(u.reflections[i]) { + variantKey := config.variantName(u.reflections[i]) + if _, ok := (*x)[variantKey]; ok { ss := make([]string, len(path)+1) copy(ss, path) ss[len(path)] = config.variantName(u.reflections[i]) diff --git a/x/schema/model_rule_when_field.go b/x/schema/model_rule_when_field.go index e0b76fb0..9b0af8c0 100644 --- a/x/schema/model_rule_when_field.go +++ b/x/schema/model_rule_when_field.go @@ -53,8 +53,8 @@ func (r *WhenField[A]) MapDefFor(x *Map, path []string, config *goConfig) (TypeM } found := false - for _, f := range x.Field { - if f.Name == parts[1] { + for key := range *x { + if key == parts[1] { found = true break } diff --git a/x/schema/model_rule_wrap_in_field.go b/x/schema/model_rule_wrap_in_field.go index e70cdb28..229c4679 100644 --- a/x/schema/model_rule_wrap_in_field.go +++ b/x/schema/model_rule_wrap_in_field.go @@ -17,11 +17,6 @@ func (w *WrapInMap[A]) SchemaToUnionType(x any, schema Schema, config *goConfig) } return &Map{ - Field: []Field{ - { - Name: w.InField, - Value: schema, - }, - }, + w.InField: schema, }, true } diff --git a/x/schema/model_schema_gen.go b/x/schema/model_schema_gen.go index 7f77479b..53181b6c 100644 --- a/x/schema/model_schema_gen.go +++ b/x/schema/model_schema_gen.go @@ -771,37 +771,11 @@ func (self *List) UnmarshalJSON(x []byte) error { func MapFromJSON(x []byte) (*Map, error) { var result *Map = new(Map) - // if is Struct - err := shared.JSONParseObject(x, func(key string, value []byte) error { - switch key { - case "Field": - return json.Unmarshal(value, &result.Field) - } - - return fmt.Errorf("schema.MapFromJSON: unknown key %s", key) - }) + err := json.Unmarshal(x, result) return result, err } func MapToJSON(x *Map) ([]byte, error) { - field_Field, err := json.Marshal(x.Field) - if err != nil { - return nil, err - } - return json.Marshal(map[string]json.RawMessage{ - "Field": field_Field, - }) -} -func (self *Map) MarshalJSON() ([]byte, error) { - return MapToJSON(self) -} - -func (self *Map) UnmarshalJSON(x []byte) error { - n, err := MapFromJSON(x) - if err != nil { - return err - } - *self = *n - return nil + return json.Marshal(x) } diff --git a/x/schema/model_self_deser.go b/x/schema/model_self_deser.go index 5d535d98..fdc0c55d 100644 --- a/x/schema/model_self_deser.go +++ b/x/schema/model_self_deser.go @@ -7,25 +7,37 @@ func (x *Map) UnmarshalSchema(y *Map) error { } func (self *Bool) UnmarshalSchema(x *Map) error { - *self = *x.Field[0].Value.(*Bool) + for _, value := range *x { + *self = *value.(*Bool) + return nil + } return nil } func (self *Binary) UnmarshalSchema(x *Map) error { - *self = *x.Field[0].Value.(*Binary) + for _, value := range *x { + *self = *value.(*Binary) + return nil + } return nil } func (self *Number) UnmarshalSchema(x *Map) error { - *self = *x.Field[0].Value.(*Number) + for _, value := range *x { + *self = *value.(*Number) + return nil + } return nil } func (self *String) UnmarshalSchema(x *Map) error { - *self = *x.Field[0].Value.(*String) + for _, value := range *x { + *self = *value.(*String) + return nil + } return nil } diff --git a/x/schema/schema_test.go b/x/schema/schema_test.go index f87df027..60c2bb2b 100644 --- a/x/schema/schema_test.go +++ b/x/schema/schema_test.go @@ -142,21 +142,18 @@ func TestMaxScalars(t *testing.T) { var m = *MkFloat(math.MaxFloat64) var s Schema = &Map{ - Field: []Field{ - {Name: "Int", Value: &m}, - {Name: "Int8", Value: &m}, - {Name: "Int16", Value: &m}, - {Name: "Int32", Value: &m}, - {Name: "Int64", Value: &m}, - {Name: "Float32", Value: &m}, - {Name: "Float64", Value: &m}, - {Name: "Uint", Value: &m}, - {Name: "Uint", Value: &m}, - {Name: "Uint8", Value: &m}, - {Name: "Uint16", Value: &m}, - {Name: "Uint32", Value: &m}, - {Name: "Uint64", Value: &m}, - }, + "Int": &m, + "Int8": &m, + "Int16": &m, + "Int32": &m, + "Int64": &m, + "Float32": &m, + "Float64": &m, + "Uint": &m, + "Uint8": &m, + "Uint16": &m, + "Uint32": &m, + "Uint64": &m, } r := MustToGo(s, WithOnlyTheseRules(WhenPath(nil, UseStruct(Max{})))).(Max) // Ints @@ -179,21 +176,18 @@ func TestMaxScalars(t *testing.T) { t.Run("test lossy conversion from small float 64 to respective scalars", func(t *testing.T) { var m = *MkFloat(float64(3)) var s Schema = &Map{ - Field: []Field{ - {Name: "Int", Value: &m}, - {Name: "Int8", Value: &m}, - {Name: "Int16", Value: &m}, - {Name: "Int32", Value: &m}, - {Name: "Int64", Value: &m}, - {Name: "Float32", Value: &m}, - {Name: "Float64", Value: &m}, - {Name: "Uint", Value: &m}, - {Name: "Uint", Value: &m}, - {Name: "Uint8", Value: &m}, - {Name: "Uint16", Value: &m}, - {Name: "Uint32", Value: &m}, - {Name: "Uint64", Value: &m}, - }, + "Int": &m, + "Int8": &m, + "Int16": &m, + "Int32": &m, + "Int64": &m, + "Float32": &m, + "Float64": &m, + "Uint": &m, + "Uint8": &m, + "Uint16": &m, + "Uint32": &m, + "Uint64": &m, } r := MustToGo(s, WithOnlyTheseRules(WhenPath(nil, UseStruct(Max{})))).(Max) // Ints @@ -247,16 +241,8 @@ func TestSchemaConversions(t *testing.T) { "bar": "str", }, out: &Map{ - Field: []Field{ - { - Name: "foo", - Value: MkInt(1), - }, - { - Name: "bar", - Value: MkString("str"), - }, - }, + "foo": MkInt(1), + "bar": MkString("str"), }, back: map[string]interface{}{ "foo": float64(1), @@ -305,16 +291,8 @@ func TestSchemaToGoStructs(t *testing.T) { }{ "schema struct to go struct": { in: &Map{ - Field: []Field{ - { - Name: "Foo", - Value: MkInt(1), - }, - { - Name: "Bar", - Value: MkString("baz"), - }, - }, + "Foo": MkInt(1), + "Bar": MkString("baz"), }, rules: []RuleMatcher{ WhenPath([]string{}, UseStruct(TestStruct1{})), @@ -328,28 +306,12 @@ func TestSchemaToGoStructs(t *testing.T) { in: &List{ Items: []Schema{ &Map{ - Field: []Field{ - { - Name: "Foo", - Value: MkInt(1), - }, - { - Name: "Bar", - Value: MkString("baz"), - }, - }, + "Foo": MkInt(1), + "Bar": MkString("baz"), }, &Map{ - Field: []Field{ - { - Name: "Foo", - Value: MkInt(13), - }, - { - Name: "Bar", - Value: MkString("baz3"), - }, - }, + "Foo": MkInt(13), + "Bar": MkString("baz3"), }, }, }, @@ -363,29 +325,11 @@ func TestSchemaToGoStructs(t *testing.T) { }, "struct with nested struct ": { in: &Map{ - Field: []Field{ - { - Name: "Foo", - Value: MkInt(1), - }, - { - Name: "Bar", - Value: MkString("baz"), - }, { - Name: "Other", - Value: &Map{ - Field: []Field{ - { - Name: "Count", - Value: MkInt(41), - }, - { - Name: "Baz", - Value: MkString("baz2"), - }, - }, - }, - }, + "Foo": MkInt(1), + "Bar": MkString("baz"), + "Other": &Map{ + "Baz": MkString("baz2"), + "Count": MkInt(41), }, }, rules: []RuleMatcher{ @@ -405,53 +349,18 @@ func TestSchemaToGoStructs(t *testing.T) { in: &List{ Items: []Schema{ &Map{ - Field: []Field{ - { - Name: "Foo", - Value: MkInt(1), - }, - { - Name: "Bar", - Value: MkString("baz"), - }, - { - Name: "Other", - Value: &Map{ - Field: []Field{ - { - Name: "Baz", - Value: MkString("baz2"), - }, - }, - }, - }, + "Foo": MkInt(1), + "Bar": MkString("baz"), + "Other": &Map{ + "Baz": MkString("baz2"), }, }, &Map{ - Field: []Field{ - { - Name: "Foo", - Value: MkInt(55), - }, - { - Name: "Bar", - Value: MkString("baz55"), - }, - { - Name: "Other", - Value: &Map{ - Field: []Field{ - { - Name: "Foo", - Value: MkInt(66), - }, - { - Name: "Bar", - Value: MkString("baz66"), - }, - }, - }, - }, + "Foo": MkInt(55), + "Bar": MkString("baz55"), + "Other": &Map{ + "Foo": MkInt(66), + "Bar": MkString("baz66"), }, }, }, @@ -506,28 +415,20 @@ func (record *recordInTest[T]) MarshalSchema() (*Map, error) { } return &Map{ - Field: []Field{ - { - Name: "ID", - Value: MkString(record.ID), - }, - { - Name: "Data", - Value: schemed, - }, - }, + "ID": MkString(record.ID), + "Data": schemed, }, nil } func (record *recordInTest[T]) UnmarshalSchema(x *Map) error { - for _, field := range x.Field { - switch field.Name { + for key, value := range *x { + switch key { case "ID": - if value, ok := As[string](field.Value); ok { + if value, ok := As[string](value); ok { record.ID = value } case "Data": - data, err := ToGoG[T](field.Value) + data, err := ToGoG[T](value) if err != nil { return fmt.Errorf(`recordInTest[T] BuildFromMapSchema: failed to convert "Data" value: %w`, err) } diff --git a/x/schema/utils.go b/x/schema/utils.go index d19beb4b..e315b0b2 100644 --- a/x/schema/utils.go +++ b/x/schema/utils.go @@ -174,9 +174,9 @@ func GetLocation(data Schema, locations []Location) Schema { return nil, locations } - for _, item := range mapData.Field { - if item.Name == x.Name { - return item.Value, locations + for key, value := range *mapData { + if key == x.Name { + return value, locations } } @@ -203,8 +203,8 @@ func GetLocation(data Schema, locations []Location) Schema { return nil, locations case *Map: - for _, item := range data.(*Map).Field { - newData := GetLocation(item.Value, locations) + for _, value := range *data.(*Map) { + newData := GetLocation(value, locations) if newData != nil { return newData, nil } @@ -251,8 +251,8 @@ func Reduce[A any](data Schema, init A, fn func(Schema, A) A) A { return init }, func(x *Map) A { - for _, y := range x.Field { - init = fn(y.Value, init) + for _, value := range *x { + init = fn(value, init) } return init @@ -353,13 +353,13 @@ func Compare(a, b Schema) int { case *None, *Bool, *Number, *String, *Binary, *List: return 1 case *Map: - if len(x.Field) == len(y.Field) { - for _, xField := range x.Field { + if len(*x) == len(*y) { + for xName, xField := range *x { var found bool - for _, yField := range y.Field { - if yField.Name == xField.Name { + for yName, yField := range *y { + if xName == yName { found = true - cmp := Compare(xField.Value, yField.Value) + cmp := Compare(xField, yField) if cmp != 0 { return cmp } @@ -373,7 +373,7 @@ func Compare(a, b Schema) int { return 0 } - if len(x.Field) > len(y.Field) { + if len(*x) > len(*y) { return 1 } diff --git a/x/workflow/workflow_deser_test.go b/x/workflow/workflow_deser_test.go index 158428a9..5be5e8e9 100644 --- a/x/workflow/workflow_deser_test.go +++ b/x/workflow/workflow_deser_test.go @@ -202,26 +202,9 @@ func TestSecond(t *testing.T) { }, "Input": { "schema.Map": { - "Field": [ - { - "Name": "prompt", - "Value": { - "schema.String": "no text" - } - }, - { - "Name": "width", - "Value": { - "schema.Number": 100 - } - }, - { - "Name": "height", - "Value": { - "schema.Number": 100 - } - } - ] + "prompt": {"schema.String": "hello world"}, + "width": {"schema.Number": 100}, + "height": {"schema.Number": 100} } } } From e2a2a6ddc6843941ac0a83351fa99a079a93f26d Mon Sep 17 00:00:00 2001 From: widmogrod Date: Tue, 12 Dec 2023 01:52:05 +0100 Subject: [PATCH 03/14] example/myapp: regenerate --- example/my-app/src/App.tsx | 56 ++----- ...b_com_widmogrod_mkunion_exammple_my-app.ts | 8 +- .../github_com_widmogrod_mkunion_x_schema.ts | 8 +- ...github_com_widmogrod_mkunion_x_workflow.ts | 140 +++++++++--------- 4 files changed, 87 insertions(+), 125 deletions(-) diff --git a/example/my-app/src/App.tsx b/example/my-app/src/App.tsx index e8517768..2bd1289f 100644 --- a/example/my-app/src/App.tsx +++ b/example/my-app/src/App.tsx @@ -282,27 +282,10 @@ function generateImage(imageWidth: number, imageHeight: number, onData?: (data: }, Input: { "schema.Map": { - Field: [ - { - Name: "prompt", - Value: { - "schema.String": "no text", - } - }, - { - Name: "width", - Value: { - "schema.Number": imageWidth, - } - }, - { - Name: "height", - Value: { - "schema.Number": imageHeight, - } - }, - ] - } as schema.Map, + "prompt": {"schema.String": "no text"}, + "width": {"schema.Number": imageWidth}, + "height": {"schema.Number": imageHeight}, + }, }, } } @@ -383,26 +366,9 @@ function runContactAwait(imageWidth: number, imageHeight: number, onData?: (data }, Input: { "schema.Map": { - Field: [ - { - Name: "prompt", - Value: { - "schema.String": "no text", - } - }, - { - Name: "width", - Value: { - "schema.Number": imageWidth, - } - }, - { - Name: "height", - Value: { - "schema.Number": imageHeight, - } - }, - ] + "prompt": {"schema.String": "no text"}, + "width": {"schema.Number": imageWidth}, + "height": {"schema.Number": imageHeight}, }, }, } @@ -787,7 +753,7 @@ function SchemaValue(props: { data?: schema.Schema }) { return <>binary } else if ("schema.Map" in props.data) { const mapData = props.data["schema.Map"]; - const keys = mapData.Field + const keys = Object.keys(mapData); if (keys && keys.length === 0) { return null; // If the map is empty, return null (no table to display) @@ -803,10 +769,10 @@ function SchemaValue(props: { data?: schema.Schema }) { {keys && keys.map((key) => ( - - {key.Name} + + {key} - + ))} diff --git a/example/my-app/src/workflow/github_com_widmogrod_mkunion_exammple_my-app.ts b/example/my-app/src/workflow/github_com_widmogrod_mkunion_exammple_my-app.ts index eb934747..cccd66d2 100644 --- a/example/my-app/src/workflow/github_com_widmogrod_mkunion_exammple_my-app.ts +++ b/example/my-app/src/workflow/github_com_widmogrod_mkunion_exammple_my-app.ts @@ -37,19 +37,19 @@ export type ChatResponses = { Responses?: ChatResult[], } -export type Service = { -} export type ListWorkflowsFn = { Count?: number, Words?: string[], EnumTest?: string, } -export type RefreshStates = { -} export type RefreshFlows = { } export type GenerateImage = { Width?: number, Height?: number, } +export type RefreshStates = { +} +export type Service = { +} diff --git a/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_schema.ts b/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_schema.ts index ab3b78f8..2cd29763 100644 --- a/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_schema.ts +++ b/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_schema.ts @@ -26,24 +26,20 @@ export type Schema = { } | { // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema "$type"?: "schema.Map", - "schema.Map": Map + "schema.Map": {[key: string]: Schema} } export type None = { } export type Binary = { - B?: string, + B?: any[], } export type List = { Items?: Schema[], } -export type Map = { - Field?: Field[], -} - export type Field = { Name?: string, Value?: Schema, diff --git a/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_workflow.ts b/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_workflow.ts index 2fa5f02b..e3be2f0c 100644 --- a/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_workflow.ts +++ b/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_workflow.ts @@ -1,67 +1,3 @@ -//generated by mkunion -export type RunOption = { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.ScheduleRun", - "workflow.ScheduleRun": ScheduleRun -} | { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.DelayRun", - "workflow.DelayRun": DelayRun -} - -export type ScheduleRun = { - Interval?: string, -} - -export type DelayRun = { - DelayBySeconds?: number, -} - -//generated by mkunion -export type Command = { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.Run", - "workflow.Run": Run -} | { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.Callback", - "workflow.Callback": Callback -} | { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.TryRecover", - "workflow.TryRecover": TryRecover -} | { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.StopSchedule", - "workflow.StopSchedule": StopSchedule -} | { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.ResumeSchedule", - "workflow.ResumeSchedule": ResumeSchedule -} - -export type Run = { - Flow?: Worflow, - Input?: schema.Schema, - RunOption?: RunOption, -} - -export type Callback = { - CallbackID?: string, - Result?: schema.Schema, -} - -export type TryRecover = { -} - -export type StopSchedule = { - ParentRunID?: string, -} - -export type ResumeSchedule = { - ParentRunID?: string, -} - //generated by mkunion export type State = { // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema @@ -245,21 +181,85 @@ export type Compare = { Right?: Reshaper, } -export type BaseState = { +//generated by mkunion +export type RunOption = { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.ScheduleRun", + "workflow.ScheduleRun": ScheduleRun +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.DelayRun", + "workflow.DelayRun": DelayRun +} + +export type ScheduleRun = { + Interval?: string, +} + +export type DelayRun = { + DelayBySeconds?: number, +} + +//generated by mkunion +export type Command = { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.Run", + "workflow.Run": Run +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.Callback", + "workflow.Callback": Callback +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.TryRecover", + "workflow.TryRecover": TryRecover +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.StopSchedule", + "workflow.StopSchedule": StopSchedule +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.ResumeSchedule", + "workflow.ResumeSchedule": ResumeSchedule +} + +export type Run = { Flow?: Worflow, - RunID?: string, - StepID?: string, - Variables?: {[key: string]: any}, - ExprResult?: {[key: string]: any}, - DefaultMaxRetries?: number, + Input?: schema.Schema, RunOption?: RunOption, } + +export type Callback = { + CallbackID?: string, + Result?: schema.Schema, +} + +export type TryRecover = { +} + +export type StopSchedule = { + ParentRunID?: string, +} + +export type ResumeSchedule = { + ParentRunID?: string, +} + export type ApplyAwaitOptions = { Timeout?: number, } export type ResumeOptions = { Timeout?: number, } +export type BaseState = { + Flow?: Worflow, + RunID?: string, + StepID?: string, + Variables?: {[key: string]: any}, + ExprResult?: {[key: string]: any}, + DefaultMaxRetries?: number, + RunOption?: RunOption, +} export type Execution = { FlowID?: string, Status?: State, From aa310c8b3e7654faf6d96a6534ebf2e6fbf5edf9 Mon Sep 17 00:00:00 2001 From: widmogrod Date: Tue, 12 Dec 2023 02:06:11 +0100 Subject: [PATCH 04/14] x/schema List now is []Schema --- ...b_com_widmogrod_mkunion_exammple_my-app.ts | 12 +- .../github_com_widmogrod_mkunion_x_schema.ts | 6 +- ...github_com_widmogrod_mkunion_x_workflow.ts | 180 +++++++++--------- x/schema/dynamo_db.go | 66 +++---- x/schema/go.go | 14 +- x/schema/go_test.go | 22 +-- x/schema/json.go | 2 +- x/schema/model.go | 12 +- x/schema/model_schema_gen.go | 43 +---- x/schema/schema_test.go | 62 +++--- x/schema/utils.go | 18 +- x/storage/schemaless/opensearch.go | 4 +- 12 files changed, 189 insertions(+), 252 deletions(-) diff --git a/example/my-app/src/workflow/github_com_widmogrod_mkunion_exammple_my-app.ts b/example/my-app/src/workflow/github_com_widmogrod_mkunion_exammple_my-app.ts index cccd66d2..1aa4f216 100644 --- a/example/my-app/src/workflow/github_com_widmogrod_mkunion_exammple_my-app.ts +++ b/example/my-app/src/workflow/github_com_widmogrod_mkunion_exammple_my-app.ts @@ -37,19 +37,19 @@ export type ChatResponses = { Responses?: ChatResult[], } +export type RefreshStates = { +} +export type RefreshFlows = { +} +export type Service = { +} export type ListWorkflowsFn = { Count?: number, Words?: string[], EnumTest?: string, } -export type RefreshFlows = { -} export type GenerateImage = { Width?: number, Height?: number, } -export type RefreshStates = { -} -export type Service = { -} diff --git a/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_schema.ts b/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_schema.ts index 2cd29763..962e1b31 100644 --- a/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_schema.ts +++ b/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_schema.ts @@ -22,7 +22,7 @@ export type Schema = { } | { // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema "$type"?: "schema.List", - "schema.List": List + "schema.List": Schema[] } | { // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema "$type"?: "schema.Map", @@ -36,10 +36,6 @@ export type Binary = { B?: any[], } -export type List = { - Items?: Schema[], -} - export type Field = { Name?: string, Value?: Schema, diff --git a/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_workflow.ts b/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_workflow.ts index e3be2f0c..b5bbf216 100644 --- a/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_workflow.ts +++ b/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_workflow.ts @@ -1,85 +1,3 @@ -//generated by mkunion -export type State = { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.NextOperation", - "workflow.NextOperation": NextOperation -} | { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.Done", - "workflow.Done": Done -} | { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.Error", - "workflow.Error": Error -} | { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.Await", - "workflow.Await": Await -} | { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.Scheduled", - "workflow.Scheduled": Scheduled -} | { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.ScheduleStopped", - "workflow.ScheduleStopped": ScheduleStopped -} - -export type NextOperation = { - Result?: schema.Schema, - BaseState?: BaseState, -} - -export type Done = { - Result?: schema.Schema, - BaseState?: BaseState, -} - -export type Error = { - Code?: string, - Reason?: string, - Retried?: number, - BaseState?: BaseState, -} - -export type Await = { - CallbackID?: string, - Timeout?: number, - BaseState?: BaseState, -} - -export type Scheduled = { - ExpectedRunTimestamp?: number, - ParentRunID?: string, - BaseState?: BaseState, -} - -export type ScheduleStopped = { - ParentRunID?: string, - BaseState?: BaseState, -} - -//generated by mkunion -export type Worflow = { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.Flow", - "workflow.Flow": Flow -} | { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.FlowRef", - "workflow.FlowRef": FlowRef -} - -export type Flow = { - Name?: string, - Arg?: string, - Body?: Expr[], -} - -export type FlowRef = { - FlowID?: string, -} - //generated by mkunion export type Expr = { // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema @@ -245,6 +163,96 @@ export type ResumeSchedule = { ParentRunID?: string, } +//generated by mkunion +export type State = { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.NextOperation", + "workflow.NextOperation": NextOperation +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.Done", + "workflow.Done": Done +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.Error", + "workflow.Error": Error +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.Await", + "workflow.Await": Await +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.Scheduled", + "workflow.Scheduled": Scheduled +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.ScheduleStopped", + "workflow.ScheduleStopped": ScheduleStopped +} + +export type NextOperation = { + Result?: schema.Schema, + BaseState?: BaseState, +} + +export type Done = { + Result?: schema.Schema, + BaseState?: BaseState, +} + +export type Error = { + Code?: string, + Reason?: string, + Retried?: number, + BaseState?: BaseState, +} + +export type Await = { + CallbackID?: string, + Timeout?: number, + BaseState?: BaseState, +} + +export type Scheduled = { + ExpectedRunTimestamp?: number, + ParentRunID?: string, + BaseState?: BaseState, +} + +export type ScheduleStopped = { + ParentRunID?: string, + BaseState?: BaseState, +} + +//generated by mkunion +export type Worflow = { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.Flow", + "workflow.Flow": Flow +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.FlowRef", + "workflow.FlowRef": FlowRef +} + +export type Flow = { + Name?: string, + Arg?: string, + Body?: Expr[], +} + +export type FlowRef = { + FlowID?: string, +} + +export type Execution = { + FlowID?: string, + Status?: State, + Location?: string, + StartTime?: number, + EndTime?: number, + Variables?: {[key: string]: any}, +} export type ApplyAwaitOptions = { Timeout?: number, } @@ -260,14 +268,6 @@ export type BaseState = { DefaultMaxRetries?: number, RunOption?: RunOption, } -export type Execution = { - FlowID?: string, - Status?: State, - Location?: string, - StartTime?: number, - EndTime?: number, - Variables?: {[key: string]: any}, -} //generated by mkunion export type FunctionDSL = { // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema diff --git a/x/schema/dynamo_db.go b/x/schema/dynamo_db.go index b8d1e6f6..61c3a5f5 100644 --- a/x/schema/dynamo_db.go +++ b/x/schema/dynamo_db.go @@ -38,7 +38,7 @@ func ToDynamoDB(x Schema) types.AttributeValue { result := &types.AttributeValueMemberL{ Value: []types.AttributeValue{}, } - for _, item := range x.Items { + for _, item := range *x { result.Value = append(result.Value, ToDynamoDB(item)) } return result @@ -61,36 +61,30 @@ func FromDynamoDB(x types.AttributeValue) (Schema, error) { return &Binary{B: y.Value}, nil case *types.AttributeValueMemberBS: - result := &List{ - Items: []Schema{}, - } + result := List{} for _, item := range y.Value { - result.Items = append(result.Items, &Binary{B: item}) + result = append(result, &Binary{B: item}) } - return result, nil + return &result, nil case *types.AttributeValueMemberNS: - result := &List{ - Items: []Schema{}, - } + result := List{} for _, item := range y.Value { num, err := strconv.ParseFloat(item, 64) if err != nil { return nil, err } - result.Items = append(result.Items, MkFloat(num)) + result = append(result, MkFloat(num)) } - return result, nil + return &result, nil case *types.AttributeValueMemberSS: - result := &List{ - Items: []Schema{}, - } + result := List{} for _, item := range y.Value { - result.Items = append(result.Items, MkString(item)) + result = append(result, MkString(item)) } - return result, nil + return &result, nil case *types.AttributeValueMemberNULL: return &None{}, nil @@ -110,17 +104,15 @@ func FromDynamoDB(x types.AttributeValue) (Schema, error) { return MkString(y.Value), nil case *types.AttributeValueMemberL: - result := &List{ - Items: []Schema{}, - } + result := List{} for _, item := range y.Value { v, err := FromDynamoDB(item) if err != nil { return nil, err } - result.Items = append(result.Items, v) + result = append(result, v) } - return result, nil + return &result, nil case *types.AttributeValueMemberM: result := make(Map) @@ -153,11 +145,11 @@ func UnwrapDynamoDB(data Schema) (Schema, error) { case "SS": switch y := value.(type) { case *List: - result := &List{} - for _, item := range y.Items { - result.Items = append(result.Items, MkString(AsDefault[string](item, ""))) + result := List{} + for _, item := range *y { + result = append(result, MkString(AsDefault[string](item, ""))) } - return result, nil + return &result, nil default: return nil, fmt.Errorf("schema.UnwrapDynamoDB: unknown type (2): %s=%T", name, value) } @@ -169,11 +161,11 @@ func UnwrapDynamoDB(data Schema) (Schema, error) { case "NS": switch y := value.(type) { case *List: - result := &List{} - for _, item := range y.Items { - result.Items = append(result.Items, MkFloat(AsDefault[float64](item, 0))) + result := List{} + for _, item := range *y { + result = append(result, MkFloat(AsDefault[float64](item, 0))) } - return result, nil + return &result, nil default: return nil, fmt.Errorf("schema.UnwrapDynamoDB: unknown type (2): %s=%T", name, value) } @@ -188,11 +180,11 @@ func UnwrapDynamoDB(data Schema) (Schema, error) { case "BS": switch y := value.(type) { case *List: - result := &List{} - for _, item := range y.Items { - result.Items = append(result.Items, item) + result := List{} + for _, item := range *y { + result = append(result, item) } - return result, nil + return &result, nil default: return nil, fmt.Errorf("schema.UnwrapDynamoDB: unknown type (2): %s=%T", name, value) } @@ -216,15 +208,15 @@ func UnwrapDynamoDB(data Schema) (Schema, error) { case "L": switch y := value.(type) { case *List: - result := &List{} - for _, item := range y.Items { + result := List{} + for _, item := range *y { unwrapped, err := UnwrapDynamoDB(item) if err != nil { return nil, err } - result.Items = append(result.Items, unwrapped) + result = append(result, unwrapped) } - return result, nil + return &result, nil default: return nil, fmt.Errorf("schema.UnwrapDynamoDB: unknown type (2): %s=%T", name, value) } diff --git a/x/schema/go.go b/x/schema/go.go index eaac5d9a..cffae46d 100644 --- a/x/schema/go.go +++ b/x/schema/go.go @@ -372,11 +372,11 @@ func goToSchema(x any, c *goConfig) Schema { } case []any: - var r = &List{} + var r = List{} for _, v := range y { - r.Items = append(r.Items, goToSchema(v, c)) + r = append(r, goToSchema(v, c)) } - return r + return &r case map[string]any: var r = make(Map) @@ -429,11 +429,11 @@ func goToSchema(x any, c *goConfig) Schema { } if v.Kind() == reflect.Slice { - var r = &List{} + var r = List{} for i := 0; i < v.Len(); i++ { - r.Items = append(r.Items, goToSchema(v.Index(i), c)) + r = append(r, goToSchema(v.Index(i), c)) } - return r + return &r } } @@ -507,7 +507,7 @@ func schemaToGo(x Schema, c *goConfig, path []string) (any, error) { }, func(x *List) (any, error) { build := c.ListDefFor(x, path).NewListBuilder() - for _, v := range x.Items { + for _, v := range *x { c.activeBuilder = build value, err := schemaToGo(v, c, append(path, "[*]")) if err != nil { diff --git a/x/schema/go_test.go b/x/schema/go_test.go index 34a4a32e..f2604f17 100644 --- a/x/schema/go_test.go +++ b/x/schema/go_test.go @@ -59,12 +59,10 @@ func TestGoToSchemaComplex(t *testing.T) { expected := &Map{ "BStruct": &Map{ "Foo": MkInt(123), - "Bars": &List{ - Items: []Schema{ - MkString("bar"), - MkString("baz"), - }, - }, + "Bars": MkList( + MkString("bar"), + MkString("baz"), + ), "Taz": &Map{ "taz1": MkString("taz2"), }, @@ -72,14 +70,12 @@ func TestGoToSchemaComplex(t *testing.T) { "Age": MkInt(123), }, "S": MkString("some string"), - "List": &List{ - Items: []Schema{ - &Map{ - "Foo": MkInt(444), - "Bar": MkInt(0), - }, + "List": MkList( + &Map{ + "Foo": MkInt(444), + "Bar": MkInt(0), }, - }, + ), "Ma": &Map{ "key": &Map{ "Foo": MkInt(666), diff --git a/x/schema/json.go b/x/schema/json.go index 3d77fc21..5307bff7 100644 --- a/x/schema/json.go +++ b/x/schema/json.go @@ -68,7 +68,7 @@ func toJSON(schema Schema, res *bytes.Buffer) error { }, func(x *List) error { res.WriteString("[") - for i, item := range x.Items { + for i, item := range *x { if i > 0 { res.WriteString(",") } diff --git a/x/schema/model.go b/x/schema/model.go index 00267688..ec10e985 100644 --- a/x/schema/model.go +++ b/x/schema/model.go @@ -35,9 +35,9 @@ func MkString(s string) *String { } func MkList(items ...Schema) *List { - return &List{ - Items: items, - } + result := make(List, len(items)) + copy(result, items) + return &result } func MkMap(fields ...Field) *Map { var result = make(Map) @@ -89,10 +89,8 @@ type ( Number float64 String string Binary struct{ B []byte } - List struct { - Items []Schema - } - Map map[string]Schema + List []Schema + Map map[string]Schema ) var _ json.Unmarshaler = (*Map)(nil) diff --git a/x/schema/model_schema_gen.go b/x/schema/model_schema_gen.go index 53181b6c..f413db8d 100644 --- a/x/schema/model_schema_gen.go +++ b/x/schema/model_schema_gen.go @@ -178,11 +178,6 @@ func (d *SchemaDepthFirstVisitor[A]) VisitList(v *List) any { if d.stop { return nil } - for idx := range v.Items { - if _ = v.Items[idx].AcceptSchema(d); d.stop { - return nil - } - } return nil } @@ -282,9 +277,6 @@ func (d *SchemaBreadthFirstVisitor[A]) VisitBinary(v *Binary) any { func (d *SchemaBreadthFirstVisitor[A]) VisitList(v *List) any { d.queue = append(d.queue, v) - for idx := range v.Items { - d.queue = append(d.queue, v.Items[idx]) - } if d.shouldExecute[v] { d.shouldExecute[v] = false @@ -729,44 +721,13 @@ func (self *Binary) UnmarshalJSON(x []byte) error { func ListFromJSON(x []byte) (*List, error) { var result *List = new(List) - // if is Struct - err := shared.JSONParseObject(x, func(key string, value []byte) error { - switch key { - case "Items": - res, err := shared.JSONToListWithDeserializer(value, result.Items, SchemaFromJSON) - if err != nil { - return fmt.Errorf("schema._FromJSON: field Schema %w", err) - } - result.Items = res - return nil - } - - return fmt.Errorf("schema.ListFromJSON: unknown key %s", key) - }) + err := json.Unmarshal(x, result) return result, err } func ListToJSON(x *List) ([]byte, error) { - field_Items, err := shared.JSONListFromSerializer(x.Items, SchemaToJSON) - if err != nil { - return nil, err - } - return json.Marshal(map[string]json.RawMessage{ - "Items": field_Items, - }) -} -func (self *List) MarshalJSON() ([]byte, error) { - return ListToJSON(self) -} - -func (self *List) UnmarshalJSON(x []byte) error { - n, err := ListFromJSON(x) - if err != nil { - return err - } - *self = *n - return nil + return json.Marshal(x) } func MapFromJSON(x []byte) (*Map, error) { diff --git a/x/schema/schema_test.go b/x/schema/schema_test.go index 60c2bb2b..0150584b 100644 --- a/x/schema/schema_test.go +++ b/x/schema/schema_test.go @@ -219,13 +219,11 @@ func TestSchemaConversions(t *testing.T) { { name: "go list to schema and back", in: []int{1, 2, 3}, - out: &List{ - Items: []Schema{ - MkInt(1), - MkInt(2), - MkInt(3), - }, - }, + out: MkList( + MkInt(1), + MkInt(2), + MkInt(3), + ), // Yes, back conversion always normalise to floats and []any // To map back to correct type use WellDefinedTypeToGo(_, WhenPath(nil, UseSlice(int))) back: []interface{}{ @@ -303,18 +301,16 @@ func TestSchemaToGoStructs(t *testing.T) { }, }, "schema with list of structs": { - in: &List{ - Items: []Schema{ - &Map{ - "Foo": MkInt(1), - "Bar": MkString("baz"), - }, - &Map{ - "Foo": MkInt(13), - "Bar": MkString("baz3"), - }, + in: MkList( + &Map{ + "Foo": MkInt(1), + "Bar": MkString("baz"), }, - }, + &Map{ + "Foo": MkInt(13), + "Bar": MkString("baz3"), + }, + ), rules: []RuleMatcher{ WhenPath([]string{"[*]"}, UseStruct(TestStruct1{})), }, @@ -346,25 +342,23 @@ func TestSchemaToGoStructs(t *testing.T) { }, }, "schema with list of structs with nested struct": { - in: &List{ - Items: []Schema{ - &Map{ - "Foo": MkInt(1), - "Bar": MkString("baz"), - "Other": &Map{ - "Baz": MkString("baz2"), - }, + in: MkList( + &Map{ + "Foo": MkInt(1), + "Bar": MkString("baz"), + "Other": &Map{ + "Baz": MkString("baz2"), }, - &Map{ - "Foo": MkInt(55), - "Bar": MkString("baz55"), - "Other": &Map{ - "Foo": MkInt(66), - "Bar": MkString("baz66"), - }, + }, + &Map{ + "Foo": MkInt(55), + "Bar": MkString("baz55"), + "Other": &Map{ + "Foo": MkInt(66), + "Bar": MkString("baz66"), }, }, - }, + ), rules: []RuleMatcher{ WhenPath([]string{"[*]"}, UseStruct(TestStruct1{})), WhenPath([]string{"[*]", "Other?.Foo"}, UseStruct(&TestStruct1{})), diff --git a/x/schema/utils.go b/x/schema/utils.go index e315b0b2..760e3380 100644 --- a/x/schema/utils.go +++ b/x/schema/utils.go @@ -184,16 +184,16 @@ func GetLocation(data Schema, locations []Location) Schema { }, func(x *LocationIndex) (Schema, []Location) { listData, ok := data.(*List) - if ok && len(listData.Items) > x.Index { - return listData.Items[x.Index], locations + if ok && len(*listData) > x.Index { + return (*listData)[x.Index], locations } return nil, locations }, func(x *LocationAnything) (Schema, []Location) { - switch data.(type) { + switch y := data.(type) { case *List: - for _, item := range data.(*List).Items { + for _, item := range *y { newData := GetLocation(item, locations) if newData != nil { return newData, nil @@ -244,7 +244,7 @@ func Reduce[A any](data Schema, init A, fn func(Schema, A) A) A { return fn(x, init) }, func(x *List) A { - for _, y := range x.Items { + for _, y := range *x { init = fn(y, init) } @@ -329,16 +329,16 @@ func Compare(a, b Schema) int { case *None, *Bool, *Number, *String, *Binary: return 1 case *List: - if len(x.Items) == len(y.Items) { - for i := range x.Items { - cmp := Compare(x.Items[i], y.Items[i]) + if len(*x) == len(*y) { + for i := range *x { + cmp := Compare((*x)[i], (*y)[i]) if cmp != 0 { return cmp } } return 0 } - if len(x.Items) > len(y.Items) { + if len(*x) > len(*y) { return 1 } diff --git a/x/storage/schemaless/opensearch.go b/x/storage/schemaless/opensearch.go index 18219793..3a665c4b 100644 --- a/x/storage/schemaless/opensearch.go +++ b/x/storage/schemaless/opensearch.go @@ -104,8 +104,8 @@ func (os *OpenSearchRepository) FindingRecords(query FindingRecords[Record[schem panic(fmt.Errorf("expected list, got %T", schemed)) } - afterSearch := make([]string, len(list.Items)) - for i, item := range list.Items { + afterSearch := make([]string, len(*list)) + for i, item := range *list { str, ok := schema.As[string](item) if !ok { panic(fmt.Errorf("expected string, got %T", item)) From fdf2ca4702a159dc5b8540e2e4bb6eb26f2b7112 Mon Sep 17 00:00:00 2001 From: widmogrod Date: Tue, 12 Dec 2023 02:38:59 +0100 Subject: [PATCH 05/14] x/schame Binary now is normalised --- example/my-app/src/App.tsx | 4 +- ...b_com_widmogrod_mkunion_exammple_my-app.ts | 32 +-- .../github_com_widmogrod_mkunion_x_schema.ts | 6 +- ...github_com_widmogrod_mkunion_x_workflow.ts | 224 +++++++++--------- x/schema/README.md | 8 + x/schema/dynamo_db.go | 6 +- x/schema/go.go | 6 +- x/schema/json.go | 2 +- x/schema/model.go | 5 +- x/schema/model_builder_struct.go | 11 + x/schema/model_schema_gen.go | 30 +-- x/schema/utils.go | 6 +- 12 files changed, 165 insertions(+), 175 deletions(-) diff --git a/example/my-app/src/App.tsx b/example/my-app/src/App.tsx index 2bd1289f..547c6bbf 100644 --- a/example/my-app/src/App.tsx +++ b/example/my-app/src/App.tsx @@ -431,8 +431,8 @@ function App() { if (data["workflow.Done"].Result) { let result = data["workflow.Done"].Result if ("schema.Binary" in result) { - if (typeof result["schema.Binary"]?.B === "string") { - setImage(result["schema.Binary"]?.B) + if (typeof result["schema.Binary"] === "string") { + setImage(result["schema.Binary"]) } } } diff --git a/example/my-app/src/workflow/github_com_widmogrod_mkunion_exammple_my-app.ts b/example/my-app/src/workflow/github_com_widmogrod_mkunion_exammple_my-app.ts index 1aa4f216..4c12cdb2 100644 --- a/example/my-app/src/workflow/github_com_widmogrod_mkunion_exammple_my-app.ts +++ b/example/my-app/src/workflow/github_com_widmogrod_mkunion_exammple_my-app.ts @@ -1,14 +1,3 @@ -//generated by mkunion -export type ChatCMD = { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "main.UserMessage", - "main.UserMessage": UserMessage -} - -export type UserMessage = { - Message?: string, -} - //generated by mkunion export type ChatResult = { // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema @@ -37,19 +26,30 @@ export type ChatResponses = { Responses?: ChatResult[], } -export type RefreshStates = { +//generated by mkunion +export type ChatCMD = { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "main.UserMessage", + "main.UserMessage": UserMessage } -export type RefreshFlows = { + +export type UserMessage = { + Message?: string, } + export type Service = { } +export type GenerateImage = { + Width?: number, + Height?: number, +} export type ListWorkflowsFn = { Count?: number, Words?: string[], EnumTest?: string, } -export type GenerateImage = { - Width?: number, - Height?: number, +export type RefreshStates = { +} +export type RefreshFlows = { } diff --git a/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_schema.ts b/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_schema.ts index 962e1b31..35a1f0d4 100644 --- a/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_schema.ts +++ b/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_schema.ts @@ -18,7 +18,7 @@ export type Schema = { } | { // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema "$type"?: "schema.Binary", - "schema.Binary": Binary + "schema.Binary": any[] } | { // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema "$type"?: "schema.List", @@ -32,10 +32,6 @@ export type Schema = { export type None = { } -export type Binary = { - B?: any[], -} - export type Field = { Name?: string, Value?: Schema, diff --git a/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_workflow.ts b/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_workflow.ts index b5bbf216..2fa5f02b 100644 --- a/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_workflow.ts +++ b/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_workflow.ts @@ -1,104 +1,3 @@ -//generated by mkunion -export type Expr = { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.End", - "workflow.End": End -} | { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.Assign", - "workflow.Assign": Assign -} | { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.Apply", - "workflow.Apply": Apply -} | { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.Choose", - "workflow.Choose": Choose -} - -export type End = { - ID?: string, - Result?: Reshaper, -} - -export type Assign = { - ID?: string, - VarOk?: string, - VarErr?: string, - Val?: Expr, -} - -export type Apply = { - ID?: string, - Name?: string, - Args?: Reshaper[], - Await?: ApplyAwaitOptions, -} - -export type Choose = { - ID?: string, - If?: Predicate, - Then?: Expr[], - Else?: Expr[], -} - -//generated by mkunion -export type Reshaper = { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.GetValue", - "workflow.GetValue": GetValue -} | { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.SetValue", - "workflow.SetValue": SetValue -} - -export type GetValue = { - Path?: string, -} - -export type SetValue = { - Value?: schema.Schema, -} - -//generated by mkunion -export type Predicate = { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.And", - "workflow.And": And -} | { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.Or", - "workflow.Or": Or -} | { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.Not", - "workflow.Not": Not -} | { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.Compare", - "workflow.Compare": Compare -} - -export type And = { - L?: Predicate[], -} - -export type Or = { - L?: Predicate[], -} - -export type Not = { - P?: Predicate, -} - -export type Compare = { - Operation?: string, - Left?: Reshaper, - Right?: Reshaper, -} - //generated by mkunion export type RunOption = { // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema @@ -245,20 +144,107 @@ export type FlowRef = { FlowID?: string, } -export type Execution = { - FlowID?: string, - Status?: State, - Location?: string, - StartTime?: number, - EndTime?: number, - Variables?: {[key: string]: any}, +//generated by mkunion +export type Expr = { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.End", + "workflow.End": End +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.Assign", + "workflow.Assign": Assign +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.Apply", + "workflow.Apply": Apply +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.Choose", + "workflow.Choose": Choose } -export type ApplyAwaitOptions = { - Timeout?: number, + +export type End = { + ID?: string, + Result?: Reshaper, } -export type ResumeOptions = { - Timeout?: number, + +export type Assign = { + ID?: string, + VarOk?: string, + VarErr?: string, + Val?: Expr, +} + +export type Apply = { + ID?: string, + Name?: string, + Args?: Reshaper[], + Await?: ApplyAwaitOptions, +} + +export type Choose = { + ID?: string, + If?: Predicate, + Then?: Expr[], + Else?: Expr[], +} + +//generated by mkunion +export type Reshaper = { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.GetValue", + "workflow.GetValue": GetValue +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.SetValue", + "workflow.SetValue": SetValue +} + +export type GetValue = { + Path?: string, } + +export type SetValue = { + Value?: schema.Schema, +} + +//generated by mkunion +export type Predicate = { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.And", + "workflow.And": And +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.Or", + "workflow.Or": Or +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.Not", + "workflow.Not": Not +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.Compare", + "workflow.Compare": Compare +} + +export type And = { + L?: Predicate[], +} + +export type Or = { + L?: Predicate[], +} + +export type Not = { + P?: Predicate, +} + +export type Compare = { + Operation?: string, + Left?: Reshaper, + Right?: Reshaper, +} + export type BaseState = { Flow?: Worflow, RunID?: string, @@ -268,6 +254,20 @@ export type BaseState = { DefaultMaxRetries?: number, RunOption?: RunOption, } +export type ApplyAwaitOptions = { + Timeout?: number, +} +export type ResumeOptions = { + Timeout?: number, +} +export type Execution = { + FlowID?: string, + Status?: State, + Location?: string, + StartTime?: number, + EndTime?: number, + Variables?: {[key: string]: any}, +} //generated by mkunion export type FunctionDSL = { // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema diff --git a/x/schema/README.md b/x/schema/README.md index ca64d97c..c9a56dbb 100644 --- a/x/schema/README.md +++ b/x/schema/README.md @@ -155,3 +155,11 @@ assert.Equal(t, data, result) - [ ] Support json tags in golang to map field names to schema - [ ] Add cata, ana, and hylo morphisms - [ ] Open `goConfigFunc` to allow customizing how golang types are converted to schema, passed from external code. + +### V0.10.x +- [x] `schema` use `x/shape` and native types to represent variants like Map and List and Bytes +- [ ] `schema` becomes `data` +- [ ] data.FromGo and data.ToGo works only on primitive values +- [ ] data.FromStruct and data.ToStruct works only on structs and reflection +- [ ] data.FromJSON and data.ToJSON removed and replaced by `mkunion` defaults +- [ ] data.FromDynamoDB and data.ToDynamoDB refactored \ No newline at end of file diff --git a/x/schema/dynamo_db.go b/x/schema/dynamo_db.go index 61c3a5f5..6b106dcd 100644 --- a/x/schema/dynamo_db.go +++ b/x/schema/dynamo_db.go @@ -31,7 +31,7 @@ func ToDynamoDB(x Schema) types.AttributeValue { }, func(x *Binary) types.AttributeValue { return &types.AttributeValueMemberB{ - Value: x.B, + Value: *x, } }, func(x *List) types.AttributeValue { @@ -58,12 +58,12 @@ func ToDynamoDB(x Schema) types.AttributeValue { func FromDynamoDB(x types.AttributeValue) (Schema, error) { switch y := x.(type) { case *types.AttributeValueMemberB: - return &Binary{B: y.Value}, nil + return MkBinary(y.Value), nil case *types.AttributeValueMemberBS: result := List{} for _, item := range y.Value { - result = append(result, &Binary{B: item}) + result = append(result, MkBinary(item)) } return &result, nil diff --git a/x/schema/go.go b/x/schema/go.go index cffae46d..26dcece2 100644 --- a/x/schema/go.go +++ b/x/schema/go.go @@ -254,12 +254,12 @@ func goToSchema(x any, c *goConfig) Schema { } case []byte: - return &Binary{B: y} + return MkBinary(y) case *[]byte: if y == nil { return &None{} } else { - return &Binary{B: *y} + return MkBinary(*y) } case float64: @@ -503,7 +503,7 @@ func schemaToGo(x Schema, c *goConfig, path []string) (any, error) { return x, nil } - return x.B, nil + return *x, nil }, func(x *List) (any, error) { build := c.ListDefFor(x, path).NewListBuilder() diff --git a/x/schema/json.go b/x/schema/json.go index 5307bff7..2a76fec9 100644 --- a/x/schema/json.go +++ b/x/schema/json.go @@ -60,7 +60,7 @@ func toJSON(schema Schema, res *bytes.Buffer) error { }, func(x *Binary) error { - _, err := fmt.Fprintf(res, "%q", base64.StdEncoding.EncodeToString(x.B)) + _, err := fmt.Fprintf(res, "%q", base64.StdEncoding.EncodeToString(*x)) if err != nil { return err } diff --git a/x/schema/model.go b/x/schema/model.go index ec10e985..eeffeef5 100644 --- a/x/schema/model.go +++ b/x/schema/model.go @@ -27,7 +27,8 @@ func MkFloat(x float64) *Number { } func MkBinary(b []byte) *Binary { - return &Binary{B: b} + v := Binary(b) + return &v } func MkString(s string) *String { @@ -88,7 +89,7 @@ type ( Bool bool Number float64 String string - Binary struct{ B []byte } + Binary []byte List []Schema Map map[string]Schema ) diff --git a/x/schema/model_builder_struct.go b/x/schema/model_builder_struct.go index d547c0a8..89ef33be 100644 --- a/x/schema/model_builder_struct.go +++ b/x/schema/model_builder_struct.go @@ -208,6 +208,17 @@ func (s *StructBuilder) set(f reflect.Value, value any) error { return nil } + if f.Type().Elem().Kind() == reflect.Uint8 { // Check if the field is a byte slice + switch vv := v.Interface().(type) { + case []byte: + f.SetBytes(vv) + return nil + case Binary: + f.SetBytes(vv) + return nil + } + } + if v.Kind() == reflect.Slice { st := reflect.SliceOf(f.Type().Elem()) ss := reflect.MakeSlice(st, v.Len(), v.Len()) diff --git a/x/schema/model_schema_gen.go b/x/schema/model_schema_gen.go index f413db8d..8d4ac547 100644 --- a/x/schema/model_schema_gen.go +++ b/x/schema/model_schema_gen.go @@ -684,39 +684,13 @@ func StringToJSON(x *String) ([]byte, error) { func BinaryFromJSON(x []byte) (*Binary, error) { var result *Binary = new(Binary) - // if is Struct - err := shared.JSONParseObject(x, func(key string, value []byte) error { - switch key { - case "B": - return json.Unmarshal(value, &result.B) - } - - return fmt.Errorf("schema.BinaryFromJSON: unknown key %s", key) - }) + err := json.Unmarshal(x, result) return result, err } func BinaryToJSON(x *Binary) ([]byte, error) { - field_B, err := json.Marshal(x.B) - if err != nil { - return nil, err - } - return json.Marshal(map[string]json.RawMessage{ - "B": field_B, - }) -} -func (self *Binary) MarshalJSON() ([]byte, error) { - return BinaryToJSON(self) -} - -func (self *Binary) UnmarshalJSON(x []byte) error { - n, err := BinaryFromJSON(x) - if err != nil { - return err - } - *self = *n - return nil + return json.Marshal(x) } func ListFromJSON(x []byte) (*List, error) { diff --git a/x/schema/utils.go b/x/schema/utils.go index 760e3380..736ae628 100644 --- a/x/schema/utils.go +++ b/x/schema/utils.go @@ -119,9 +119,9 @@ func As[A int | int8 | int16 | int32 | int64 | func(x *Binary) (A, bool) { switch any(def).(type) { case []byte: - return any(x.B).(A), true + return any([]byte(*x)).(A), true case string: - return any(string(x.B)).(A), true + return any(string(*x)).(A), true } return def, false @@ -319,7 +319,7 @@ func Compare(a, b Schema) int { case *None, *Bool, *Number, *String: return 1 case *Binary: - return bytes.Compare(x.B, y.B) + return bytes.Compare(*x, *y) } return -1 From a1e84bfd61b2dd9638133a826d8a082e1a805f58 Mon Sep 17 00:00:00 2001 From: widmogrod Date: Tue, 12 Dec 2023 21:54:34 +0100 Subject: [PATCH 06/14] x/shape Number type has NumberKinds, typeScript generation improvements --- ...b_com_widmogrod_mkunion_exammple_my-app.ts | 34 ++--- .../github_com_widmogrod_mkunion_x_schema.ts | 18 ++- ...github_com_widmogrod_mkunion_x_workflow.ts | 144 +++++++++--------- x/schema/json.go | 2 + x/shape/fromast.go | 6 +- x/shape/fromfile.go | 8 +- x/shape/fromfile_test.go | 2 + x/shape/fromgo_test.go | 70 +++++++++ x/shape/shape.go | 30 ++++ x/shape/totypescript.go | 89 ++++++++++- x/shape/totypescript_test.go | 118 +++++++------- x/storage/schemaless/storage.go | 16 +- 12 files changed, 363 insertions(+), 174 deletions(-) diff --git a/example/my-app/src/workflow/github_com_widmogrod_mkunion_exammple_my-app.ts b/example/my-app/src/workflow/github_com_widmogrod_mkunion_exammple_my-app.ts index 4c12cdb2..cd1e4995 100644 --- a/example/my-app/src/workflow/github_com_widmogrod_mkunion_exammple_my-app.ts +++ b/example/my-app/src/workflow/github_com_widmogrod_mkunion_exammple_my-app.ts @@ -1,3 +1,14 @@ +//generated by mkunion +export type ChatCMD = { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "main.UserMessage", + "main.UserMessage": UserMessage +} + +export type UserMessage = { + Message?: string, +} + //generated by mkunion export type ChatResult = { // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema @@ -26,30 +37,19 @@ export type ChatResponses = { Responses?: ChatResult[], } -//generated by mkunion -export type ChatCMD = { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "main.UserMessage", - "main.UserMessage": UserMessage -} - -export type UserMessage = { - Message?: string, -} - -export type Service = { -} -export type GenerateImage = { - Width?: number, - Height?: number, -} export type ListWorkflowsFn = { Count?: number, Words?: string[], EnumTest?: string, } +export type GenerateImage = { + Width?: number, + Height?: number, +} export type RefreshStates = { } export type RefreshFlows = { } +export type Service = { +} diff --git a/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_schema.ts b/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_schema.ts index 35a1f0d4..1ad5c2ab 100644 --- a/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_schema.ts +++ b/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_schema.ts @@ -6,32 +6,38 @@ export type Schema = { } | { // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema "$type"?: "schema.Bool", - "schema.Bool": boolean + "schema.Bool": Bool } | { // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema "$type"?: "schema.Number", - "schema.Number": number + "schema.Number": Number } | { // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema "$type"?: "schema.String", - "schema.String": string + "schema.String": String } | { // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema "$type"?: "schema.Binary", - "schema.Binary": any[] + "schema.Binary": Binary } | { // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema "$type"?: "schema.List", - "schema.List": Schema[] + "schema.List": List } | { // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema "$type"?: "schema.Map", - "schema.Map": {[key: string]: Schema} + "schema.Map": Map } export type None = { } +export type Bool = boolean +export type Number = number +export type String = string +export type Binary = string +export type List = Schema[] +export type Map = {[key: string]: Schema} export type Field = { Name?: string, Value?: Schema, diff --git a/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_workflow.ts b/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_workflow.ts index 2fa5f02b..bdc791f5 100644 --- a/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_workflow.ts +++ b/example/my-app/src/workflow/github_com_widmogrod_mkunion_x_workflow.ts @@ -1,67 +1,3 @@ -//generated by mkunion -export type RunOption = { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.ScheduleRun", - "workflow.ScheduleRun": ScheduleRun -} | { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.DelayRun", - "workflow.DelayRun": DelayRun -} - -export type ScheduleRun = { - Interval?: string, -} - -export type DelayRun = { - DelayBySeconds?: number, -} - -//generated by mkunion -export type Command = { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.Run", - "workflow.Run": Run -} | { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.Callback", - "workflow.Callback": Callback -} | { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.TryRecover", - "workflow.TryRecover": TryRecover -} | { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.StopSchedule", - "workflow.StopSchedule": StopSchedule -} | { - // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "workflow.ResumeSchedule", - "workflow.ResumeSchedule": ResumeSchedule -} - -export type Run = { - Flow?: Worflow, - Input?: schema.Schema, - RunOption?: RunOption, -} - -export type Callback = { - CallbackID?: string, - Result?: schema.Schema, -} - -export type TryRecover = { -} - -export type StopSchedule = { - ParentRunID?: string, -} - -export type ResumeSchedule = { - ParentRunID?: string, -} - //generated by mkunion export type State = { // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema @@ -245,21 +181,76 @@ export type Compare = { Right?: Reshaper, } -export type BaseState = { +//generated by mkunion +export type RunOption = { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.ScheduleRun", + "workflow.ScheduleRun": ScheduleRun +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.DelayRun", + "workflow.DelayRun": DelayRun +} + +export type ScheduleRun = { + Interval?: string, +} + +export type DelayRun = { + DelayBySeconds?: number, +} + +//generated by mkunion +export type Command = { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.Run", + "workflow.Run": Run +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.Callback", + "workflow.Callback": Callback +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.TryRecover", + "workflow.TryRecover": TryRecover +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.StopSchedule", + "workflow.StopSchedule": StopSchedule +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "workflow.ResumeSchedule", + "workflow.ResumeSchedule": ResumeSchedule +} + +export type Run = { Flow?: Worflow, - RunID?: string, - StepID?: string, - Variables?: {[key: string]: any}, - ExprResult?: {[key: string]: any}, - DefaultMaxRetries?: number, + Input?: schema.Schema, RunOption?: RunOption, } -export type ApplyAwaitOptions = { - Timeout?: number, + +export type Callback = { + CallbackID?: string, + Result?: schema.Schema, +} + +export type TryRecover = { +} + +export type StopSchedule = { + ParentRunID?: string, +} + +export type ResumeSchedule = { + ParentRunID?: string, } + export type ResumeOptions = { Timeout?: number, } +export type ApplyAwaitOptions = { + Timeout?: number, +} export type Execution = { FlowID?: string, Status?: State, @@ -268,6 +259,15 @@ export type Execution = { EndTime?: number, Variables?: {[key: string]: any}, } +export type BaseState = { + Flow?: Worflow, + RunID?: string, + StepID?: string, + Variables?: {[key: string]: any}, + ExprResult?: {[key: string]: any}, + DefaultMaxRetries?: number, + RunOption?: RunOption, +} //generated by mkunion export type FunctionDSL = { // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema diff --git a/x/schema/json.go b/x/schema/json.go index 2a76fec9..98a986dd 100644 --- a/x/schema/json.go +++ b/x/schema/json.go @@ -7,6 +7,7 @@ import ( "fmt" ) +// deprecated FromJSON func FromJSON(data []byte) (Schema, error) { if len(data) == 0 { return nil, nil @@ -20,6 +21,7 @@ func FromJSON(data []byte) (Schema, error) { return FromGo(x), nil } +// deprecated: ToJSON use SchemaToJSON func ToJSON(schema Schema) ([]byte, error) { res := bytes.Buffer{} err := toJSON(schema, &res) diff --git a/x/shape/fromast.go b/x/shape/fromast.go index 54ca53e8..adc85c18 100644 --- a/x/shape/fromast.go +++ b/x/shape/fromast.go @@ -22,8 +22,10 @@ func FromAst(x any, fx ...func(x Shape)) Shape { return &BooleanLike{} case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", - "float64", "float32": - return &NumberLike{} + "float64", "float32", "byte", "rune": + return &NumberLike{ + Kind: TypeStringToNumberKindMap[y.Name], + } default: if !y.IsExported() { log.Infof("formast: skipping non exported type %s", y.Name) diff --git a/x/shape/fromfile.go b/x/shape/fromfile.go index e2f516ce..7ac0e36d 100644 --- a/x/shape/fromfile.go +++ b/x/shape/fromfile.go @@ -219,10 +219,16 @@ func (f *InferredInfo) Visit(n ast.Node) ast.Visitor { Named: f.named(), } + //case "byte", "rune": + // f.shapes[f.currentType] = &NumberLike{ + // Named: f.named(), + // } + case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", - "float64", "float32": + "float64", "float32", "byte", "rune": f.shapes[f.currentType] = &NumberLike{ + Kind: TypeStringToNumberKindMap[next.Name], Named: f.named(), } diff --git a/x/shape/fromfile_test.go b/x/shape/fromfile_test.go index d329b53c..7c7e3096 100644 --- a/x/shape/fromfile_test.go +++ b/x/shape/fromfile_test.go @@ -95,6 +95,7 @@ func TestInferFromFile(t *testing.T) { }, }, &NumberLike{ + Kind: &Int64{}, Named: &Named{ Name: "D", PkgName: "testasset", @@ -102,6 +103,7 @@ func TestInferFromFile(t *testing.T) { }, }, &NumberLike{ + Kind: &Float64{}, Named: &Named{ Name: "E", PkgName: "testasset", diff --git a/x/shape/fromgo_test.go b/x/shape/fromgo_test.go index ae325625..762a962a 100644 --- a/x/shape/fromgo_test.go +++ b/x/shape/fromgo_test.go @@ -117,6 +117,76 @@ func TestFromGoo(t *testing.T) { PkgName: "shape", PkgImportName: "github.com/widmogrod/mkunion/x/shape", Fields: []*FieldLike{ + { + Name: "Kind", + Type: &UnionLike{ + Name: "NumberKind", + PkgName: "shape", + PkgImportName: "github.com/widmogrod/mkunion/x/shape", + Variant: []Shape{ + &StructLike{ + Name: "UInt8", + PkgName: "shape", + PkgImportName: "github.com/widmogrod/mkunion/x/shape", + Fields: []*FieldLike{}, + }, + &StructLike{ + Name: "UInt16", + PkgName: "shape", + PkgImportName: "github.com/widmogrod/mkunion/x/shape", + Fields: []*FieldLike{}, + }, + &StructLike{ + Name: "UInt32", + PkgName: "shape", + PkgImportName: "github.com/widmogrod/mkunion/x/shape", + Fields: []*FieldLike{}, + }, + &StructLike{ + Name: "UInt64", + PkgName: "shape", + PkgImportName: "github.com/widmogrod/mkunion/x/shape", + Fields: []*FieldLike{}, + }, + &StructLike{ + Name: "Int8", + PkgName: "shape", + PkgImportName: "github.com/widmogrod/mkunion/x/shape", + Fields: []*FieldLike{}, + }, + &StructLike{ + Name: "Int16", + PkgName: "shape", + PkgImportName: "github.com/widmogrod/mkunion/x/shape", + Fields: []*FieldLike{}, + }, + &StructLike{ + Name: "Int32", + PkgName: "shape", + PkgImportName: "github.com/widmogrod/mkunion/x/shape", + Fields: []*FieldLike{}, + }, + &StructLike{ + Name: "Int64", + PkgName: "shape", + PkgImportName: "github.com/widmogrod/mkunion/x/shape", + Fields: []*FieldLike{}, + }, + &StructLike{ + Name: "Float32", + PkgName: "shape", + PkgImportName: "github.com/widmogrod/mkunion/x/shape", + Fields: []*FieldLike{}, + }, + &StructLike{ + Name: "Float64", + PkgName: "shape", + PkgImportName: "github.com/widmogrod/mkunion/x/shape", + Fields: []*FieldLike{}, + }, + }, + }, + }, namedRef, }, }, diff --git a/x/shape/shape.go b/x/shape/shape.go index 87b944db..7535372f 100644 --- a/x/shape/shape.go +++ b/x/shape/shape.go @@ -19,6 +19,7 @@ type ( //Guard Guard } NumberLike struct { + Kind NumberKind Named *Named //Guard Guard } @@ -54,6 +55,35 @@ type ( } ) +// go:generate go run ../../cmd/mkunion/main.go -name=NumberKind +type ( + UInt8 struct{} + UInt16 struct{} + UInt32 struct{} + UInt64 struct{} + Int8 struct{} + Int16 struct{} + Int32 struct{} + Int64 struct{} + Float32 struct{} + Float64 struct{} +) + +var TypeStringToNumberKindMap = map[string]NumberKind{ + "uint8": &UInt8{}, + "uint16": &UInt16{}, + "uint32": &UInt32{}, + "uint64": &UInt64{}, + "int8": &Int8{}, + "int16": &Int16{}, + "int32": &Int32{}, + "int64": &Int64{}, + "float32": &Float32{}, + "float64": &Float64{}, + "byte": &UInt8{}, + "rune": &Int32{}, +} + type Named struct { Name string PkgName string diff --git a/x/shape/totypescript.go b/x/shape/totypescript.go index e7252be5..8a753980 100644 --- a/x/shape/totypescript.go +++ b/x/shape/totypescript.go @@ -30,7 +30,63 @@ func (o *TypeScriptOptions) NeedsToImportPkgName(pkg packageName, imp packageImp o.imports[pkg] = imp } +func ToTypeScriptOptimisation(x Shape) Shape { + return MustMatchShape( + x, + func(x *Any) Shape { + return x + }, + func(x *RefName) Shape { + return x + }, + func(x *BooleanLike) Shape { + return x + }, + func(x *StringLike) Shape { + return x + }, + func(x *NumberLike) Shape { + return x + }, + func(x *ListLike) Shape { + // do forward lookup and detect if we can optimise and convert to string + switch y := x.Element.(type) { + case *NumberLike: + switch y.Kind.(type) { + // byte is uint8 + // rune is int32 + case *UInt8, *Int32: + return &StringLike{ + Named: x.Named, + } + } + } + + x.Element = ToTypeScriptOptimisation(x.Element) + return x + }, + func(x *MapLike) Shape { + x.Val = ToTypeScriptOptimisation(x.Val) + x.Key = ToTypeScriptOptimisation(x.Key) + return x + }, + func(x *StructLike) Shape { + for _, field := range x.Fields { + field.Type = ToTypeScriptOptimisation(field.Type) + } + return x + }, + func(x *UnionLike) Shape { + for _, variant := range x.Variant { + variant = ToTypeScriptOptimisation(variant) + } + return x + }, + ) +} + func ToTypeScript(x Shape, option *TypeScriptOptions) string { + x = ToTypeScriptOptimisation(x) return MustMatchShape( x, func(x *Any) string { @@ -46,19 +102,36 @@ func ToTypeScript(x Shape, option *TypeScriptOptions) string { return fmt.Sprintf("%s.%s", x.PkgName, x.Name) }, func(x *BooleanLike) string { - return "bool" + if IsNamed(x) { + return fmt.Sprintf("export type %s = boolean", x.Named.Name) + } + return "boolean" }, func(x *StringLike) string { + if IsNamed(x) { + return fmt.Sprintf("export type %s = string", x.Named.Name) + } return "string" }, func(x *NumberLike) string { + if IsNamed(x) { + return fmt.Sprintf("export type %s = number", x.Named.Name) + } return "number" }, func(x *ListLike) string { - return fmt.Sprintf("%s[]", ToTypeScript(x.Element, option)) + result := fmt.Sprintf("%s[]", ToTypeScript(x.Element, option)) + if IsNamed(x) { + return fmt.Sprintf("export type %s = %s", x.Named.Name, result) + } + return result }, func(x *MapLike) string { - return fmt.Sprintf("{[key: %s]: %s}", ToTypeScript(x.Key, option), ToTypeScript(x.Val, option)) + result := fmt.Sprintf("{[key: %s]: %s}", ToTypeScript(x.Key, option), ToTypeScript(x.Val, option)) + if IsNamed(x) { + return fmt.Sprintf("export type %s = %s", x.Named.Name, result) + } + return result }, func(x *StructLike) string { result := &strings.Builder{} @@ -201,7 +274,7 @@ func toTypeTypeScriptTypeName(variant Shape, option *TypeScriptOptions) string { }, func(x *BooleanLike) string { if IsNamed(x) { - typeName := "boolean" + typeName := x.Named.Name typeNameFul := fmt.Sprintf("%s.%s", x.Named.PkgName, x.Named.Name) result := &strings.Builder{} @@ -217,7 +290,7 @@ func toTypeTypeScriptTypeName(variant Shape, option *TypeScriptOptions) string { }, func(x *StringLike) string { if IsNamed(x) { - typeName := "string" + typeName := x.Named.Name typeNameFul := fmt.Sprintf("%s.%s", x.Named.PkgName, x.Named.Name) result := &strings.Builder{} @@ -234,7 +307,7 @@ func toTypeTypeScriptTypeName(variant Shape, option *TypeScriptOptions) string { }, func(x *NumberLike) string { if IsNamed(x) { - typeName := "number" + typeName := x.Named.Name typeNameFul := fmt.Sprintf("%s.%s", x.Named.PkgName, x.Named.Name) result := &strings.Builder{} @@ -250,7 +323,7 @@ func toTypeTypeScriptTypeName(variant Shape, option *TypeScriptOptions) string { }, func(x *ListLike) string { if IsNamed(x) { - typeName := fmt.Sprintf("%s[]", ToTypeScript(x.Element, option)) + typeName := x.Named.Name typeNameFul := fmt.Sprintf("%s.%s", x.Named.PkgName, x.Named.Name) result := &strings.Builder{} @@ -267,7 +340,7 @@ func toTypeTypeScriptTypeName(variant Shape, option *TypeScriptOptions) string { }, func(x *MapLike) string { if IsNamed(x) { - typeName := fmt.Sprintf("{[key: %s]: %s}", ToTypeScript(x.Key, option), ToTypeScript(x.Val, option)) + typeName := x.Named.Name typeNameFul := fmt.Sprintf("%s.%s", x.Named.PkgName, x.Named.Name) result := &strings.Builder{} diff --git a/x/shape/totypescript_test.go b/x/shape/totypescript_test.go index 4dee560f..0b252aad 100644 --- a/x/shape/totypescript_test.go +++ b/x/shape/totypescript_test.go @@ -7,53 +7,15 @@ import ( ) func TestTypeScriptSchemaGeneration(t *testing.T) { + inferred, err := InferFromFile("testasset/type_example.go") + if err != nil { + t.Fatal(err) + } + + union := inferred.RetrieveUnion("Example") tsr := NewTypeScriptRenderer() - tsr.AddUnion(&UnionLike{ - Name: "Tree", - PkgName: "test", - PkgImportName: "go.import.test", - Variant: []Shape{ - &StructLike{ - Name: "Branch", - PkgName: "test", - PkgImportName: "go.import.test", - Fields: []*FieldLike{ - { - Name: "L", - Type: &RefName{ - Name: "Tree", - PkgName: "test", - PkgImportName: "go.import.test", - }, - }, - { - Name: "R", - Type: &RefName{ - Name: "Tree", - PkgName: "test", - PkgImportName: "go.import.test", - }, - }, - }, - }, - &StructLike{ - Name: "Leaf", - PkgName: "test", - PkgImportName: "go.import.test", - Fields: []*FieldLike{ - { - Name: "Value", - Type: &RefName{ - Name: "Schema", - PkgName: "schema", - PkgImportName: "github.com/widmogrod/mkunion/x/schema", - }, - }, - }, - }, - }, - }) + tsr.AddUnion(&union) tsr.AddStruct(&StructLike{ Name: "SomeStruct", @@ -62,39 +24,73 @@ func TestTypeScriptSchemaGeneration(t *testing.T) { Fields: nil, }) - err := tsr.WriteToDir("_test/") + err = tsr.WriteToDir("_test/") assert.NoError(t, err) - assert.FileExists(t, "_test/go_import_test.ts") + assert.FileExists(t, "_test/github_com_widmogrod_mkunion_x_shape_testasset.ts") - contents, err := os.ReadFile("_test/go_import_test.ts") + contents, err := os.ReadFile("_test/github_com_widmogrod_mkunion_x_shape_testasset.ts") assert.NoError(t, err) expected := `//generated by mkunion -export type Tree = { +export type Example = { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "testasset.A", + "testasset.A": A +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "testasset.B", + "testasset.B": B +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "testasset.C", + "testasset.C": C +} | { // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "test.Branch", - "test.Branch": Branch + "$type"?: "testasset.D", + "testasset.D": D } | { // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema - "$type"?: "test.Leaf", - "test.Leaf": Leaf + "$type"?: "testasset.E", + "testasset.E": E +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "testasset.F", + "testasset.F": F +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "testasset.H", + "testasset.H": H +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "testasset.I", + "testasset.I": I +} | { + // $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema + "$type"?: "testasset.J", + "testasset.J": J } -export type Branch = { - L?: Tree, - R?: Tree, +export type A = { + Name?: string, } -export type Leaf = { - Value?: schema.Schema, +export type B = { + Age?: number, + A?: A, + T?: time.Time, } -export type SomeStruct = { -} +export type C = string +export type D = number +export type E = number +export type F = boolean +export type H = {[key: string]: Example} +export type I = Example[] +export type J = string[] //eslint-disable-next-line -import * as schema from './github_com_widmogrod_mkunion_x_schema' +import * as time from './time' ` assert.Equal(t, expected, string(contents)) } diff --git a/x/storage/schemaless/storage.go b/x/storage/schemaless/storage.go index a4216142..11634ce3 100644 --- a/x/storage/schemaless/storage.go +++ b/x/storage/schemaless/storage.go @@ -25,6 +25,8 @@ var ( // data records, which is current implementation // index records, which is future implementation // - when two replicas have same aggregator rules, then during replication of logs, index can be reused +// +// go:generate go run ../../../cmd/mkunion/main.go -name RecordDSL type Record[A any] struct { ID string Type string @@ -41,19 +43,13 @@ const ( PolicyOverwriteServerChanges ) +// go:generate go run ../../../cmd/mkunion/main.go -name RecordsDSL type ( UpdateRecords[T any] struct { UpdatingPolicy UpdatingPolicy Saving map[string]T Deleting map[string]T } -) - -func (s UpdateRecords[T]) IsEmpty() bool { - return len(s.Saving) == 0 && len(s.Deleting) == 0 -} - -type ( FindingRecords[T any] struct { RecordType string Where *predicate.WherePredicates @@ -62,7 +58,13 @@ type ( After *Cursor //Before *Cursor } +) + +func (s UpdateRecords[T]) IsEmpty() bool { + return len(s.Saving) == 0 && len(s.Deleting) == 0 +} +type ( SortField struct { Field string Descending bool From 5ba7a50920de5bd170f5e3f66b4c9e035d9e46f0 Mon Sep 17 00:00:00 2001 From: widmogrod Date: Tue, 12 Dec 2023 22:08:48 +0100 Subject: [PATCH 07/14] x/generators shape generates NumberKind --- x/generators/shape_generator.go | 48 +++++++++++++++++++++++++++- x/generators/shape_generator_test.go | 4 ++- x/generators/testutils/tree.go | 2 +- 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/x/generators/shape_generator.go b/x/generators/shape_generator.go index bc811075..e522b698 100644 --- a/x/generators/shape_generator.go +++ b/x/generators/shape_generator.go @@ -132,10 +132,16 @@ func (g *ShapeGenerator) ShapeToString(x shape.Shape, depth int) string { result := &bytes.Buffer{} if shape.IsNamed(x) { fmt.Fprintf(result, "&shape.NumberLike{\n") + g.fprintNumberKind(result, x.Kind, 1) g.fprintNamedFields(result, x.Named, 1) fmt.Fprintf(result, "}") } else { - fmt.Fprintf(result, "&shape.NumberLike{}") + fmt.Fprintf(result, "&shape.NumberLike{") + if x.Kind != nil { + fmt.Fprintf(result, "\n") + g.fprintNumberKind(result, x.Kind, 1) + } + fmt.Fprintf(result, "}") } return g.padLeft(depth, result.String()) @@ -217,6 +223,46 @@ func (g *ShapeGenerator) ShapeToString(x shape.Shape, depth int) string { ) } +func (g *ShapeGenerator) kintToGoName(kind shape.NumberKind) string { + return shape.MustMatchNumberKind( + kind, + func(x *shape.UInt8) string { + return "shape.UInt8{}" + }, + func(x *shape.UInt16) string { + return "shape.UInt16{}" + }, + func(x *shape.UInt32) string { + return "shape.UInt32{}" + }, + func(x *shape.UInt64) string { + return "shape.UInt64{}" + }, + func(x *shape.Int8) string { + return "shape.Int8{}" + }, + func(x *shape.Int16) string { + return "shape.Int16{}" + }, + func(x *shape.Int32) string { + return "shape.Int32{}" + }, + func(x *shape.Int64) string { + return "shape.Int64{}" + }, + func(x *shape.Float32) string { + return "shape.Float32{}" + }, + func(x *shape.Float64) string { + return "shape.Float64{}" + }, + ) +} + +func (g *ShapeGenerator) fprintNumberKind(result *bytes.Buffer, kind shape.NumberKind, depth int) { + fmt.Fprintf(result, strings.Repeat("\t", depth)+"Kind: &%s,\n", g.kintToGoName(kind)) +} + func (g *ShapeGenerator) fprintNamedFields(result *bytes.Buffer, x *shape.Named, depth int) { fmt.Fprintf(result, strings.Repeat("\t", depth)+"Named: &shape.Named{\n") fmt.Fprintf(result, strings.Repeat("\t", depth)+"\tName: %q,\n", x.Name) diff --git a/x/generators/shape_generator_test.go b/x/generators/shape_generator_test.go index 14aa7bbe..d20eb7df 100644 --- a/x/generators/shape_generator_test.go +++ b/x/generators/shape_generator_test.go @@ -87,7 +87,9 @@ func LeafShape() shape.Shape { Fields: []*shape.FieldLike{ { Name: "Value", - Type: &shape.NumberLike{}, + Type: &shape.NumberLike{ + Kind: &shape.Int64{}, + }, }, }, } diff --git a/x/generators/testutils/tree.go b/x/generators/testutils/tree.go index c2eab5bb..76b03879 100644 --- a/x/generators/testutils/tree.go +++ b/x/generators/testutils/tree.go @@ -7,6 +7,6 @@ type ( List []Tree Map map[string]Tree } - Leaf struct{ Value int } + Leaf struct{ Value int64 } K string ) From c4fc02885f46d9693cb2cf5f0251f49a4e7de75f Mon Sep 17 00:00:00 2001 From: widmogrod Date: Tue, 12 Dec 2023 22:09:31 +0100 Subject: [PATCH 08/14] x/generators regenerate types --- x/generators/testutils/tree_tree_gen.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x/generators/testutils/tree_tree_gen.go b/x/generators/testutils/tree_tree_gen.go index 610b9dea..2d2c7f3d 100644 --- a/x/generators/testutils/tree_tree_gen.go +++ b/x/generators/testutils/tree_tree_gen.go @@ -386,7 +386,9 @@ func LeafShape() shape.Shape { Fields: []*shape.FieldLike{ { Name: "Value", - Type: &shape.NumberLike{}, + Type: &shape.NumberLike{ + Kind: &shape.Int64{}, + }, }, }, } From 252787d87f73deefe46726366ab68f4ded62a8a4 Mon Sep 17 00:00:00 2001 From: widmogrod Date: Tue, 12 Dec 2023 22:10:10 +0100 Subject: [PATCH 09/14] my-app: leverage new typescript types --- example/my-app/src/App.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/example/my-app/src/App.tsx b/example/my-app/src/App.tsx index 547c6bbf..1212273f 100644 --- a/example/my-app/src/App.tsx +++ b/example/my-app/src/App.tsx @@ -6,7 +6,6 @@ import {Chat} from "./Chat"; import {GenerateImage, ListWorkflowsFn} from "./workflow/github_com_widmogrod_mkunion_exammple_my-app"; function flowCreate(flow: workflow.Flow) { - console.log("save-flow", flow) return fetch('http://localhost:8080/flow', { method: 'POST', body: JSON.stringify(flow), @@ -431,9 +430,7 @@ function App() { if (data["workflow.Done"].Result) { let result = data["workflow.Done"].Result if ("schema.Binary" in result) { - if (typeof result["schema.Binary"] === "string") { - setImage(result["schema.Binary"]) - } + setImage(result["schema.Binary"]) } } } else if ("workflow.Error" in data) { From 1fa8f4787353d98e673417b726431c8a965a16c9 Mon Sep 17 00:00:00 2001 From: widmogrod Date: Tue, 12 Dec 2023 22:10:50 +0100 Subject: [PATCH 10/14] x/generators fix typo --- x/generators/shape_generator.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x/generators/shape_generator.go b/x/generators/shape_generator.go index e522b698..46165cae 100644 --- a/x/generators/shape_generator.go +++ b/x/generators/shape_generator.go @@ -223,7 +223,7 @@ func (g *ShapeGenerator) ShapeToString(x shape.Shape, depth int) string { ) } -func (g *ShapeGenerator) kintToGoName(kind shape.NumberKind) string { +func (g *ShapeGenerator) kindToGoName(kind shape.NumberKind) string { return shape.MustMatchNumberKind( kind, func(x *shape.UInt8) string { @@ -260,7 +260,7 @@ func (g *ShapeGenerator) kintToGoName(kind shape.NumberKind) string { } func (g *ShapeGenerator) fprintNumberKind(result *bytes.Buffer, kind shape.NumberKind, depth int) { - fmt.Fprintf(result, strings.Repeat("\t", depth)+"Kind: &%s,\n", g.kintToGoName(kind)) + fmt.Fprintf(result, strings.Repeat("\t", depth)+"Kind: &%s,\n", g.kindToGoName(kind)) } func (g *ShapeGenerator) fprintNamedFields(result *bytes.Buffer, x *shape.Named, depth int) { From ec0c75a5dc8b63bf5fbb624a40b5a066a2924288 Mon Sep 17 00:00:00 2001 From: widmogrod Date: Tue, 12 Dec 2023 22:38:46 +0100 Subject: [PATCH 11/14] x/shape prepare generics draft --- x/shape/testasset/type_generics.go | 15 + .../testasset/type_generics_graphdsl_gen.go | 360 ++++++++++++++++++ x/shape/testasset/type_to_json_test.go | 55 +++ 3 files changed, 430 insertions(+) create mode 100644 x/shape/testasset/type_generics.go create mode 100644 x/shape/testasset/type_generics_graphdsl_gen.go diff --git a/x/shape/testasset/type_generics.go b/x/shape/testasset/type_generics.go new file mode 100644 index 00000000..e88f8670 --- /dev/null +++ b/x/shape/testasset/type_generics.go @@ -0,0 +1,15 @@ +package testasset + +//go:generate go run ../../../cmd/mkunion/main.go --name=GraphDSL +type ( + Graph[T any] struct { + Vertices map[string]*Vertex[T] + } + Vertex[T any] struct { + Value T + Edges []*Edge[T] + } + Edge[T any] struct { + Weight float64 + } +) diff --git a/x/shape/testasset/type_generics_graphdsl_gen.go b/x/shape/testasset/type_generics_graphdsl_gen.go new file mode 100644 index 00000000..9d34f172 --- /dev/null +++ b/x/shape/testasset/type_generics_graphdsl_gen.go @@ -0,0 +1,360 @@ +// Code generated by mkunion. DO NOT EDIT. +package testasset + +import "github.com/widmogrod/mkunion/f" +import "github.com/widmogrod/mkunion/x/schema" +import "github.com/widmogrod/mkunion/x/shape" +import "github.com/widmogrod/mkunion/x/shared" +import "encoding/json" +import "fmt" + +//mkunion-extension:visitor + +type GraphDSLVisitor[T1 any] interface { + VisitGraph(v *Graph[T1]) any + VisitVertex(v *Vertex[T1]) any + VisitEdge(v *Edge[T1]) any +} + +type GraphDSL[T1 any] interface { + AcceptGraphDSL(g GraphDSLVisitor[T1]) any +} + +func (r *Graph[T1]) AcceptGraphDSL(v GraphDSLVisitor[T1]) any { return v.VisitGraph(r) } +func (r *Vertex[T1]) AcceptGraphDSL(v GraphDSLVisitor[T1]) any { return v.VisitVertex(r) } +func (r *Edge[T1]) AcceptGraphDSL(v GraphDSLVisitor[T1]) any { return v.VisitEdge(r) } + +var ( + _ GraphDSL[any] = (*Graph[any])(nil) + _ GraphDSL[any] = (*Vertex[any])(nil) + _ GraphDSL[any] = (*Edge[any])(nil) +) + +func MatchGraphDSL[T1, TOut any]( + x GraphDSL[T1], + f1 func(x *Graph[T1]) TOut, + f2 func(x *Vertex[T1]) TOut, + f3 func(x *Edge[T1]) TOut, + df func(x GraphDSL[T1]) TOut, +) TOut { + return f.Match3(x, f1, f2, f3, df) +} + +func MatchGraphDSLR2[T1, TOut1, TOut2 any]( + x GraphDSL[T1], + f1 func(x *Graph[T1]) (TOut1, TOut2), + f2 func(x *Vertex[T1]) (TOut1, TOut2), + f3 func(x *Edge[T1]) (TOut1, TOut2), + df func(x GraphDSL[T1]) (TOut1, TOut2), +) (TOut1, TOut2) { + return f.Match3R2(x, f1, f2, f3, df) +} + +func MustMatchGraphDSL[T1, TOut any]( + x GraphDSL[T1], + f1 func(x *Graph[T1]) TOut, + f2 func(x *Vertex[T1]) TOut, + f3 func(x *Edge[T1]) TOut, +) TOut { + return f.MustMatch3(x, f1, f2, f3) +} + +func MustMatchGraphDSLR0[T1 any]( + x GraphDSL[T1], + f1 func(x *Graph[T1]), + f2 func(x *Vertex[T1]), + f3 func(x *Edge[T1]), +) { + f.MustMatch3R0(x, f1, f2, f3) +} + +func MustMatchGraphDSLR2[T1, TOut1, TOut2 any]( + x GraphDSL[T1], + f1 func(x *Graph[T1]) (TOut1, TOut2), + f2 func(x *Vertex[T1]) (TOut1, TOut2), + f3 func(x *Edge[T1]) (TOut1, TOut2), +) (TOut1, TOut2) { + return f.MustMatch3R2(x, f1, f2, f3) +} + +// mkunion-extension:schema +func init() { + schema.RegisterUnionTypes(GraphDSLSchemaDef[any]()) +} + +func GraphDSLSchemaDef[T1 any]() *schema.UnionVariants[GraphDSL[T1]] { + return schema.MustDefineUnion[GraphDSL[T1]]( + new(Graph[T1]), + new(Vertex[T1]), + new(Edge[T1]), + ) +} + +// mkunion-extension:shape +func GraphDSLShape() shape.Shape { + return &shape.UnionLike{ + Name: "GraphDSL", + PkgName: "testasset", + PkgImportName: "github.com/widmogrod/mkunion/x/shape/testasset", + Variant: []shape.Shape{ + GraphShape(), + VertexShape(), + EdgeShape(), + }, + } +} + +func GraphShape() shape.Shape { + return &shape.StructLike{ + Name: "Graph", + PkgName: "testasset", + PkgImportName: "github.com/widmogrod/mkunion/x/shape/testasset", + Fields: []*shape.FieldLike{ + { + Name: "Vertices", + Type: &shape.MapLike{ + Key: &shape.StringLike{}, + KeyIsPointer: false, + Val: &shape.Any{}, + ValIsPointer: true, + }, + }, + }, + } +} + +func VertexShape() shape.Shape { + return &shape.StructLike{ + Name: "Vertex", + PkgName: "testasset", + PkgImportName: "github.com/widmogrod/mkunion/x/shape/testasset", + Fields: []*shape.FieldLike{ + { + Name: "Value", + Type: &shape.RefName{ + Name: "T", + PkgName: "testasset", + PkgImportName: "github.com/widmogrod/mkunion/x/shape/testasset", + }, + }, + { + Name: "Edges", + Type: &shape.ListLike{ + Element: &shape.Any{}, + ElementIsPointer: true, + }, + }, + }, + } +} + +func EdgeShape() shape.Shape { + return &shape.StructLike{ + Name: "Edge", + PkgName: "testasset", + PkgImportName: "github.com/widmogrod/mkunion/x/shape/testasset", + Fields: []*shape.FieldLike{ + { + Name: "Weight", + Type: &shape.NumberLike{ + Kind: &shape.Float64{}, + }, + }, + }, + } +} + +// mkunion-extension:json +type GraphDSLUnionJSON struct { + Type string `json:"$type,omitempty"` + Graph json.RawMessage `json:"testasset.Graph,omitempty"` + Vertex json.RawMessage `json:"testasset.Vertex,omitempty"` + Edge json.RawMessage `json:"testasset.Edge,omitempty"` +} + +func GraphDSLFromJSON[T1 any](x []byte) (GraphDSL[T1], error) { + var data GraphDSLUnionJSON + err := json.Unmarshal(x, &data) + if err != nil { + return nil, err + } + + switch data.Type { + case "testasset.Graph": + return GraphFromJSON[T1](data.Graph) + case "testasset.Vertex": + return VertexFromJSON[T1](data.Vertex) + case "testasset.Edge": + return EdgeFromJSON[T1](data.Edge) + } + + if data.Graph != nil { + return GraphFromJSON[T1](data.Graph) + } else if data.Vertex != nil { + return VertexFromJSON[T1](data.Vertex) + } else if data.Edge != nil { + return EdgeFromJSON[T1](data.Edge) + } + + return nil, fmt.Errorf("unknown type %s", data.Type) +} + +func GraphDSLToJSON[T1 any](x GraphDSL[T1]) ([]byte, error) { + if x == nil { + return nil, nil + } + return MustMatchGraphDSLR2( + x, + func(x *Graph[T1]) ([]byte, error) { + body, err := GraphToJSON(x) + if err != nil { + return nil, err + } + + return json.Marshal(GraphDSLUnionJSON{ + Type: "testasset.Graph", + Graph: body, + }) + }, + func(x *Vertex[T1]) ([]byte, error) { + body, err := VertexToJSON(x) + if err != nil { + return nil, err + } + + return json.Marshal(GraphDSLUnionJSON{ + Type: "testasset.Vertex", + Vertex: body, + }) + }, + func(x *Edge[T1]) ([]byte, error) { + body, err := EdgeToJSON(x) + if err != nil { + return nil, err + } + + return json.Marshal(GraphDSLUnionJSON{ + Type: "testasset.Edge", + Edge: body, + }) + }, + ) +} + +func GraphFromJSON[T1 any](x []byte) (*Graph[T1], error) { + var result *Graph[T1] = new(Graph[T1]) + // if is Struct + err := shared.JSONParseObject(x, func(key string, value []byte) error { + switch key { + case "Vertices": + return json.Unmarshal(value, &result.Vertices) + } + + return fmt.Errorf("testasset.GraphFromJSON: unknown key %s", key) + }) + + return result, err +} + +func GraphToJSON[T1 any](x *Graph[T1]) ([]byte, error) { + field_Vertices, err := json.Marshal(x.Vertices) + if err != nil { + return nil, err + } + return json.Marshal(map[string]json.RawMessage{ + "Vertices": field_Vertices, + }) +} +func (self *Graph[T1]) MarshalJSON() ([]byte, error) { + return GraphToJSON(self) +} + +func (self *Graph[T1]) UnmarshalJSON(x []byte) error { + n, err := GraphFromJSON[T1](x) + if err != nil { + return err + } + *self = *n + return nil +} + +func VertexFromJSON[T1 any](x []byte) (*Vertex[T1], error) { + var result *Vertex[T1] = new(Vertex[T1]) + // if is Struct + err := shared.JSONParseObject(x, func(key string, value []byte) error { + switch key { + case "Value": + return json.Unmarshal(value, &result.Value) + case "Edges": + return json.Unmarshal(value, &result.Edges) + } + + return fmt.Errorf("testasset.VertexFromJSON: unknown key %s", key) + }) + + return result, err +} + +func VertexToJSON[T1 any](x *Vertex[T1]) ([]byte, error) { + field_Value, err := json.Marshal(x.Value) + if err != nil { + return nil, err + } + field_Edges, err := json.Marshal(x.Edges) + if err != nil { + return nil, err + } + return json.Marshal(map[string]json.RawMessage{ + "Value": field_Value, + "Edges": field_Edges, + }) +} +func (self *Vertex[T1]) MarshalJSON() ([]byte, error) { + return VertexToJSON[T1](self) +} + +func (self *Vertex[T1]) UnmarshalJSON(x []byte) error { + n, err := VertexFromJSON[T1](x) + if err != nil { + return err + } + *self = *n + return nil +} + +func EdgeFromJSON[T1 any](x []byte) (*Edge[T1], error) { + var result *Edge[T1] = new(Edge[T1]) + // if is Struct + err := shared.JSONParseObject(x, func(key string, value []byte) error { + switch key { + case "Weight": + return json.Unmarshal(value, &result.Weight) + } + + return fmt.Errorf("testasset.EdgeFromJSON: unknown key %s", key) + }) + + return result, err +} + +func EdgeToJSON[T1 any](x *Edge[T1]) ([]byte, error) { + field_Weight, err := json.Marshal(x.Weight) + if err != nil { + return nil, err + } + return json.Marshal(map[string]json.RawMessage{ + "Weight": field_Weight, + }) +} +func (self *Edge[T1]) MarshalJSON() ([]byte, error) { + return EdgeToJSON(self) +} + +func (self *Edge[T1]) UnmarshalJSON(x []byte) error { + n, err := EdgeFromJSON[T1](x) + if err != nil { + return err + } + *self = *n + return nil +} diff --git a/x/shape/testasset/type_to_json_test.go b/x/shape/testasset/type_to_json_test.go index 06e19864..6cdba1ec 100644 --- a/x/shape/testasset/type_to_json_test.go +++ b/x/shape/testasset/type_to_json_test.go @@ -96,3 +96,58 @@ func TestOtherToJSON_A(t *testing.T) { }, }, example) } + +func TestGraphDSL(t *testing.T) { + graph := &Graph[int]{} + graph.Vertices = map[string]*Vertex[int]{ + "1": { + Value: 1, + Edges: []*Edge[int]{ + { + Weight: 1.0, + }, + }, + }, + "2": { + Value: 2, + Edges: []*Edge[int]{ + { + Weight: 2.0, + }, + }, + }, + } + + result, err := GraphDSLToJSON[int](graph) + assert.NoError(t, err) + assert.JSONEq(t, `{ + "$type": "testasset.Graph", + "testasset.Graph": { + "Vertices": { + "1": { + "Edges": [ + { + "Weight": 1 + } + ], + "Value": 1 + }, + "2": { + "Edges": [ + { + "Weight": 2 + } + ], + "Value": 2 + } + } + } +}`, string(result)) + + t.Log(string(result)) + + example, err := GraphDSLFromJSON[int](result) + assert.NoError(t, err) + assert.Equal(t, graph, example) + +} From 4f63e8310ef30bbb45abef5440ff9b5bb42bd2bb Mon Sep 17 00:00:00 2001 From: widmogrod Date: Tue, 12 Dec 2023 22:55:48 +0100 Subject: [PATCH 12/14] disable generation of generics --- x/shape/testasset/type_generics.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/shape/testasset/type_generics.go b/x/shape/testasset/type_generics.go index e88f8670..8af500c8 100644 --- a/x/shape/testasset/type_generics.go +++ b/x/shape/testasset/type_generics.go @@ -1,6 +1,6 @@ package testasset -//go:generate go run ../../../cmd/mkunion/main.go --name=GraphDSL +// go:generate go run ../../../cmd/mkunion/main.go --name=GraphDSL type ( Graph[T any] struct { Vertices map[string]*Vertex[T] From 82c48575bf43881829096cc927bf25e2c0c36648 Mon Sep 17 00:00:00 2001 From: widmogrod Date: Tue, 12 Dec 2023 22:56:17 +0100 Subject: [PATCH 13/14] my-app: test how struct aliasing would work as intermediary --- example/my-app/server.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/example/my-app/server.go b/example/my-app/server.go index b8098ea7..1962c9df 100644 --- a/example/my-app/server.go +++ b/example/my-app/server.go @@ -115,6 +115,12 @@ type GenerateImage struct { Height int `desc:"height of image as int between 50 and 500"` } +//go:generate mkunion -name=QueryDSL +type ( + FindState schemaless.FindingRecords[schemaless.Record[workflow.State]] + FindFlow schemaless.FindingRecords[schemaless.Record[workflow.Flow]] +) + func main() { schema.RegisterUnionTypes(ChatResultSchemaDef()) schema.RegisterUnionTypes(ChatCMDSchemaDef()) From f6e37794727a51f19de3516367e5406abea8b938 Mon Sep 17 00:00:00 2001 From: widmogrod Date: Tue, 12 Dec 2023 22:59:01 +0100 Subject: [PATCH 14/14] x/shape commit number kind generated file --- x/shape/shape_numberkind_gen.go | 733 ++++++++++++++++++++++++++++++++ 1 file changed, 733 insertions(+) create mode 100644 x/shape/shape_numberkind_gen.go diff --git a/x/shape/shape_numberkind_gen.go b/x/shape/shape_numberkind_gen.go new file mode 100644 index 00000000..35543139 --- /dev/null +++ b/x/shape/shape_numberkind_gen.go @@ -0,0 +1,733 @@ +// Code generated by mkunion. DO NOT EDIT. +package shape + +import "github.com/widmogrod/mkunion/f" +import "github.com/widmogrod/mkunion/x/schema" +import "github.com/widmogrod/mkunion/x/shared" +import "encoding/json" +import "fmt" + +//mkunion-extension:visitor + +type NumberKindVisitor interface { + VisitUInt8(v *UInt8) any + VisitUInt16(v *UInt16) any + VisitUInt32(v *UInt32) any + VisitUInt64(v *UInt64) any + VisitInt8(v *Int8) any + VisitInt16(v *Int16) any + VisitInt32(v *Int32) any + VisitInt64(v *Int64) any + VisitFloat32(v *Float32) any + VisitFloat64(v *Float64) any +} + +type NumberKind interface { + AcceptNumberKind(g NumberKindVisitor) any +} + +func (r *UInt8) AcceptNumberKind(v NumberKindVisitor) any { return v.VisitUInt8(r) } +func (r *UInt16) AcceptNumberKind(v NumberKindVisitor) any { return v.VisitUInt16(r) } +func (r *UInt32) AcceptNumberKind(v NumberKindVisitor) any { return v.VisitUInt32(r) } +func (r *UInt64) AcceptNumberKind(v NumberKindVisitor) any { return v.VisitUInt64(r) } +func (r *Int8) AcceptNumberKind(v NumberKindVisitor) any { return v.VisitInt8(r) } +func (r *Int16) AcceptNumberKind(v NumberKindVisitor) any { return v.VisitInt16(r) } +func (r *Int32) AcceptNumberKind(v NumberKindVisitor) any { return v.VisitInt32(r) } +func (r *Int64) AcceptNumberKind(v NumberKindVisitor) any { return v.VisitInt64(r) } +func (r *Float32) AcceptNumberKind(v NumberKindVisitor) any { return v.VisitFloat32(r) } +func (r *Float64) AcceptNumberKind(v NumberKindVisitor) any { return v.VisitFloat64(r) } + +var ( + _ NumberKind = (*UInt8)(nil) + _ NumberKind = (*UInt16)(nil) + _ NumberKind = (*UInt32)(nil) + _ NumberKind = (*UInt64)(nil) + _ NumberKind = (*Int8)(nil) + _ NumberKind = (*Int16)(nil) + _ NumberKind = (*Int32)(nil) + _ NumberKind = (*Int64)(nil) + _ NumberKind = (*Float32)(nil) + _ NumberKind = (*Float64)(nil) +) + +func MatchNumberKind[TOut any]( + x NumberKind, + f1 func(x *UInt8) TOut, + f2 func(x *UInt16) TOut, + f3 func(x *UInt32) TOut, + f4 func(x *UInt64) TOut, + f5 func(x *Int8) TOut, + f6 func(x *Int16) TOut, + f7 func(x *Int32) TOut, + f8 func(x *Int64) TOut, + f9 func(x *Float32) TOut, + f10 func(x *Float64) TOut, + df func(x NumberKind) TOut, +) TOut { + return f.Match10(x, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, df) +} + +func MatchNumberKindR2[TOut1, TOut2 any]( + x NumberKind, + f1 func(x *UInt8) (TOut1, TOut2), + f2 func(x *UInt16) (TOut1, TOut2), + f3 func(x *UInt32) (TOut1, TOut2), + f4 func(x *UInt64) (TOut1, TOut2), + f5 func(x *Int8) (TOut1, TOut2), + f6 func(x *Int16) (TOut1, TOut2), + f7 func(x *Int32) (TOut1, TOut2), + f8 func(x *Int64) (TOut1, TOut2), + f9 func(x *Float32) (TOut1, TOut2), + f10 func(x *Float64) (TOut1, TOut2), + df func(x NumberKind) (TOut1, TOut2), +) (TOut1, TOut2) { + return f.Match10R2(x, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, df) +} + +func MustMatchNumberKind[TOut any]( + x NumberKind, + f1 func(x *UInt8) TOut, + f2 func(x *UInt16) TOut, + f3 func(x *UInt32) TOut, + f4 func(x *UInt64) TOut, + f5 func(x *Int8) TOut, + f6 func(x *Int16) TOut, + f7 func(x *Int32) TOut, + f8 func(x *Int64) TOut, + f9 func(x *Float32) TOut, + f10 func(x *Float64) TOut, +) TOut { + return f.MustMatch10(x, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10) +} + +func MustMatchNumberKindR0( + x NumberKind, + f1 func(x *UInt8), + f2 func(x *UInt16), + f3 func(x *UInt32), + f4 func(x *UInt64), + f5 func(x *Int8), + f6 func(x *Int16), + f7 func(x *Int32), + f8 func(x *Int64), + f9 func(x *Float32), + f10 func(x *Float64), +) { + f.MustMatch10R0(x, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10) +} + +func MustMatchNumberKindR2[TOut1, TOut2 any]( + x NumberKind, + f1 func(x *UInt8) (TOut1, TOut2), + f2 func(x *UInt16) (TOut1, TOut2), + f3 func(x *UInt32) (TOut1, TOut2), + f4 func(x *UInt64) (TOut1, TOut2), + f5 func(x *Int8) (TOut1, TOut2), + f6 func(x *Int16) (TOut1, TOut2), + f7 func(x *Int32) (TOut1, TOut2), + f8 func(x *Int64) (TOut1, TOut2), + f9 func(x *Float32) (TOut1, TOut2), + f10 func(x *Float64) (TOut1, TOut2), +) (TOut1, TOut2) { + return f.MustMatch10R2(x, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10) +} + +// mkunion-extension:schema +func init() { + schema.RegisterUnionTypes(NumberKindSchemaDef()) +} + +func NumberKindSchemaDef() *schema.UnionVariants[NumberKind] { + return schema.MustDefineUnion[NumberKind]( + new(UInt8), + new(UInt16), + new(UInt32), + new(UInt64), + new(Int8), + new(Int16), + new(Int32), + new(Int64), + new(Float32), + new(Float64), + ) +} + +// mkunion-extension:shape +func NumberKindShape() Shape { + return &UnionLike{ + Name: "NumberKind", + PkgName: "shape", + PkgImportName: "github.com/widmogrod/mkunion/x/shape", + Variant: []Shape{ + UInt8Shape(), + UInt16Shape(), + UInt32Shape(), + UInt64Shape(), + Int8Shape(), + Int16Shape(), + Int32Shape(), + Int64Shape(), + Float32Shape(), + Float64Shape(), + }, + } +} + +func UInt8Shape() Shape { + return &StructLike{ + Name: "UInt8", + PkgName: "shape", + PkgImportName: "github.com/widmogrod/mkunion/x/shape", + } +} + +func UInt16Shape() Shape { + return &StructLike{ + Name: "UInt16", + PkgName: "shape", + PkgImportName: "github.com/widmogrod/mkunion/x/shape", + } +} + +func UInt32Shape() Shape { + return &StructLike{ + Name: "UInt32", + PkgName: "shape", + PkgImportName: "github.com/widmogrod/mkunion/x/shape", + } +} + +func UInt64Shape() Shape { + return &StructLike{ + Name: "UInt64", + PkgName: "shape", + PkgImportName: "github.com/widmogrod/mkunion/x/shape", + } +} + +func Int8Shape() Shape { + return &StructLike{ + Name: "Int8", + PkgName: "shape", + PkgImportName: "github.com/widmogrod/mkunion/x/shape", + } +} + +func Int16Shape() Shape { + return &StructLike{ + Name: "Int16", + PkgName: "shape", + PkgImportName: "github.com/widmogrod/mkunion/x/shape", + } +} + +func Int32Shape() Shape { + return &StructLike{ + Name: "Int32", + PkgName: "shape", + PkgImportName: "github.com/widmogrod/mkunion/x/shape", + } +} + +func Int64Shape() Shape { + return &StructLike{ + Name: "Int64", + PkgName: "shape", + PkgImportName: "github.com/widmogrod/mkunion/x/shape", + } +} + +func Float32Shape() Shape { + return &StructLike{ + Name: "Float32", + PkgName: "shape", + PkgImportName: "github.com/widmogrod/mkunion/x/shape", + } +} + +func Float64Shape() Shape { + return &StructLike{ + Name: "Float64", + PkgName: "shape", + PkgImportName: "github.com/widmogrod/mkunion/x/shape", + } +} + +// mkunion-extension:json +type NumberKindUnionJSON struct { + Type string `json:"$type,omitempty"` + UInt8 json.RawMessage `json:"shape.UInt8,omitempty"` + UInt16 json.RawMessage `json:"shape.UInt16,omitempty"` + UInt32 json.RawMessage `json:"shape.UInt32,omitempty"` + UInt64 json.RawMessage `json:"shape.UInt64,omitempty"` + Int8 json.RawMessage `json:"shape.Int8,omitempty"` + Int16 json.RawMessage `json:"shape.Int16,omitempty"` + Int32 json.RawMessage `json:"shape.Int32,omitempty"` + Int64 json.RawMessage `json:"shape.Int64,omitempty"` + Float32 json.RawMessage `json:"shape.Float32,omitempty"` + Float64 json.RawMessage `json:"shape.Float64,omitempty"` +} + +func NumberKindFromJSON(x []byte) (NumberKind, error) { + var data NumberKindUnionJSON + err := json.Unmarshal(x, &data) + if err != nil { + return nil, err + } + + switch data.Type { + case "shape.UInt8": + return UInt8FromJSON(data.UInt8) + case "shape.UInt16": + return UInt16FromJSON(data.UInt16) + case "shape.UInt32": + return UInt32FromJSON(data.UInt32) + case "shape.UInt64": + return UInt64FromJSON(data.UInt64) + case "shape.Int8": + return Int8FromJSON(data.Int8) + case "shape.Int16": + return Int16FromJSON(data.Int16) + case "shape.Int32": + return Int32FromJSON(data.Int32) + case "shape.Int64": + return Int64FromJSON(data.Int64) + case "shape.Float32": + return Float32FromJSON(data.Float32) + case "shape.Float64": + return Float64FromJSON(data.Float64) + } + + if data.UInt8 != nil { + return UInt8FromJSON(data.UInt8) + } else if data.UInt16 != nil { + return UInt16FromJSON(data.UInt16) + } else if data.UInt32 != nil { + return UInt32FromJSON(data.UInt32) + } else if data.UInt64 != nil { + return UInt64FromJSON(data.UInt64) + } else if data.Int8 != nil { + return Int8FromJSON(data.Int8) + } else if data.Int16 != nil { + return Int16FromJSON(data.Int16) + } else if data.Int32 != nil { + return Int32FromJSON(data.Int32) + } else if data.Int64 != nil { + return Int64FromJSON(data.Int64) + } else if data.Float32 != nil { + return Float32FromJSON(data.Float32) + } else if data.Float64 != nil { + return Float64FromJSON(data.Float64) + } + + return nil, fmt.Errorf("unknown type %s", data.Type) +} + +func NumberKindToJSON(x NumberKind) ([]byte, error) { + if x == nil { + return nil, nil + } + return MustMatchNumberKindR2( + x, + func(x *UInt8) ([]byte, error) { + body, err := UInt8ToJSON(x) + if err != nil { + return nil, err + } + + return json.Marshal(NumberKindUnionJSON{ + Type: "shape.UInt8", + UInt8: body, + }) + }, + func(x *UInt16) ([]byte, error) { + body, err := UInt16ToJSON(x) + if err != nil { + return nil, err + } + + return json.Marshal(NumberKindUnionJSON{ + Type: "shape.UInt16", + UInt16: body, + }) + }, + func(x *UInt32) ([]byte, error) { + body, err := UInt32ToJSON(x) + if err != nil { + return nil, err + } + + return json.Marshal(NumberKindUnionJSON{ + Type: "shape.UInt32", + UInt32: body, + }) + }, + func(x *UInt64) ([]byte, error) { + body, err := UInt64ToJSON(x) + if err != nil { + return nil, err + } + + return json.Marshal(NumberKindUnionJSON{ + Type: "shape.UInt64", + UInt64: body, + }) + }, + func(x *Int8) ([]byte, error) { + body, err := Int8ToJSON(x) + if err != nil { + return nil, err + } + + return json.Marshal(NumberKindUnionJSON{ + Type: "shape.Int8", + Int8: body, + }) + }, + func(x *Int16) ([]byte, error) { + body, err := Int16ToJSON(x) + if err != nil { + return nil, err + } + + return json.Marshal(NumberKindUnionJSON{ + Type: "shape.Int16", + Int16: body, + }) + }, + func(x *Int32) ([]byte, error) { + body, err := Int32ToJSON(x) + if err != nil { + return nil, err + } + + return json.Marshal(NumberKindUnionJSON{ + Type: "shape.Int32", + Int32: body, + }) + }, + func(x *Int64) ([]byte, error) { + body, err := Int64ToJSON(x) + if err != nil { + return nil, err + } + + return json.Marshal(NumberKindUnionJSON{ + Type: "shape.Int64", + Int64: body, + }) + }, + func(x *Float32) ([]byte, error) { + body, err := Float32ToJSON(x) + if err != nil { + return nil, err + } + + return json.Marshal(NumberKindUnionJSON{ + Type: "shape.Float32", + Float32: body, + }) + }, + func(x *Float64) ([]byte, error) { + body, err := Float64ToJSON(x) + if err != nil { + return nil, err + } + + return json.Marshal(NumberKindUnionJSON{ + Type: "shape.Float64", + Float64: body, + }) + }, + ) +} + +func UInt8FromJSON(x []byte) (*UInt8, error) { + var result *UInt8 = new(UInt8) + // if is Struct + err := shared.JSONParseObject(x, func(key string, value []byte) error { + switch key { + } + + return fmt.Errorf("shape.UInt8FromJSON: unknown key %s", key) + }) + + return result, err +} + +func UInt8ToJSON(x *UInt8) ([]byte, error) { + return json.Marshal(map[string]json.RawMessage{}) +} +func (self *UInt8) MarshalJSON() ([]byte, error) { + return UInt8ToJSON(self) +} + +func (self *UInt8) UnmarshalJSON(x []byte) error { + n, err := UInt8FromJSON(x) + if err != nil { + return err + } + *self = *n + return nil +} + +func UInt16FromJSON(x []byte) (*UInt16, error) { + var result *UInt16 = new(UInt16) + // if is Struct + err := shared.JSONParseObject(x, func(key string, value []byte) error { + switch key { + } + + return fmt.Errorf("shape.UInt16FromJSON: unknown key %s", key) + }) + + return result, err +} + +func UInt16ToJSON(x *UInt16) ([]byte, error) { + return json.Marshal(map[string]json.RawMessage{}) +} +func (self *UInt16) MarshalJSON() ([]byte, error) { + return UInt16ToJSON(self) +} + +func (self *UInt16) UnmarshalJSON(x []byte) error { + n, err := UInt16FromJSON(x) + if err != nil { + return err + } + *self = *n + return nil +} + +func UInt32FromJSON(x []byte) (*UInt32, error) { + var result *UInt32 = new(UInt32) + // if is Struct + err := shared.JSONParseObject(x, func(key string, value []byte) error { + switch key { + } + + return fmt.Errorf("shape.UInt32FromJSON: unknown key %s", key) + }) + + return result, err +} + +func UInt32ToJSON(x *UInt32) ([]byte, error) { + return json.Marshal(map[string]json.RawMessage{}) +} +func (self *UInt32) MarshalJSON() ([]byte, error) { + return UInt32ToJSON(self) +} + +func (self *UInt32) UnmarshalJSON(x []byte) error { + n, err := UInt32FromJSON(x) + if err != nil { + return err + } + *self = *n + return nil +} + +func UInt64FromJSON(x []byte) (*UInt64, error) { + var result *UInt64 = new(UInt64) + // if is Struct + err := shared.JSONParseObject(x, func(key string, value []byte) error { + switch key { + } + + return fmt.Errorf("shape.UInt64FromJSON: unknown key %s", key) + }) + + return result, err +} + +func UInt64ToJSON(x *UInt64) ([]byte, error) { + return json.Marshal(map[string]json.RawMessage{}) +} +func (self *UInt64) MarshalJSON() ([]byte, error) { + return UInt64ToJSON(self) +} + +func (self *UInt64) UnmarshalJSON(x []byte) error { + n, err := UInt64FromJSON(x) + if err != nil { + return err + } + *self = *n + return nil +} + +func Int8FromJSON(x []byte) (*Int8, error) { + var result *Int8 = new(Int8) + // if is Struct + err := shared.JSONParseObject(x, func(key string, value []byte) error { + switch key { + } + + return fmt.Errorf("shape.Int8FromJSON: unknown key %s", key) + }) + + return result, err +} + +func Int8ToJSON(x *Int8) ([]byte, error) { + return json.Marshal(map[string]json.RawMessage{}) +} +func (self *Int8) MarshalJSON() ([]byte, error) { + return Int8ToJSON(self) +} + +func (self *Int8) UnmarshalJSON(x []byte) error { + n, err := Int8FromJSON(x) + if err != nil { + return err + } + *self = *n + return nil +} + +func Int16FromJSON(x []byte) (*Int16, error) { + var result *Int16 = new(Int16) + // if is Struct + err := shared.JSONParseObject(x, func(key string, value []byte) error { + switch key { + } + + return fmt.Errorf("shape.Int16FromJSON: unknown key %s", key) + }) + + return result, err +} + +func Int16ToJSON(x *Int16) ([]byte, error) { + return json.Marshal(map[string]json.RawMessage{}) +} +func (self *Int16) MarshalJSON() ([]byte, error) { + return Int16ToJSON(self) +} + +func (self *Int16) UnmarshalJSON(x []byte) error { + n, err := Int16FromJSON(x) + if err != nil { + return err + } + *self = *n + return nil +} + +func Int32FromJSON(x []byte) (*Int32, error) { + var result *Int32 = new(Int32) + // if is Struct + err := shared.JSONParseObject(x, func(key string, value []byte) error { + switch key { + } + + return fmt.Errorf("shape.Int32FromJSON: unknown key %s", key) + }) + + return result, err +} + +func Int32ToJSON(x *Int32) ([]byte, error) { + return json.Marshal(map[string]json.RawMessage{}) +} +func (self *Int32) MarshalJSON() ([]byte, error) { + return Int32ToJSON(self) +} + +func (self *Int32) UnmarshalJSON(x []byte) error { + n, err := Int32FromJSON(x) + if err != nil { + return err + } + *self = *n + return nil +} + +func Int64FromJSON(x []byte) (*Int64, error) { + var result *Int64 = new(Int64) + // if is Struct + err := shared.JSONParseObject(x, func(key string, value []byte) error { + switch key { + } + + return fmt.Errorf("shape.Int64FromJSON: unknown key %s", key) + }) + + return result, err +} + +func Int64ToJSON(x *Int64) ([]byte, error) { + return json.Marshal(map[string]json.RawMessage{}) +} +func (self *Int64) MarshalJSON() ([]byte, error) { + return Int64ToJSON(self) +} + +func (self *Int64) UnmarshalJSON(x []byte) error { + n, err := Int64FromJSON(x) + if err != nil { + return err + } + *self = *n + return nil +} + +func Float32FromJSON(x []byte) (*Float32, error) { + var result *Float32 = new(Float32) + // if is Struct + err := shared.JSONParseObject(x, func(key string, value []byte) error { + switch key { + } + + return fmt.Errorf("shape.Float32FromJSON: unknown key %s", key) + }) + + return result, err +} + +func Float32ToJSON(x *Float32) ([]byte, error) { + return json.Marshal(map[string]json.RawMessage{}) +} +func (self *Float32) MarshalJSON() ([]byte, error) { + return Float32ToJSON(self) +} + +func (self *Float32) UnmarshalJSON(x []byte) error { + n, err := Float32FromJSON(x) + if err != nil { + return err + } + *self = *n + return nil +} + +func Float64FromJSON(x []byte) (*Float64, error) { + var result *Float64 = new(Float64) + // if is Struct + err := shared.JSONParseObject(x, func(key string, value []byte) error { + switch key { + } + + return fmt.Errorf("shape.Float64FromJSON: unknown key %s", key) + }) + + return result, err +} + +func Float64ToJSON(x *Float64) ([]byte, error) { + return json.Marshal(map[string]json.RawMessage{}) +} +func (self *Float64) MarshalJSON() ([]byte, error) { + return Float64ToJSON(self) +} + +func (self *Float64) UnmarshalJSON(x []byte) error { + n, err := Float64FromJSON(x) + if err != nil { + return err + } + *self = *n + return nil +}