From de320771c53a702123605f76b04be7f41b1e1a0d Mon Sep 17 00:00:00 2001 From: Geoffrey Ragot Date: Fri, 25 Oct 2024 17:50:20 +0200 Subject: [PATCH] feat: add generator tool --- docs/api/README.md | 2 + internal/api/v2/controllers_ledgers_create.go | 3 +- internal/api/v2/errors.go | 1 + openapi.yaml | 4 +- openapi/v2.yaml | 4 +- pkg/client/.speakeasy/gen.lock | 6 +- pkg/client/.speakeasy/gen.yaml | 2 +- .../docs/models/components/v2errorsenum.md | 37 +++--- .../components/v2posttransactionscript.md | 2 +- pkg/client/docs/sdks/v2/README.md | 4 +- pkg/client/formance.go | 4 +- pkg/client/models/components/v2errorsenum.go | 35 +++--- .../models/components/v2posttransaction.go | 6 +- test/e2e/api_transactions_create_test.go | 12 +- test/performance/benchmark_test.go | 6 +- tools/generator/cmd/root.go | 119 ++++++++++++++++++ tools/generator/examples/example1.js | 26 ++++ tools/generator/main.go | 7 ++ 18 files changed, 220 insertions(+), 60 deletions(-) create mode 100644 tools/generator/cmd/root.go create mode 100644 tools/generator/examples/example1.js create mode 100644 tools/generator/main.go diff --git a/docs/api/README.md b/docs/api/README.md index eb3ee14a7..51a790cdc 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -2636,6 +2636,7 @@ Authorization ( Scopes: ledger:write ) |script|object|false|none|none| |» plain|string|true|none|none| |» vars|object|false|none|none| +|»» **additionalProperties**|string|false|none|none| |reference|string|false|none|none| |metadata|[V2Metadata](#schemav2metadata)|true|none|none| @@ -3191,6 +3192,7 @@ Authorization ( Scopes: ledger:write ) |*anonymous*|BULK_SIZE_EXCEEDED| |*anonymous*|INTERPRETER_PARSE| |*anonymous*|INTERPRETER_RUNTIME| +|*anonymous*|LEDGER_ALREADY_EXISTS|

V2LedgerInfoResponse

diff --git a/internal/api/v2/controllers_ledgers_create.go b/internal/api/v2/controllers_ledgers_create.go index ace33cbc0..488b6d1d9 100644 --- a/internal/api/v2/controllers_ledgers_create.go +++ b/internal/api/v2/controllers_ledgers_create.go @@ -34,10 +34,11 @@ func createLedger(systemController system.Controller) http.HandlerFunc { if err := systemController.CreateLedger(r.Context(), chi.URLParam(r, "ledger"), configuration); err != nil { switch { case errors.Is(err, system.ErrInvalidLedgerConfiguration{}) || - errors.Is(err, system.ErrLedgerAlreadyExists) || errors.Is(err, ledger.ErrInvalidLedgerName{}) || errors.Is(err, ledger.ErrInvalidBucketName{}): api.BadRequest(w, ErrValidation, err) + case errors.Is(err, system.ErrLedgerAlreadyExists): + api.BadRequest(w, ErrLedgerAlreadyExists, err) default: common.HandleCommonErrors(w, r, err) } diff --git a/internal/api/v2/errors.go b/internal/api/v2/errors.go index 7306c6019..438a1e9ad 100644 --- a/internal/api/v2/errors.go +++ b/internal/api/v2/errors.go @@ -9,6 +9,7 @@ const ( ErrCompilationFailed = "COMPILATION_FAILED" ErrMetadataOverride = "METADATA_OVERRIDE" ErrBulkSizeExceeded = "BULK_SIZE_EXCEEDED" + ErrLedgerAlreadyExists = "LEDGER_ALREADY_EXISTS" ErrInterpreterParse = "INTERPRETER_PARSE" ErrInterpreterRuntime = "INTERPRETER_RUNTIME" diff --git a/openapi.yaml b/openapi.yaml index 2766718c9..13c7ef9ea 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -3296,7 +3296,8 @@ components: vars: type: object properties: {} - additionalProperties: true + additionalProperties: + type: string example: user: users:042 required: @@ -3455,6 +3456,7 @@ components: - BULK_SIZE_EXCEEDED - INTERPRETER_PARSE - INTERPRETER_RUNTIME + - LEDGER_ALREADY_EXISTS example: VALIDATION V2LedgerInfoResponse: type: object diff --git a/openapi/v2.yaml b/openapi/v2.yaml index cb4b4f02f..77f31b9d8 100644 --- a/openapi/v2.yaml +++ b/openapi/v2.yaml @@ -1565,7 +1565,8 @@ components: vars: type: object properties: {} - additionalProperties: true + additionalProperties: + type: string example: user: users:042 required: @@ -1725,6 +1726,7 @@ components: - BULK_SIZE_EXCEEDED - INTERPRETER_PARSE - INTERPRETER_RUNTIME + - LEDGER_ALREADY_EXISTS example: VALIDATION V2LedgerInfoResponse: type: object diff --git a/pkg/client/.speakeasy/gen.lock b/pkg/client/.speakeasy/gen.lock index 3bb1f546f..6ee5c6fe8 100644 --- a/pkg/client/.speakeasy/gen.lock +++ b/pkg/client/.speakeasy/gen.lock @@ -1,12 +1,12 @@ lockVersion: 2.0.0 id: a9ac79e1-e429-4ee3-96c4-ec973f19bec3 management: - docChecksum: db305ef0d86f319f00b70be811eefeea + docChecksum: 169efa4fe3c5d6561f06920598d20df4 docVersion: v1 speakeasyVersion: 1.351.0 generationVersion: 2.384.1 - releaseVersion: 0.4.18 - configChecksum: 37cc61b909fcb3fb116f1cd48c1fc99a + releaseVersion: 0.4.20 + configChecksum: e66e70c75590218ba585d230919d03e3 features: go: additionalDependencies: 0.1.0 diff --git a/pkg/client/.speakeasy/gen.yaml b/pkg/client/.speakeasy/gen.yaml index c007341f1..2718b0b1c 100644 --- a/pkg/client/.speakeasy/gen.yaml +++ b/pkg/client/.speakeasy/gen.yaml @@ -15,7 +15,7 @@ generation: auth: oAuth2ClientCredentialsEnabled: true go: - version: 0.4.18 + version: 0.4.20 additionalDependencies: {} allowUnknownFieldsInWeakUnions: false clientServerStatusCodesAsErrors: true diff --git a/pkg/client/docs/models/components/v2errorsenum.md b/pkg/client/docs/models/components/v2errorsenum.md index 35dc2f95a..f1757ffab 100644 --- a/pkg/client/docs/models/components/v2errorsenum.md +++ b/pkg/client/docs/models/components/v2errorsenum.md @@ -3,21 +3,22 @@ ## Values -| Name | Value | -| -------------------------------- | -------------------------------- | -| `V2ErrorsEnumInternal` | INTERNAL | -| `V2ErrorsEnumInsufficientFund` | INSUFFICIENT_FUND | -| `V2ErrorsEnumValidation` | VALIDATION | -| `V2ErrorsEnumConflict` | CONFLICT | -| `V2ErrorsEnumCompilationFailed` | COMPILATION_FAILED | -| `V2ErrorsEnumMetadataOverride` | METADATA_OVERRIDE | -| `V2ErrorsEnumNotFound` | NOT_FOUND | -| `V2ErrorsEnumRevertOccurring` | REVERT_OCCURRING | -| `V2ErrorsEnumAlreadyRevert` | ALREADY_REVERT | -| `V2ErrorsEnumNoPostings` | NO_POSTINGS | -| `V2ErrorsEnumLedgerNotFound` | LEDGER_NOT_FOUND | -| `V2ErrorsEnumImport` | IMPORT | -| `V2ErrorsEnumTimeout` | TIMEOUT | -| `V2ErrorsEnumBulkSizeExceeded` | BULK_SIZE_EXCEEDED | -| `V2ErrorsEnumInterpreterParse` | INTERPRETER_PARSE | -| `V2ErrorsEnumInterpreterRuntime` | INTERPRETER_RUNTIME | \ No newline at end of file +| Name | Value | +| --------------------------------- | --------------------------------- | +| `V2ErrorsEnumInternal` | INTERNAL | +| `V2ErrorsEnumInsufficientFund` | INSUFFICIENT_FUND | +| `V2ErrorsEnumValidation` | VALIDATION | +| `V2ErrorsEnumConflict` | CONFLICT | +| `V2ErrorsEnumCompilationFailed` | COMPILATION_FAILED | +| `V2ErrorsEnumMetadataOverride` | METADATA_OVERRIDE | +| `V2ErrorsEnumNotFound` | NOT_FOUND | +| `V2ErrorsEnumRevertOccurring` | REVERT_OCCURRING | +| `V2ErrorsEnumAlreadyRevert` | ALREADY_REVERT | +| `V2ErrorsEnumNoPostings` | NO_POSTINGS | +| `V2ErrorsEnumLedgerNotFound` | LEDGER_NOT_FOUND | +| `V2ErrorsEnumImport` | IMPORT | +| `V2ErrorsEnumTimeout` | TIMEOUT | +| `V2ErrorsEnumBulkSizeExceeded` | BULK_SIZE_EXCEEDED | +| `V2ErrorsEnumInterpreterParse` | INTERPRETER_PARSE | +| `V2ErrorsEnumInterpreterRuntime` | INTERPRETER_RUNTIME | +| `V2ErrorsEnumLedgerAlreadyExists` | LEDGER_ALREADY_EXISTS | \ No newline at end of file diff --git a/pkg/client/docs/models/components/v2posttransactionscript.md b/pkg/client/docs/models/components/v2posttransactionscript.md index 657c7dd0e..b92c259b4 100644 --- a/pkg/client/docs/models/components/v2posttransactionscript.md +++ b/pkg/client/docs/models/components/v2posttransactionscript.md @@ -6,4 +6,4 @@ | Field | Type | Required | Description | Example | | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | | `Plain` | *string* | :heavy_check_mark: | N/A | vars {
account $user
}
send [COIN 10] (
source = @world
destination = $user
)
| -| `Vars` | map[string]*any* | :heavy_minus_sign: | N/A | {
"user": "users:042"
} | \ No newline at end of file +| `Vars` | map[string]*string* | :heavy_minus_sign: | N/A | {
"user": "users:042"
} | \ No newline at end of file diff --git a/pkg/client/docs/sdks/v2/README.md b/pkg/client/docs/sdks/v2/README.md index 03958d3b0..9292a94ed 100644 --- a/pkg/client/docs/sdks/v2/README.md +++ b/pkg/client/docs/sdks/v2/README.md @@ -418,7 +418,7 @@ func main() { destination = $user ) ", - Vars: map[string]any{ + Vars: map[string]string{ "user": "users:042", }, }, @@ -959,7 +959,7 @@ func main() { destination = $user ) ", - Vars: map[string]any{ + Vars: map[string]string{ "user": "users:042", }, }, diff --git a/pkg/client/formance.go b/pkg/client/formance.go index 66b87bf4e..2d2842cb3 100644 --- a/pkg/client/formance.go +++ b/pkg/client/formance.go @@ -143,9 +143,9 @@ func New(opts ...SDKOption) *Formance { sdkConfiguration: sdkConfiguration{ Language: "go", OpenAPIDocVersion: "v1", - SDKVersion: "0.4.18", + SDKVersion: "0.4.20", GenVersion: "2.384.1", - UserAgent: "speakeasy-sdk/go 0.4.18 2.384.1 v1 github.com/formancehq/ledger/pkg/client", + UserAgent: "speakeasy-sdk/go 0.4.20 2.384.1 v1 github.com/formancehq/ledger/pkg/client", Hooks: hooks.New(), }, } diff --git a/pkg/client/models/components/v2errorsenum.go b/pkg/client/models/components/v2errorsenum.go index 862ca3c52..8bbc25e7a 100644 --- a/pkg/client/models/components/v2errorsenum.go +++ b/pkg/client/models/components/v2errorsenum.go @@ -10,22 +10,23 @@ import ( type V2ErrorsEnum string const ( - V2ErrorsEnumInternal V2ErrorsEnum = "INTERNAL" - V2ErrorsEnumInsufficientFund V2ErrorsEnum = "INSUFFICIENT_FUND" - V2ErrorsEnumValidation V2ErrorsEnum = "VALIDATION" - V2ErrorsEnumConflict V2ErrorsEnum = "CONFLICT" - V2ErrorsEnumCompilationFailed V2ErrorsEnum = "COMPILATION_FAILED" - V2ErrorsEnumMetadataOverride V2ErrorsEnum = "METADATA_OVERRIDE" - V2ErrorsEnumNotFound V2ErrorsEnum = "NOT_FOUND" - V2ErrorsEnumRevertOccurring V2ErrorsEnum = "REVERT_OCCURRING" - V2ErrorsEnumAlreadyRevert V2ErrorsEnum = "ALREADY_REVERT" - V2ErrorsEnumNoPostings V2ErrorsEnum = "NO_POSTINGS" - V2ErrorsEnumLedgerNotFound V2ErrorsEnum = "LEDGER_NOT_FOUND" - V2ErrorsEnumImport V2ErrorsEnum = "IMPORT" - V2ErrorsEnumTimeout V2ErrorsEnum = "TIMEOUT" - V2ErrorsEnumBulkSizeExceeded V2ErrorsEnum = "BULK_SIZE_EXCEEDED" - V2ErrorsEnumInterpreterParse V2ErrorsEnum = "INTERPRETER_PARSE" - V2ErrorsEnumInterpreterRuntime V2ErrorsEnum = "INTERPRETER_RUNTIME" + V2ErrorsEnumInternal V2ErrorsEnum = "INTERNAL" + V2ErrorsEnumInsufficientFund V2ErrorsEnum = "INSUFFICIENT_FUND" + V2ErrorsEnumValidation V2ErrorsEnum = "VALIDATION" + V2ErrorsEnumConflict V2ErrorsEnum = "CONFLICT" + V2ErrorsEnumCompilationFailed V2ErrorsEnum = "COMPILATION_FAILED" + V2ErrorsEnumMetadataOverride V2ErrorsEnum = "METADATA_OVERRIDE" + V2ErrorsEnumNotFound V2ErrorsEnum = "NOT_FOUND" + V2ErrorsEnumRevertOccurring V2ErrorsEnum = "REVERT_OCCURRING" + V2ErrorsEnumAlreadyRevert V2ErrorsEnum = "ALREADY_REVERT" + V2ErrorsEnumNoPostings V2ErrorsEnum = "NO_POSTINGS" + V2ErrorsEnumLedgerNotFound V2ErrorsEnum = "LEDGER_NOT_FOUND" + V2ErrorsEnumImport V2ErrorsEnum = "IMPORT" + V2ErrorsEnumTimeout V2ErrorsEnum = "TIMEOUT" + V2ErrorsEnumBulkSizeExceeded V2ErrorsEnum = "BULK_SIZE_EXCEEDED" + V2ErrorsEnumInterpreterParse V2ErrorsEnum = "INTERPRETER_PARSE" + V2ErrorsEnumInterpreterRuntime V2ErrorsEnum = "INTERPRETER_RUNTIME" + V2ErrorsEnumLedgerAlreadyExists V2ErrorsEnum = "LEDGER_ALREADY_EXISTS" ) func (e V2ErrorsEnum) ToPointer() *V2ErrorsEnum { @@ -68,6 +69,8 @@ func (e *V2ErrorsEnum) UnmarshalJSON(data []byte) error { case "INTERPRETER_PARSE": fallthrough case "INTERPRETER_RUNTIME": + fallthrough + case "LEDGER_ALREADY_EXISTS": *e = V2ErrorsEnum(v) return nil default: diff --git a/pkg/client/models/components/v2posttransaction.go b/pkg/client/models/components/v2posttransaction.go index a4e3c695f..812d3497a 100644 --- a/pkg/client/models/components/v2posttransaction.go +++ b/pkg/client/models/components/v2posttransaction.go @@ -8,8 +8,8 @@ import ( ) type V2PostTransactionScript struct { - Plain string `json:"plain"` - Vars map[string]any `json:"vars,omitempty"` + Plain string `json:"plain"` + Vars map[string]string `json:"vars,omitempty"` } func (o *V2PostTransactionScript) GetPlain() string { @@ -19,7 +19,7 @@ func (o *V2PostTransactionScript) GetPlain() string { return o.Plain } -func (o *V2PostTransactionScript) GetVars() map[string]any { +func (o *V2PostTransactionScript) GetVars() map[string]string { if o == nil { return nil } diff --git a/test/e2e/api_transactions_create_test.go b/test/e2e/api_transactions_create_test.go index f7f40cacc..227822224 100644 --- a/test/e2e/api_transactions_create_test.go +++ b/test/e2e/api_transactions_create_test.go @@ -332,7 +332,7 @@ var _ = Context("Ledger accounts list API tests", func() { source = @world destination = @bob )`, - Vars: map[string]interface{}{}, + Vars: map[string]string{}, }, }, Ledger: "default", @@ -365,7 +365,7 @@ var _ = Context("Ledger accounts list API tests", func() { source = @world destination = @bob )`, - Vars: map[string]interface{}{ + Vars: map[string]string{ "amount": "USD -100", }, }, @@ -393,7 +393,7 @@ var _ = Context("Ledger accounts list API tests", func() { Metadata: map[string]string{}, Script: &components.V2PostTransactionScript{ Plain: `XXX`, - Vars: map[string]interface{}{}, + Vars: map[string]string{}, }, }, Ledger: "default", @@ -422,7 +422,7 @@ var _ = Context("Ledger accounts list API tests", func() { } set_tx_meta("foo", "bar") `, - Vars: map[string]interface{}{ + Vars: map[string]string{ "amount": "USD 100", }, }, @@ -449,7 +449,7 @@ var _ = Context("Ledger accounts list API tests", func() { destination = @bob ) set_tx_meta("foo", "bar")`, - Vars: map[string]interface{}{}, + Vars: map[string]string{}, }, }, Ledger: "default", @@ -471,7 +471,7 @@ var _ = Context("Ledger accounts list API tests", func() { source = @world destination = @bob )`, - Vars: map[string]interface{}{}, + Vars: map[string]string{}, }, }, DryRun: pointer.For(true), diff --git a/test/performance/benchmark_test.go b/test/performance/benchmark_test.go index d816b796e..3ac8b0f21 100644 --- a/test/performance/benchmark_test.go +++ b/test/performance/benchmark_test.go @@ -156,16 +156,12 @@ func (benchmark *Benchmark) createTransaction( script string, vars map[string]string, ) (*ledger.Transaction, error) { - varsAsMapAny := make(map[string]any) - for k, v := range vars { - varsAsMapAny[k] = v - } response, err := client.Ledger.V2.CreateTransaction(ctx, operations.V2CreateTransactionRequest{ Ledger: l.Name, V2PostTransaction: components.V2PostTransaction{ Script: &components.V2PostTransactionScript{ Plain: script, - Vars: varsAsMapAny, + Vars: vars, }, }, }) diff --git a/tools/generator/cmd/root.go b/tools/generator/cmd/root.go new file mode 100644 index 000000000..832e97a5c --- /dev/null +++ b/tools/generator/cmd/root.go @@ -0,0 +1,119 @@ +package cmd + +import ( + "errors" + "fmt" + "github.com/formancehq/go-libs/v2/logging" + ledgerclient "github.com/formancehq/ledger/pkg/client" + "github.com/formancehq/ledger/pkg/client/models/components" + "github.com/formancehq/ledger/pkg/client/models/operations" + "github.com/formancehq/ledger/pkg/client/models/sdkerrors" + "github.com/formancehq/ledger/pkg/generate" + "os" + "sync" + + "github.com/spf13/cobra" +) + +var ( + rootCmd = &cobra.Command{ + Use: "generator ", + Short: "Generate data for a ledger. WARNING: This is an experimental tool.", + RunE: run, + Args: cobra.ExactArgs(2), + } + parallelFlag = "parallel" + ledgerFlag = "ledger" + untilTransactionIDFlag = "until-transaction-id" +) + +func run(cmd *cobra.Command, args []string) error { + ledgerUrl := args[0] + scriptLocation := args[1] + + fileContent, err := os.ReadFile(scriptLocation) + if err != nil { + return fmt.Errorf("failed to read file: %w", err) + } + + vus, err := cmd.Flags().GetInt(parallelFlag) + if err != nil { + return fmt.Errorf("failed to get vu: %w", err) + } + + ledger, err := cmd.Flags().GetString(ledgerFlag) + if err != nil { + return fmt.Errorf("failed to get ledger: %w", err) + } + + untilTransactionID, err := cmd.Flags().GetInt64(untilTransactionIDFlag) + if err != nil { + return fmt.Errorf("failed to get untilTransactionID: %w", err) + } + + client := ledgerclient.New(ledgerclient.WithServerURL(ledgerUrl)) + + _, err = client.Ledger.V2.CreateLedger(cmd.Context(), operations.V2CreateLedgerRequest{ + Ledger: ledger, + }) + if err != nil { + sdkError := &sdkerrors.V2ErrorResponse{} + if !errors.As(err, &sdkError) || sdkError.ErrorCode != components.V2ErrorsEnumLedgerAlreadyExists { + return fmt.Errorf("failed to create ledger: %w", err) + } + } + + wg := sync.WaitGroup{} + wg.Add(vus) + + for i := 0; i < vus; i++ { + generator, err := generate.NewGenerator(string(fileContent)) + if err != nil { + return fmt.Errorf("failed to create generator: %w", err) + } + go func() { + defer wg.Done() + + for { + next := generator.Next(i) + tx, err := client.Ledger.V2.CreateTransaction( + cmd.Context(), + operations.V2CreateTransactionRequest{ + Ledger: ledger, + V2PostTransaction: components.V2PostTransaction{ + Script: &components.V2PostTransactionScript{ + Plain: next.Script, + Vars: next.Variables, + }, + }, + }, + ) + if err != nil { + logging.FromContext(cmd.Context()).Errorf("Vu stopped with error: %s", err) + return + } + if untilTransactionID != 0 && tx.V2CreateTransactionResponse.Data.ID.Int64() >= untilTransactionID { + return + } + } + }() + } + + wg.Wait() + + return nil +} + +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func init() { + rootCmd.Flags().IntP(parallelFlag, "p", 1, "Number of parallel users") + rootCmd.Flags().StringP(ledgerFlag, "l", "default", "Ledger to feed") + rootCmd.Flags().Int64P(untilTransactionIDFlag, "u", 0, "Stop after this transaction ID") + rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/tools/generator/examples/example1.js b/tools/generator/examples/example1.js new file mode 100644 index 000000000..2517a3c16 --- /dev/null +++ b/tools/generator/examples/example1.js @@ -0,0 +1,26 @@ +const script = `vars { + account $order + account $seller +} +send [USD/2 100] ( + source = @world + destination = $order +) +send [USD/2 1] ( + source = $order + destination = @fees +) +send [USD/2 99] ( + source = $order + destination = $seller +)` + +function next(iteration) { + return { + script, + variables: { + order: `orders:${uuid()}`, + seller: `sellers:${iteration % 5}` + } + } +} \ No newline at end of file diff --git a/tools/generator/main.go b/tools/generator/main.go new file mode 100644 index 000000000..9b4def5eb --- /dev/null +++ b/tools/generator/main.go @@ -0,0 +1,7 @@ +package main + +import "github.com/formancehq/ledger/tools/generator/cmd" + +func main() { + cmd.Execute() +}