From a8df9b472abed366e8adf960c3af62a1ec8ae6e7 Mon Sep 17 00:00:00 2001 From: Azorlogh Date: Mon, 1 Dec 2025 17:25:38 +0100 Subject: [PATCH 01/14] wip --- docs/api/README.md | 130 ++++++++++- docs/events/InsertedSchema.json | 49 ++++ internal/README.md | 88 ++++++- internal/api/bulking/elements.go | 2 +- .../api/v2/controllers_transactions_create.go | 21 +- .../controller/ledger/controller_default.go | 38 +++- .../ledger/controller_default_test.go | 215 +++++++++++------- internal/errors.go | 3 + internal/machine/vm/machine.go | 1 + internal/machine/vm/run.go | 5 +- internal/schema.go | 3 +- .../bucket/migrations/44-add-schema/up.sql | 1 + internal/transaction_templates.go | 69 ++++++ openapi.yaml | 19 ++ openapi/v2.yaml | 19 ++ pkg/client/.speakeasy/gen.lock | 6 +- pkg/client/.speakeasy/logs/naming.log | 9 +- .../components/v2posttransactionscript.md | 1 + .../docs/models/components/v2schemadata.md | 7 +- .../components/v2transactiontemplate.md | 10 + pkg/client/docs/sdks/v2/README.md | 1 + .../models/components/v2posttransaction.go | 12 +- pkg/client/models/components/v2schemadata.go | 9 + .../components/v2transactiontemplate.go | 30 +++ .../.speakeasy/logs/naming.log | 9 +- test/e2e/api_schema_test.go | 80 +++++-- 26 files changed, 691 insertions(+), 146 deletions(-) create mode 100644 internal/transaction_templates.go create mode 100644 pkg/client/docs/models/components/v2transactiontemplate.md create mode 100644 pkg/client/models/components/v2transactiontemplate.go diff --git a/docs/api/README.md b/docs/api/README.md index 8efd585a95..d6e042570c 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -342,7 +342,14 @@ Idempotency-Key: string ".pattern": "^[0-9]{16}$" } } - } + }, + "transactions": [ + { + "id": "string", + "description": "string", + "script": "string" + } + ] } ``` @@ -421,7 +428,14 @@ Accept: application/json "$userID": { ".pattern": "^[0-9]{16}$" } - } + }, + "transactions": [ + { + "id": "string", + "description": "string", + "script": "string" + } + ] } } } @@ -488,7 +502,14 @@ Accept: application/json "$userID": { ".pattern": "^[0-9]{16}$" } - } + }, + "transactions": [ + { + "id": "string", + "description": "string", + "script": "string" + } + ] } } ], @@ -703,6 +724,7 @@ Accept: application/json } ], "script": { + "template": "CUSTOMER_DEPOSIT", "plain": "vars {\naccount $user\n}\nsend [COIN 10] (\n\tsource = @world\n\tdestination = $user\n)\n", "vars": { "user": "users:042" @@ -1549,6 +1571,7 @@ Idempotency-Key: string } ], "script": { + "template": "CUSTOMER_DEPOSIT", "plain": "vars {\naccount $user\n}\nsend [COIN 10] (\n\tsource = @world\n\tdestination = $user\n)\n", "vars": { "user": "users:042" @@ -3979,6 +4002,7 @@ This operation does not require authentication } ], "script": { + "template": "CUSTOMER_DEPOSIT", "plain": "vars {\naccount $user\n}\nsend [COIN 10] (\n\tsource = @world\n\tdestination = $user\n)\n", "vars": { "user": "users:042" @@ -4009,6 +4033,7 @@ This operation does not require authentication |timestamp|string(date-time)|false|none|none| |postings|[[V2Posting](#schemav2posting)]|false|none|none| |script|object|false|none|none| +|» template|string|false|none|none| |» plain|string|true|none|none| |» vars|object|false|none|none| |»» **additionalProperties**|string|false|none|none| @@ -4711,6 +4736,7 @@ This operation does not require authentication } ], "script": { + "template": "CUSTOMER_DEPOSIT", "plain": "vars {\naccount $user\n}\nsend [COIN 10] (\n\tsource = @world\n\tdestination = $user\n)\n", "vars": { "user": "users:042" @@ -4786,6 +4812,7 @@ This operation does not require authentication } ], "script": { + "template": "CUSTOMER_DEPOSIT", "plain": "vars {\naccount $user\n}\nsend [COIN 10] (\n\tsource = @world\n\tdestination = $user\n)\n", "vars": { "user": "users:042" @@ -4858,6 +4885,7 @@ xor } ], "script": { + "template": "CUSTOMER_DEPOSIT", "plain": "vars {\naccount $user\n}\nsend [COIN 10] (\n\tsource = @world\n\tdestination = $user\n)\n", "vars": { "user": "users:042" @@ -5730,6 +5758,56 @@ Chart of account |---|---|---|---|---| |**additionalProperties**|[V2ChartSegment](#schemav2chartsegment)|false|none|Segment within a chart of accounts| +

V2TransactionTemplate

+ + + + + + +```json +{ + "id": "string", + "description": "string", + "script": "string" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|none| +|description|string|false|none|none| +|script|string|false|none|none| + +

V2TransactionTemplates

+ + + + + + +```json +[ + { + "id": "string", + "description": "string", + "script": "string" + } +] + +``` + +Transaction templates + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|*anonymous*|[[V2TransactionTemplate](#schemav2transactiontemplate)]|false|none|Transaction templates| +

V2SchemaData

@@ -5745,7 +5823,14 @@ Chart of account ".pattern": "^[0-9]{16}$" } } - } + }, + "transactions": [ + { + "id": "string", + "description": "string", + "script": "string" + } + ] } ``` @@ -5757,6 +5842,7 @@ Schema data structure for ledger schemas |Name|Type|Required|Restrictions|Description| |---|---|---|---|---| |chart|[V2ChartOfAccounts](#schemav2chartofaccounts)|true|none|Chart of account| +|transactions|[V2TransactionTemplates](#schemav2transactiontemplates)|false|none|Transaction templates|

V2Schema

@@ -5774,7 +5860,14 @@ Schema data structure for ledger schemas "$userID": { ".pattern": "^[0-9]{16}$" } - } + }, + "transactions": [ + { + "id": "string", + "description": "string", + "script": "string" + } + ] } } @@ -5815,7 +5908,14 @@ and "$userID": { ".pattern": "^[0-9]{16}$" } - } + }, + "transactions": [ + { + "id": "string", + "description": "string", + "script": "string" + } + ] } } } @@ -5847,7 +5947,14 @@ and "$userID": { ".pattern": "^[0-9]{16}$" } - } + }, + "transactions": [ + { + "id": "string", + "description": "string", + "script": "string" + } + ] } } ], @@ -5884,7 +5991,14 @@ and "$userID": { ".pattern": "^[0-9]{16}$" } - } + }, + "transactions": [ + { + "id": "string", + "description": "string", + "script": "string" + } + ] } } ], diff --git a/docs/events/InsertedSchema.json b/docs/events/InsertedSchema.json index a56c104e71..9f408c9f3b 100644 --- a/docs/events/InsertedSchema.json +++ b/docs/events/InsertedSchema.json @@ -117,6 +117,9 @@ "chart": { "$ref": "#/$defs/ChartOfAccounts" }, + "transactions": { + "$ref": "#/$defs/TransactionTemplates" + }, "version": { "type": "string" }, @@ -128,6 +131,7 @@ "type": "object", "required": [ "chart", + "transactions", "version", "createdAt" ] @@ -136,6 +140,51 @@ "type": "string", "format": "date-time", "title": "Normalized date" +<<<<<<< HEAD:docs/events/InsertedSchema.json +======= + }, + "TransactionTemplate": { + "properties": { + "Description": { + "type": "string" + }, + "Script": { + "type": "string" + }, + "Runtime": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "Description", + "Script", + "Runtime" + ] + }, + "TransactionTemplates": { + "additionalProperties": { + "$ref": "#/$defs/TransactionTemplate" + }, + "type": "object" + }, + "UpdatedSchema": { + "properties": { + "ledger": { + "type": "string" + }, + "schema": { + "$ref": "#/$defs/Schema" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "ledger", + "schema" + ] +>>>>>>> 4a8518f7 (wip):docs/events/UpdatedSchema.json } } } \ No newline at end of file diff --git a/internal/README.md b/internal/README.md index 6fa49b9113..49e7952b45 100644 --- a/internal/README.md +++ b/internal/README.md @@ -68,6 +68,7 @@ import "github.com/formancehq/ledger/internal" - [func \(e ErrInvalidLedgerName\) Error\(\) string](<#ErrInvalidLedgerName.Error>) - [func \(e ErrInvalidLedgerName\) Is\(err error\) bool](<#ErrInvalidLedgerName.Is>) - [type ErrInvalidSchema](<#ErrInvalidSchema>) + - [func NewErrInvalidSchema\(err error\) ErrInvalidSchema](<#NewErrInvalidSchema>) - [func \(e ErrInvalidSchema\) Error\(\) string](<#ErrInvalidSchema.Error>) - [func \(e ErrInvalidSchema\) Is\(err error\) bool](<#ErrInvalidSchema.Is>) - [type ErrPipelineAlreadyExists](<#ErrPipelineAlreadyExists>) @@ -137,6 +138,7 @@ import "github.com/formancehq/ledger/internal" - [func \(p RevertedTransaction\) NeedsSchema\(\) bool](<#RevertedTransaction.NeedsSchema>) - [func \(r RevertedTransaction\) Type\(\) LogType](<#RevertedTransaction.Type>) - [func \(r RevertedTransaction\) ValidateWithSchema\(schema Schema\) error](<#RevertedTransaction.ValidateWithSchema>) +- [type RuntimeType](<#RuntimeType>) - [type SavedMetadata](<#SavedMetadata>) - [func \(p SavedMetadata\) NeedsSchema\(\) bool](<#SavedMetadata.NeedsSchema>) - [func \(s SavedMetadata\) Type\(\) LogType](<#SavedMetadata.Type>) @@ -168,6 +170,10 @@ import "github.com/formancehq/ledger/internal" - [type TransactionData](<#TransactionData>) - [func NewTransactionData\(\) TransactionData](<#NewTransactionData>) - [func \(data TransactionData\) WithPostings\(postings ...Posting\) TransactionData](<#TransactionData.WithPostings>) +- [type TransactionTemplate](<#TransactionTemplate>) +- [type TransactionTemplates](<#TransactionTemplates>) + - [func \(t TransactionTemplates\) MarshalJSON\(\) \(\[\]byte, error\)](<#TransactionTemplates.MarshalJSON>) + - [func \(t \*TransactionTemplates\) UnmarshalJSON\(data \[\]byte\) error](<#TransactionTemplates.UnmarshalJSON>) - [type Transactions](<#Transactions>) - [type Volumes](<#Volumes>) - [func NewEmptyVolumes\(\) Volumes](<#NewEmptyVolumes>) @@ -745,7 +751,7 @@ func (e ErrAlreadyStarted) Is(err error) bool -## type [ErrInvalidAccount]() +## type [ErrInvalidAccount]() @@ -756,7 +762,7 @@ type ErrInvalidAccount struct { ``` -### func \(ErrInvalidAccount\) [Error]() +### func \(ErrInvalidAccount\) [Error]() ```go func (e ErrInvalidAccount) Error() string @@ -765,7 +771,7 @@ func (e ErrInvalidAccount) Error() string -### func \(ErrInvalidAccount\) [Is]() +### func \(ErrInvalidAccount\) [Is]() ```go func (e ErrInvalidAccount) Is(err error) bool @@ -842,6 +848,15 @@ type ErrInvalidSchema struct { } ``` + +### func [NewErrInvalidSchema]() + +```go +func NewErrInvalidSchema(err error) ErrInvalidSchema +``` + + + ### func \(ErrInvalidSchema\) [Error]() @@ -1546,6 +1561,24 @@ func (r RevertedTransaction) ValidateWithSchema(schema Schema) error + +## type [RuntimeType]() + + + +```go +type RuntimeType string +``` + + + +```go +const ( + RuntimeExperimentalInterpreter RuntimeType = "experimental-interpreter" + RuntimeMachine RuntimeType = "machine" +) +``` + ## type [SavedMetadata]() @@ -1596,7 +1629,7 @@ func (s SavedMetadata) ValidateWithSchema(schema Schema) error -## type [Schema]() +## type [Schema]() @@ -1611,7 +1644,7 @@ type Schema struct { ``` -### func [NewSchema]() +### func [NewSchema]() ```go func NewSchema(version string, data SchemaData) (Schema, error) @@ -1620,13 +1653,14 @@ func NewSchema(version string, data SchemaData) (Schema, error) -## type [SchemaData]() +## type [SchemaData]() ```go type SchemaData struct { - Chart ChartOfAccounts `json:"chart" bun:"chart"` + Chart ChartOfAccounts `json:"chart" bun:"chart"` + Transactions TransactionTemplates `json:"transactions" bun:"transactions"` } ``` @@ -1856,6 +1890,46 @@ func (data TransactionData) WithPostings(postings ...Posting) TransactionData + +## type [TransactionTemplate]() + + + +```go +type TransactionTemplate struct { + Description string + Script string + Runtime RuntimeType +} +``` + + +## type [TransactionTemplates]() + + + +```go +type TransactionTemplates map[string]TransactionTemplate +``` + + +### func \(TransactionTemplates\) [MarshalJSON]() + +```go +func (t TransactionTemplates) MarshalJSON() ([]byte, error) +``` + + + + +### func \(\*TransactionTemplates\) [UnmarshalJSON]() + +```go +func (t *TransactionTemplates) UnmarshalJSON(data []byte) error +``` + +Marshal that transforms a list of transactions with ids into a map + ## type [Transactions]() diff --git a/internal/api/bulking/elements.go b/internal/api/bulking/elements.go index b1f592810d..e1b6da6d28 100644 --- a/internal/api/bulking/elements.go +++ b/internal/api/bulking/elements.go @@ -123,7 +123,7 @@ func (req TransactionRequest) ToCore() (*ledgercontroller.CreateTransaction, err } runScript = ledgercontroller.TxToScriptData(txData, req.Force) - } else { + } else if req.Script.Plain != "" || req.Script.Template != "" { runScript = ledgercontroller.RunScript{ Script: req.Script.ToCore(), Timestamp: req.Timestamp, diff --git a/internal/api/v2/controllers_transactions_create.go b/internal/api/v2/controllers_transactions_create.go index af435e2a3f..75bfc47201 100644 --- a/internal/api/v2/controllers_transactions_create.go +++ b/internal/api/v2/controllers_transactions_create.go @@ -2,6 +2,7 @@ package v2 import ( "errors" + "fmt" "net/http" "github.com/formancehq/go-libs/v3/api" @@ -16,13 +17,21 @@ func createTransaction(w http.ResponseWriter, r *http.Request) { common.WithBody(w, r, func(payload bulking.TransactionRequest) { l := common.LedgerFromContext(r.Context()) - if len(payload.Postings) > 0 && payload.Script.Plain != "" { - api.BadRequest(w, common.ErrValidation, errors.New("cannot pass postings and numscript in the same request")) - return + txType := []string{} + if len(payload.Postings) > 0 { + txType = append(txType, "postings") } - - if len(payload.Postings) == 0 && payload.Script.Plain == "" { - api.BadRequest(w, common.ErrNoPostings, errors.New("you need to pass either a posting array or a numscript script")) + if payload.Script.Plain != "" { + txType = append(txType, "numscript") + } + if payload.Script.Template != "" { + txType = append(txType, "template") + } + if len(txType) > 1 { + api.BadRequest(w, common.ErrValidation, fmt.Errorf("cannot pass %v and %v in the same request", txType[0], txType[1])) + return + } else if len(txType) == 0 { + api.BadRequest(w, common.ErrNoPostings, errors.New("you must pass either a posting array, a numscript script, or a template")) return } // nodes(gfyrag): parameter 'force' initially sent using a query param diff --git a/internal/controller/ledger/controller_default.go b/internal/controller/ledger/controller_default.go index c237d85ebd..5770a8478a 100644 --- a/internal/controller/ledger/controller_default.go +++ b/internal/controller/ledger/controller_default.go @@ -65,6 +65,15 @@ func (ctrl *DefaultController) insertSchema(ctx context.Context, store Store, _s if err != nil { return nil, fmt.Errorf("creating schema: %w", err) } + + for id, template := range schema.Transactions { + parser := ctrl.getParser(RuntimeType(template.Runtime)) + _, err := parser.Parse(template.Script) + if err != nil { + return nil, ledger.NewErrInvalidSchema(fmt.Errorf("invalid template %s: %w", id, err)) + } + } + if err := store.InsertSchema(ctx, &schema); err != nil { if errors.Is(err, postgres.ErrConstraintsFailed{}) { return nil, newErrSchemaAlreadyExists(parameters.Input.Version) @@ -394,8 +403,8 @@ func (ctrl *DefaultController) Export(ctx context.Context, w ExportWriter) error ) } -func (ctrl *DefaultController) getParser(tx CreateTransaction) NumscriptParser { - switch tx.Runtime { +func (ctrl *DefaultController) getParser(runtimeType RuntimeType) NumscriptParser { + switch runtimeType { case RuntimeExperimentalInterpreter: return ctrl.interpreterParser case RuntimeMachine: @@ -406,11 +415,21 @@ func (ctrl *DefaultController) getParser(tx CreateTransaction) NumscriptParser { } func (ctrl *DefaultController) createTransaction(ctx context.Context, store Store, schema *ledger.Schema, parameters Parameters[CreateTransaction]) (*ledger.CreatedTransaction, error) { - logger := logging.FromContext(ctx).WithField("req", uuid.NewString()[:8]) ctx = logging.ContextWithLogger(ctx, logger) - m, err := ctrl.getParser(parameters.Input).Parse(parameters.Input.Plain) + if schema != nil { + if parameters.Input.Template == "" { + return nil, newErrSchemaValidationError(parameters.SchemaVersion, fmt.Errorf("transactions on this ledger must use a template")) + } + if template, ok := schema.SchemaData.Transactions[parameters.Input.Template]; ok { + parameters.Input.Plain = template.Script + } else { + return nil, newErrSchemaValidationError(parameters.SchemaVersion, fmt.Errorf("failed to find transaction template `%s`", parameters.Input.Template)) + } + } + + m, err := ctrl.getParser(parameters.Input.Runtime).Parse(parameters.Input.Plain) if err != nil { return nil, fmt.Errorf("failed to compile script: %w", err) } @@ -421,7 +440,8 @@ func (ctrl *DefaultController) createTransaction(ctx context.Context, store Stor ctrl.tracer, ctrl.executeMachineHistogram, func(ctx context.Context) (*NumscriptExecutionResult, error) { - return m.Execute(ctx, store, parameters.Input.Vars) + a, err := m.Execute(ctx, store, parameters.Input.Vars) + return a, err }, ) if err != nil { @@ -482,7 +502,7 @@ func (ctrl *DefaultController) CreateTransaction(ctx context.Context, parameters return ctrl.createTransactionLp.forgeLog(ctx, ctrl.store, parameters, ctrl.createTransaction) } -func (ctrl *DefaultController) revertTransaction(ctx context.Context, store Store, _schema *ledger.Schema, parameters Parameters[RevertTransaction]) (*ledger.RevertedTransaction, error) { +func (ctrl *DefaultController) revertTransaction(ctx context.Context, store Store, schema *ledger.Schema, parameters Parameters[RevertTransaction]) (*ledger.RevertedTransaction, error) { var ( hasBeenReverted bool err error @@ -552,7 +572,7 @@ func (ctrl *DefaultController) RevertTransaction(ctx context.Context, parameters return ctrl.revertTransactionLp.forgeLog(ctx, ctrl.store, parameters, ctrl.revertTransaction) } -func (ctrl *DefaultController) saveTransactionMetadata(ctx context.Context, store Store, _schema *ledger.Schema, parameters Parameters[SaveTransactionMetadata]) (*ledger.SavedMetadata, error) { +func (ctrl *DefaultController) saveTransactionMetadata(ctx context.Context, store Store, schema *ledger.Schema, parameters Parameters[SaveTransactionMetadata]) (*ledger.SavedMetadata, error) { if _, _, err := store.UpdateTransactionMetadata(ctx, parameters.Input.TransactionID, parameters.Input.Metadata, time.Time{}); err != nil { return nil, err } @@ -600,7 +620,7 @@ func (ctrl *DefaultController) SaveAccountMetadata(ctx context.Context, paramete return log, idempotencyHit, err } -func (ctrl *DefaultController) deleteTransactionMetadata(ctx context.Context, store Store, _schema *ledger.Schema, parameters Parameters[DeleteTransactionMetadata]) (*ledger.DeletedMetadata, error) { +func (ctrl *DefaultController) deleteTransactionMetadata(ctx context.Context, store Store, schema *ledger.Schema, parameters Parameters[DeleteTransactionMetadata]) (*ledger.DeletedMetadata, error) { _, modified, err := store.DeleteTransactionMetadata(ctx, parameters.Input.TransactionID, parameters.Input.Key, time.Time{}) if err != nil { return nil, err @@ -622,7 +642,7 @@ func (ctrl *DefaultController) DeleteTransactionMetadata(ctx context.Context, pa return log, idempotencyHit, err } -func (ctrl *DefaultController) deleteAccountMetadata(ctx context.Context, store Store, _schema *ledger.Schema, parameters Parameters[DeleteAccountMetadata]) (*ledger.DeletedMetadata, error) { +func (ctrl *DefaultController) deleteAccountMetadata(ctx context.Context, store Store, schema *ledger.Schema, parameters Parameters[DeleteAccountMetadata]) (*ledger.DeletedMetadata, error) { err := store.DeleteAccountMetadata(ctx, parameters.Input.Address, parameters.Input.Key) if err != nil { return nil, err diff --git a/internal/controller/ledger/controller_default_test.go b/internal/controller/ledger/controller_default_test.go index f7cb48389b..f962392fe5 100644 --- a/internal/controller/ledger/controller_default_test.go +++ b/internal/controller/ledger/controller_default_test.go @@ -18,11 +18,14 @@ import ( "github.com/formancehq/go-libs/v3/time" ledger "github.com/formancehq/ledger/internal" + "github.com/formancehq/ledger/internal/machine/vm" "github.com/formancehq/ledger/internal/storage/common" ledgerstore "github.com/formancehq/ledger/internal/storage/ledger" ) -func testCreateTransaction(t *testing.T, withSchema bool) { +func TestCreateTransactionWithoutSchema(t *testing.T) { + t.Parallel() + ctrl := gomock.NewController(t) store := NewMockStore(ctrl) @@ -33,60 +36,6 @@ func testCreateTransaction(t *testing.T, withSchema bool) { l := NewDefaultController(ledger.Ledger{}, store, parser, machineParser, interpreterParser) - var schema ledger.Schema - var schemaVersion string - if withSchema { - schemaVersion = "v1.0" - schema = ledger.Schema{ - SchemaData: ledger.SchemaData{ - Chart: ledger.ChartOfAccounts{ - "world": { - Account: &ledger.ChartAccount{ - Metadata: map[string]ledger.ChartAccountMetadata{ - "foo": { - Default: pointer.For("bar"), - }, - }, - }, - }, - "bank": { - Account: &ledger.ChartAccount{}, - }, - }, - }, - Version: schemaVersion, - } - - store.EXPECT(). - BeginTX(gomock.Any(), nil). - Return(store, &bun.Tx{}, nil) - - store.EXPECT(). - InsertSchema(gomock.Any(), &schema). - Return(nil) - - store.EXPECT(). - Commit(gomock.Any()). - Return(nil) - - store.EXPECT(). - InsertLog(gomock.Any(), gomock.Cond(func(x any) bool { - return x.(*ledger.Log).Type == ledger.InsertedSchemaLogType - })). - DoAndReturn(func(_ context.Context, log *ledger.Log) any { - log.ID = pointer.For(uint64(0)) - return log - }) - - _, _, _, err := l.InsertSchema(context.Background(), Parameters[InsertSchema]{ - Input: InsertSchema{ - Version: schema.Version, - Data: schema.SchemaData, - }, - }) - require.NoError(t, err) - } - runScript := RunScript{} parser.EXPECT(). @@ -108,27 +57,14 @@ func testCreateTransaction(t *testing.T, withSchema bool) { Postings: ledger.Postings{posting}, }, nil) - if withSchema { - store.EXPECT(). - FindSchema(gomock.Any(), "v1.0"). - Return(&schema, nil) - } else { - store.EXPECT(). - FindLatestSchemaVersion(gomock.Any()). - Return(nil, nil) - } + store.EXPECT(). + FindLatestSchemaVersion(gomock.Any()). + Return(nil, nil) - if withSchema { - store.EXPECT(). - CommitTransaction(gomock.Any(), gomock.Any()). - Return(nil) - store.EXPECT().UpsertAccounts(gomock.Any(), gomock.Any()) - } else { - store.EXPECT(). - CommitTransaction(gomock.Any(), gomock.Any()). - Return(nil) - store.EXPECT().UpsertAccounts(gomock.Any(), gomock.Any()) - } + store.EXPECT(). + CommitTransaction(gomock.Any(), gomock.Any()). + Return(nil) + store.EXPECT().UpsertAccounts(gomock.Any(), gomock.Any()) store.EXPECT(). InsertLog(gomock.Any(), gomock.Cond(func(x any) bool { @@ -140,7 +76,6 @@ func testCreateTransaction(t *testing.T, withSchema bool) { }) _, _, _, err := l.CreateTransaction(context.Background(), Parameters[CreateTransaction]{ - SchemaVersion: schemaVersion, Input: CreateTransaction{ RunScript: runScript, }, @@ -148,10 +83,132 @@ func testCreateTransaction(t *testing.T, withSchema bool) { require.NoError(t, err) } -func TestCreateTransaction(t *testing.T) { +func TestCreateTransactionWithSchema(t *testing.T) { t.Parallel() - testCreateTransaction(t, false) - testCreateTransaction(t, true) + ctrl := gomock.NewController(t) + + store := NewMockStore(ctrl) + numscriptRuntime := NewMockNumscriptRuntime(ctrl) + parser := NewMockNumscriptParser(ctrl) + machineParser := NewMockNumscriptParser(ctrl) + interpreterParser := NewMockNumscriptParser(ctrl) + + l := NewDefaultController(ledger.Ledger{}, store, parser, machineParser, interpreterParser) + + script := ` +var { + account $dest +} +send [EUR/2 100] ( + source = world + destination = $dest +)` + + schemaVersion := "v1.0" + schema := ledger.Schema{ + SchemaData: ledger.SchemaData{ + Chart: ledger.ChartOfAccounts{ + "world": { + Account: &ledger.ChartAccount{ + Metadata: map[string]ledger.ChartAccountMetadata{ + "foo": { + Default: pointer.For("bar"), + }, + }, + }, + }, + "bank": { + Account: &ledger.ChartAccount{}, + }, + }, + }, + Version: schemaVersion, + } + + store.EXPECT(). + BeginTX(gomock.Any(), nil). + Return(store, &bun.Tx{}, nil) + + store.EXPECT(). + InsertSchema(gomock.Any(), &schema). + Return(nil) + + store.EXPECT(). + Commit(gomock.Any()). + Return(nil) + + store.EXPECT(). + InsertLog(gomock.Any(), gomock.Cond(func(x any) bool { + return x.(*ledger.Log).Type == ledger.InsertedSchemaLogType + })). + DoAndReturn(func(_ context.Context, log *ledger.Log) any { + log.ID = pointer.For(uint64(0)) + return log + }) + + _, _, _, err := l.InsertSchema(context.Background(), Parameters[InsertSchema]{ + Input: InsertSchema{ + Version: schema.Version, + Data: schema.SchemaData, + }, + }) + require.NoError(t, err) + + runScript := RunScript{ + Script: vm.Script{ + Plain: "", + Template: "TRANSFER", + Vars: map[string]string{ + "dest": "bank", + }, + }, + } + + parser.EXPECT(). + Parse(script). + Return(numscriptRuntime, nil) + + store.EXPECT(). + BeginTX(gomock.Any(), nil). + Return(store, &bun.Tx{}, nil) + + numscriptRuntime.EXPECT(). + Execute(gomock.Any(), store, runScript.Vars). + Return(&NumscriptExecutionResult{ + Postings: ledger.Postings{ + ledger.NewPosting("world", "bank", "USD", big.NewInt(100)), + }, + }, nil) + + store.EXPECT(). + Commit(gomock.Any()). + Return(nil) + + store.EXPECT(). + FindSchema(gomock.Any(), "v1.0"). + Return(&schema, nil) + + store.EXPECT(). + CommitTransaction(gomock.Any(), gomock.Any()). + Return(nil) + store.EXPECT().UpsertAccounts(gomock.Any(), gomock.Any()) + + store.EXPECT(). + InsertLog(gomock.Any(), gomock.Cond(func(x any) bool { + return x.(*ledger.Log).Type == ledger.NewTransactionLogType + })). + DoAndReturn(func(_ context.Context, log *ledger.Log) any { + log.ID = pointer.For(uint64(0)) + return log + }) + + _, _, _, err = l.CreateTransaction(context.Background(), Parameters[CreateTransaction]{ + SchemaVersion: schemaVersion, + Input: CreateTransaction{ + RunScript: runScript, + }, + }) + require.NoError(t, err) } func TestRevertTransaction(t *testing.T) { diff --git a/internal/errors.go b/internal/errors.go index 205e931b65..2f9df7066d 100644 --- a/internal/errors.go +++ b/internal/errors.go @@ -99,6 +99,9 @@ func (e ErrInvalidSchema) Is(err error) bool { _, ok := err.(ErrInvalidSchema) return ok } +func NewErrInvalidSchema(err error) ErrInvalidSchema { + return ErrInvalidSchema{err} +} type ErrInvalidAccount struct { path []string diff --git a/internal/machine/vm/machine.go b/internal/machine/vm/machine.go index f9c29f5584..83f9bba4e4 100644 --- a/internal/machine/vm/machine.go +++ b/internal/machine/vm/machine.go @@ -469,6 +469,7 @@ func (m *Machine) tick() (bool, error) { } func (m *Machine) Execute() error { + fmt.Printf("running machine!") go m.Printer(m.printChan) defer close(m.printChan) diff --git a/internal/machine/vm/run.go b/internal/machine/vm/run.go index 9a010d3204..8d0ad3a168 100644 --- a/internal/machine/vm/run.go +++ b/internal/machine/vm/run.go @@ -19,8 +19,9 @@ type RunScript struct { } type Script struct { - Plain string `json:"plain"` - Vars map[string]string `json:"vars" swaggertype:"object"` + Plain string `json:"plain"` + Template string `json:"template"` + Vars map[string]string `json:"vars" swaggertype:"object"` } type ScriptV1 struct { diff --git a/internal/schema.go b/internal/schema.go index f2dc98a67a..fed1b0ec3e 100644 --- a/internal/schema.go +++ b/internal/schema.go @@ -9,7 +9,8 @@ import ( ) type SchemaData struct { - Chart ChartOfAccounts `json:"chart" bun:"chart"` + Chart ChartOfAccounts `json:"chart" bun:"chart"` + Transactions TransactionTemplates `json:"transactions" bun:"transactions"` } type Schema struct { diff --git a/internal/storage/bucket/migrations/44-add-schema/up.sql b/internal/storage/bucket/migrations/44-add-schema/up.sql index e20743c63e..221b063391 100644 --- a/internal/storage/bucket/migrations/44-add-schema/up.sql +++ b/internal/storage/bucket/migrations/44-add-schema/up.sql @@ -7,6 +7,7 @@ do $$ version text not null, created_at timestamp without time zone not null default now(), chart jsonb not null, + transactions jsonb not null, primary key (ledger, version) ); diff --git a/internal/transaction_templates.go b/internal/transaction_templates.go new file mode 100644 index 0000000000..21856aea58 --- /dev/null +++ b/internal/transaction_templates.go @@ -0,0 +1,69 @@ +package ledger + +import ( + "encoding/json" + "errors" + "fmt" +) + +type RuntimeType string + +const ( + RuntimeExperimentalInterpreter RuntimeType = "experimental-interpreter" + RuntimeMachine RuntimeType = "machine" +) + +type TransactionTemplate struct { + Description string + Script string + Runtime RuntimeType +} + +type TransactionTemplates map[string]TransactionTemplate + +// Marshal that transforms a list of transactions with ids into a map +func (t *TransactionTemplates) UnmarshalJSON(data []byte) error { + var rawList []struct { + ID string + Description string + Script string + } + if err := json.Unmarshal(data, &rawList); err != nil { + return err + } + out := make(map[string]TransactionTemplate) + for _, item := range rawList { + if item.ID == "" { + return errors.New("transaction template id cannot be empty") + } + if _, exists := out[item.ID]; exists { + return fmt.Errorf("duplicate transaction template id: %v", item.ID) + } + out[item.ID] = TransactionTemplate{ + Description: item.Description, + Script: item.Script, + } + } + *t = out + return nil +} + +func (t TransactionTemplates) MarshalJSON() ([]byte, error) { + var rawList []struct { + ID string + Description string + Script string + } + for id, item := range t { + rawList = append(rawList, struct { + ID string + Description string + Script string + }{ + ID: id, + Description: item.Description, + Script: item.Script, + }) + } + return json.Marshal(rawList) +} diff --git a/openapi.yaml b/openapi.yaml index 82d6c74bfb..9e4e98ce4a 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -4147,6 +4147,9 @@ components: script: type: object properties: + template: + type: string + example: CUSTOMER_DEPOSIT plain: type: string example: "vars {\naccount $user\n}\nsend [COIN 10] (\n\tsource = @world\n\tdestination = $user\n)\n" @@ -4590,12 +4593,28 @@ components: users: $userID: .pattern: "^[0-9]{16}$" + V2TransactionTemplate: + type: object + properties: + id: + type: string + description: + type: string + script: + type: string + V2TransactionTemplates: + type: array + description: Transaction templates + items: + $ref: "#/components/schemas/V2TransactionTemplate" V2SchemaData: type: object description: Schema data structure for ledger schemas properties: chart: $ref: "#/components/schemas/V2ChartOfAccounts" + transactions: + $ref: "#/components/schemas/V2TransactionTemplates" required: - chart V2Schema: diff --git a/openapi/v2.yaml b/openapi/v2.yaml index 95124e5524..8e63dc1e5d 100644 --- a/openapi/v2.yaml +++ b/openapi/v2.yaml @@ -2371,6 +2371,9 @@ components: script: type: object properties: + template: + type: string + example: CUSTOMER_DEPOSIT plain: type: string example: "vars {\naccount $user\n}\nsend [COIN 10] (\n\tsource = @world\n\tdestination = $user\n)\n" @@ -2814,12 +2817,28 @@ components: users: $userID: .pattern: "^[0-9]{16}$" + V2TransactionTemplate: + type: object + properties: + id: + type: string + description: + type: string + script: + type: string + V2TransactionTemplates: + type: array + description: Transaction templates + items: + $ref: "#/components/schemas/V2TransactionTemplate" V2SchemaData: type: object description: Schema data structure for ledger schemas properties: chart: $ref: "#/components/schemas/V2ChartOfAccounts" + transactions: + $ref: "#/components/schemas/V2TransactionTemplates" required: - chart V2Schema: diff --git a/pkg/client/.speakeasy/gen.lock b/pkg/client/.speakeasy/gen.lock index 011f964ef5..629df5f59b 100644 --- a/pkg/client/.speakeasy/gen.lock +++ b/pkg/client/.speakeasy/gen.lock @@ -1,7 +1,7 @@ lockVersion: 2.0.0 id: a9ac79e1-e429-4ee3-96c4-ec973f19bec3 management: - docChecksum: ac202ae13d35aeafe6349dd1302aa143 + docChecksum: f44bd3cacac136edd368a7bccb00a9c7 docVersion: v2 speakeasyVersion: 1.563.0 generationVersion: 2.629.1 @@ -125,6 +125,7 @@ generatedFiles: - /models/components/v2targettype.go - /models/components/v2transaction.go - /models/components/v2transactionscursorresponse.go + - /models/components/v2transactiontemplate.go - /models/components/v2volume.go - /models/components/v2volumeswithbalance.go - /models/components/v2volumeswithbalancecursorresponse.go @@ -313,6 +314,7 @@ generatedFiles: - docs/models/components/v2transaction.md - docs/models/components/v2transactionscursorresponse.md - docs/models/components/v2transactionscursorresponsecursor.md + - docs/models/components/v2transactiontemplate.md - docs/models/components/v2volume.md - docs/models/components/v2volumeswithbalance.md - docs/models/components/v2volumeswithbalancecursorresponse.md @@ -934,7 +936,7 @@ examples: schemaVersion: "v1.0.0" force: true requestBody: - application/json: {"postings": [{"amount": 100, "asset": "COIN", "destination": "users:002", "source": "users:001"}], "script": {"plain": "vars {\naccount $user\n}\nsend [COIN 10] (\n\tsource = @world\n\tdestination = $user\n)\n", "vars": {"user": "users:042"}}, "reference": "ref:001", "metadata": {"admin": "true"}, "accountMetadata": {"key": {"admin": "true"}, "key1": {"admin": "true"}, "key2": {"admin": "true"}}} + application/json: {"postings": [{"amount": 100, "asset": "COIN", "destination": "users:002", "source": "users:001"}], "script": {"template": "CUSTOMER_DEPOSIT", "plain": "vars {\naccount $user\n}\nsend [COIN 10] (\n\tsource = @world\n\tdestination = $user\n)\n", "vars": {"user": "users:042"}}, "reference": "ref:001", "metadata": {"admin": "true"}, "accountMetadata": {"key": {"admin": "true"}, "key1": {"admin": "true"}, "key2": {"admin": "true"}}} responses: "200": application/json: {"data": {"timestamp": "2024-06-10T07:40:23.819Z", "postings": [{"amount": 100, "asset": "COIN", "destination": "users:002", "source": "users:001"}], "reference": "ref:001", "metadata": {"admin": "true"}, "id": 339560, "reverted": true, "preCommitVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "postCommitVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "preCommitEffectiveVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "postCommitEffectiveVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}}} diff --git a/pkg/client/.speakeasy/logs/naming.log b/pkg/client/.speakeasy/logs/naming.log index f47c4ad90a..cff16385ee 100644 --- a/pkg/client/.speakeasy/logs/naming.log +++ b/pkg/client/.speakeasy/logs/naming.log @@ -101,13 +101,14 @@ V2GetLedgerResponse (HttpMeta: HTTPMetadata, V2GetLedgerResponse: V2GetLedgerRes V2CreateLedgerRequest (ledger: string, V2CreateLedgerRequest: V2CreateLedgerRequest) V2CreateLedgerRequest (bucket: string, metadata: map, features: map) V2CreateLedgerResponse (HttpMeta: HTTPMetadata) -V2InsertSchemaRequest (ledger: string, version: string, Idempotency-Key: string ...) - V2SchemaData (chart: map) +V2InsertSchemaRequest (ledger: string, version: string, V2SchemaData: V2SchemaData) + V2SchemaData (chart: map, transactions: array) V2ChartSegment (.self: class, .pattern: string, .rules: V2ChartAccountRules ...) Self (empty) V2ChartAccountRules (empty) V2ChartAccountMetadata (default: string) -V2InsertSchemaResponse (HttpMeta: HTTPMetadata, Headers: map) + V2TransactionTemplate (id: string, description: string, script: string) +V2InsertSchemaResponse (HttpMeta: HTTPMetadata) V2GetSchemaRequest (ledger: string, version: string) V2GetSchemaResponse (HttpMeta: HTTPMetadata, V2SchemaResponse: V2SchemaResponse) V2SchemaResponse (data: V2Schema) @@ -135,7 +136,7 @@ V2CreateBulkRequest (ledger: string, continueOnFailure: boolean, atomic: boolean V2BulkElementCreateTransaction (action: string, ik: string, data: V2PostTransaction) V2PostTransaction (timestamp: date-time, postings: array, script: class ...) V2Posting (amount: bigint, asset: string, destination: string ...) - V2PostTransactionScript (plain: string, vars: map) + V2PostTransactionScript (template: string, plain: string, vars: map) Runtime (enum: experimental-interpreter, machine) V2BulkElementAddMetadata (action: string, ik: string, data: class) Data (targetId: V2TargetId, targetType: V2TargetType, metadata: map) diff --git a/pkg/client/docs/models/components/v2posttransactionscript.md b/pkg/client/docs/models/components/v2posttransactionscript.md index b92c259b4a..48b20a151b 100644 --- a/pkg/client/docs/models/components/v2posttransactionscript.md +++ b/pkg/client/docs/models/components/v2posttransactionscript.md @@ -5,5 +5,6 @@ | Field | Type | Required | Description | Example | | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | +| `Template` | **string* | :heavy_minus_sign: | N/A | CUSTOMER_DEPOSIT | | `Plain` | *string* | :heavy_check_mark: | N/A | vars {
account $user
}
send [COIN 10] (
source = @world
destination = $user
)
| | `Vars` | map[string]*string* | :heavy_minus_sign: | N/A | {
"user": "users:042"
} | \ No newline at end of file diff --git a/pkg/client/docs/models/components/v2schemadata.md b/pkg/client/docs/models/components/v2schemadata.md index 8a1e491a53..da2e4ff4d5 100644 --- a/pkg/client/docs/models/components/v2schemadata.md +++ b/pkg/client/docs/models/components/v2schemadata.md @@ -5,6 +5,7 @@ Schema data structure for ledger schemas ## Fields -| Field | Type | Required | Description | Example | -| --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | -| `Chart` | map[string][components.V2ChartSegment](../../models/components/v2chartsegment.md) | :heavy_check_mark: | Chart of account | {
"users": {
"$userID": {
".pattern": "^[0-9]{16}$"
}
}
} | \ No newline at end of file +| Field | Type | Required | Description | Example | +| -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | +| `Chart` | map[string][components.V2ChartSegment](../../models/components/v2chartsegment.md) | :heavy_check_mark: | Chart of account | {
"users": {
"$userID": {
".pattern": "^[0-9]{16}$"
}
}
} | +| `Transactions` | [][components.V2TransactionTemplate](../../models/components/v2transactiontemplate.md) | :heavy_minus_sign: | Transaction templates | | \ No newline at end of file diff --git a/pkg/client/docs/models/components/v2transactiontemplate.md b/pkg/client/docs/models/components/v2transactiontemplate.md new file mode 100644 index 0000000000..3bca0b9ea1 --- /dev/null +++ b/pkg/client/docs/models/components/v2transactiontemplate.md @@ -0,0 +1,10 @@ +# V2TransactionTemplate + + +## Fields + +| Field | Type | Required | Description | +| ------------------ | ------------------ | ------------------ | ------------------ | +| `ID` | **string* | :heavy_minus_sign: | N/A | +| `Description` | **string* | :heavy_minus_sign: | N/A | +| `Script` | **string* | :heavy_minus_sign: | N/A | \ 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 45f971e80e..e8c79f07a4 100644 --- a/pkg/client/docs/sdks/v2/README.md +++ b/pkg/client/docs/sdks/v2/README.md @@ -1223,6 +1223,7 @@ func main() { }, }, Script: &components.V2PostTransactionScript{ + Template: client.String("CUSTOMER_DEPOSIT"), Plain: "vars {\n" + "account $user\n" + "}\n" + diff --git a/pkg/client/models/components/v2posttransaction.go b/pkg/client/models/components/v2posttransaction.go index cbcd7e4532..1951120cb9 100644 --- a/pkg/client/models/components/v2posttransaction.go +++ b/pkg/client/models/components/v2posttransaction.go @@ -10,8 +10,16 @@ import ( ) type V2PostTransactionScript struct { - Plain string `json:"plain"` - Vars map[string]string `json:"vars,omitempty"` + Template *string `json:"template,omitempty"` + Plain string `json:"plain"` + Vars map[string]string `json:"vars,omitempty"` +} + +func (o *V2PostTransactionScript) GetTemplate() *string { + if o == nil { + return nil + } + return o.Template } func (o *V2PostTransactionScript) GetPlain() string { diff --git a/pkg/client/models/components/v2schemadata.go b/pkg/client/models/components/v2schemadata.go index c3ff516d1b..f51fb8d2b1 100644 --- a/pkg/client/models/components/v2schemadata.go +++ b/pkg/client/models/components/v2schemadata.go @@ -6,6 +6,8 @@ package components type V2SchemaData struct { // Chart of account Chart map[string]V2ChartSegment `json:"chart"` + // Transaction templates + Transactions []V2TransactionTemplate `json:"transactions,omitempty"` } func (o *V2SchemaData) GetChart() map[string]V2ChartSegment { @@ -14,3 +16,10 @@ func (o *V2SchemaData) GetChart() map[string]V2ChartSegment { } return o.Chart } + +func (o *V2SchemaData) GetTransactions() []V2TransactionTemplate { + if o == nil { + return nil + } + return o.Transactions +} diff --git a/pkg/client/models/components/v2transactiontemplate.go b/pkg/client/models/components/v2transactiontemplate.go new file mode 100644 index 0000000000..95ee23a8cd --- /dev/null +++ b/pkg/client/models/components/v2transactiontemplate.go @@ -0,0 +1,30 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package components + +type V2TransactionTemplate struct { + ID *string `json:"id,omitempty"` + Description *string `json:"description,omitempty"` + Script *string `json:"script,omitempty"` +} + +func (o *V2TransactionTemplate) GetID() *string { + if o == nil { + return nil + } + return o.ID +} + +func (o *V2TransactionTemplate) GetDescription() *string { + if o == nil { + return nil + } + return o.Description +} + +func (o *V2TransactionTemplate) GetScript() *string { + if o == nil { + return nil + } + return o.Script +} diff --git a/pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log b/pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log index f47c4ad90a..cff16385ee 100644 --- a/pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log +++ b/pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log @@ -101,13 +101,14 @@ V2GetLedgerResponse (HttpMeta: HTTPMetadata, V2GetLedgerResponse: V2GetLedgerRes V2CreateLedgerRequest (ledger: string, V2CreateLedgerRequest: V2CreateLedgerRequest) V2CreateLedgerRequest (bucket: string, metadata: map, features: map) V2CreateLedgerResponse (HttpMeta: HTTPMetadata) -V2InsertSchemaRequest (ledger: string, version: string, Idempotency-Key: string ...) - V2SchemaData (chart: map) +V2InsertSchemaRequest (ledger: string, version: string, V2SchemaData: V2SchemaData) + V2SchemaData (chart: map, transactions: array) V2ChartSegment (.self: class, .pattern: string, .rules: V2ChartAccountRules ...) Self (empty) V2ChartAccountRules (empty) V2ChartAccountMetadata (default: string) -V2InsertSchemaResponse (HttpMeta: HTTPMetadata, Headers: map) + V2TransactionTemplate (id: string, description: string, script: string) +V2InsertSchemaResponse (HttpMeta: HTTPMetadata) V2GetSchemaRequest (ledger: string, version: string) V2GetSchemaResponse (HttpMeta: HTTPMetadata, V2SchemaResponse: V2SchemaResponse) V2SchemaResponse (data: V2Schema) @@ -135,7 +136,7 @@ V2CreateBulkRequest (ledger: string, continueOnFailure: boolean, atomic: boolean V2BulkElementCreateTransaction (action: string, ik: string, data: V2PostTransaction) V2PostTransaction (timestamp: date-time, postings: array, script: class ...) V2Posting (amount: bigint, asset: string, destination: string ...) - V2PostTransactionScript (plain: string, vars: map) + V2PostTransactionScript (template: string, plain: string, vars: map) Runtime (enum: experimental-interpreter, machine) V2BulkElementAddMetadata (action: string, ik: string, data: class) Data (targetId: V2TargetId, targetType: V2TargetType, metadata: map) diff --git a/test/e2e/api_schema_test.go b/test/e2e/api_schema_test.go index 27f7720016..d502ab9d61 100644 --- a/test/e2e/api_schema_test.go +++ b/test/e2e/api_schema_test.go @@ -81,6 +81,20 @@ var _ = Context("Ledger schema API tests", func() { }, }, }, + Transactions: []components.V2TransactionTemplate{ + { + ID: pointer.For("TEST_TEMPLATE"), + Script: pointer.For(` + vars { + account $a + account $b + } + send [USD 100] ( + source = $a + destination = $b + )`), + }, + }, }, }) Expect(err).To(BeNil()) @@ -203,20 +217,12 @@ var _ = Context("Ledger schema API tests", func() { Ledger: "default", SchemaVersion: &schemaVersion, V2PostTransaction: components.V2PostTransaction{ - Force: pointer.For(true), - AccountMetadata: map[string]map[string]string{}, - Postings: []components.V2Posting{ - { - Source: "bank:001", - Destination: "users:001", - Amount: big.NewInt(100), - Asset: "USD", - }, - { - Source: "users:001", - Destination: "bank:001", - Amount: big.NewInt(100), - Asset: "USD", + Force: pointer.For(true), + Script: &components.V2PostTransactionScript{ + Template: pointer.For("TEST_TEMPLATE"), + Vars: map[string]string{ + "a": "world", + "b": "bank:001", }, }, }, @@ -459,11 +465,8 @@ var _ = Context("Ledger schema API tests", func() { }) Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumNotFound))) }) - }) - When("testing logs contain schema version", func() { - It("should include schema version in transaction logs", func(specContext SpecContext) { - // Create a transaction with schema version + It("should fail with a postings transaction", func(specContext SpecContext) { schemaVersion := "v1.0.0" _, err := Wait(specContext, DeferClient(testServer)).Ledger.V2.CreateTransaction(ctx, operations.V2CreateTransactionRequest{ Ledger: "default", @@ -486,6 +489,47 @@ var _ = Context("Ledger schema API tests", func() { }, }, }) + Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumValidation))) + }) + + It("should fail with an ad-hoc script transaction", func(specContext SpecContext) { + schemaVersion := "v1.0.0" + _, err := Wait(specContext, DeferClient(testServer)).Ledger.V2.CreateTransaction(ctx, operations.V2CreateTransactionRequest{ + Ledger: "default", + SchemaVersion: &schemaVersion, + V2PostTransaction: components.V2PostTransaction{ + Force: pointer.For(true), + Script: pointer.For(components.V2PostTransactionScript{ + Plain: ` +send [EUR/2] ( + source = $users:001 + destination = $bank:001 +)`, + }), + }, + }) + Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumValidation))) + }) + }) + + When("testing logs contain schema version", func() { + It("should include schema version in transaction logs", func(specContext SpecContext) { + // Create a transaction with schema version + schemaVersion := "v1.0.0" + _, err := Wait(specContext, DeferClient(testServer)).Ledger.V2.CreateTransaction(ctx, operations.V2CreateTransactionRequest{ + Ledger: "default", + SchemaVersion: &schemaVersion, + V2PostTransaction: components.V2PostTransaction{ + Force: pointer.For(true), + Script: &components.V2PostTransactionScript{ + Template: pointer.For("TEST_TEMPLATE"), + Vars: map[string]string{ + "a": "world", + "b": "users:001", + }, + }, + }, + }) Expect(err).To(BeNil()) // Get logs and verify schema version is included From 7c2b69689ece15616ee3b8702291c4efaa5f0e01 Mon Sep 17 00:00:00 2001 From: Azorlogh Date: Tue, 2 Dec 2025 15:25:38 +0100 Subject: [PATCH 02/14] logs --- docs/api/README.md | 108 +++++----- docs/events/CommittedTransactions.json | 6 +- docs/events/InsertedSchema.json | 19 -- docs/events/RevertedTransaction.json | 6 +- internal/README.md | 33 ++-- internal/api/v2/controllers_bulk_test.go | 3 + internal/api/v2/views_test.go | 8 + .../controller/ledger/controller_default.go | 3 +- .../ledger/controller_default_test.go | 10 + internal/machine/vm/machine.go | 1 - .../43-add-transaction-templates/notes.yaml | 1 + .../43-add-transaction-templates/up.sql | 8 + internal/transaction.go | 5 + pkg/client/.speakeasy/gen.lock | 2 +- pkg/client/.speakeasy/logs/naming.log | 2 +- pkg/client/docs/models/components/v2schema.md | 11 +- pkg/client/models/components/v2schema.go | 9 + .../.speakeasy/logs/naming.log | 2 +- test/e2e/api_schema_test.go | 185 ++++++++---------- 19 files changed, 220 insertions(+), 202 deletions(-) create mode 100644 internal/storage/bucket/migrations/43-add-transaction-templates/notes.yaml create mode 100644 internal/storage/bucket/migrations/43-add-transaction-templates/up.sql diff --git a/docs/api/README.md b/docs/api/README.md index d6e042570c..7b43affddd 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -428,15 +428,15 @@ Accept: application/json "$userID": { ".pattern": "^[0-9]{16}$" } - }, - "transactions": [ - { - "id": "string", - "description": "string", - "script": "string" - } - ] - } + } + }, + "transactions": [ + { + "id": "string", + "description": "string", + "script": "string" + } + ] } } ``` @@ -502,15 +502,15 @@ Accept: application/json "$userID": { ".pattern": "^[0-9]{16}$" } - }, - "transactions": [ - { - "id": "string", - "description": "string", - "script": "string" - } - ] - } + } + }, + "transactions": [ + { + "id": "string", + "description": "string", + "script": "string" + } + ] } ], "hasMore": true, @@ -5860,15 +5860,15 @@ Schema data structure for ledger schemas "$userID": { ".pattern": "^[0-9]{16}$" } - }, - "transactions": [ - { - "id": "string", - "description": "string", - "script": "string" - } - ] - } + } + }, + "transactions": [ + { + "id": "string", + "description": "string", + "script": "string" + } + ] } ``` @@ -5908,15 +5908,15 @@ and "$userID": { ".pattern": "^[0-9]{16}$" } - }, - "transactions": [ - { - "id": "string", - "description": "string", - "script": "string" - } - ] - } + } + }, + "transactions": [ + { + "id": "string", + "description": "string", + "script": "string" + } + ] } } @@ -5947,15 +5947,15 @@ and "$userID": { ".pattern": "^[0-9]{16}$" } - }, - "transactions": [ - { - "id": "string", - "description": "string", - "script": "string" - } - ] - } + } + }, + "transactions": [ + { + "id": "string", + "description": "string", + "script": "string" + } + ] } ], "hasMore": true, @@ -5991,15 +5991,15 @@ and "$userID": { ".pattern": "^[0-9]{16}$" } - }, - "transactions": [ - { - "id": "string", - "description": "string", - "script": "string" - } - ] - } + } + }, + "transactions": [ + { + "id": "string", + "description": "string", + "script": "string" + } + ] } ], "hasMore": true, diff --git a/docs/events/CommittedTransactions.json b/docs/events/CommittedTransactions.json index cff82aad38..80d6db65d5 100644 --- a/docs/events/CommittedTransactions.json +++ b/docs/events/CommittedTransactions.json @@ -113,6 +113,9 @@ "postCommitEffectiveVolumes": { "$ref": "#/$defs/PostCommitVolumes" }, + "template": { + "type": "string" + }, "reverted": { "type": "boolean" }, @@ -129,7 +132,8 @@ "postings", "metadata", "timestamp", - "id" + "id", + "template" ] }, "Volumes": { diff --git a/docs/events/InsertedSchema.json b/docs/events/InsertedSchema.json index 9f408c9f3b..ef49b13810 100644 --- a/docs/events/InsertedSchema.json +++ b/docs/events/InsertedSchema.json @@ -140,8 +140,6 @@ "type": "string", "format": "date-time", "title": "Normalized date" -<<<<<<< HEAD:docs/events/InsertedSchema.json -======= }, "TransactionTemplate": { "properties": { @@ -168,23 +166,6 @@ "$ref": "#/$defs/TransactionTemplate" }, "type": "object" - }, - "UpdatedSchema": { - "properties": { - "ledger": { - "type": "string" - }, - "schema": { - "$ref": "#/$defs/Schema" - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "ledger", - "schema" - ] ->>>>>>> 4a8518f7 (wip):docs/events/UpdatedSchema.json } } } \ No newline at end of file diff --git a/docs/events/RevertedTransaction.json b/docs/events/RevertedTransaction.json index 2fd4ddfd51..664bf78728 100644 --- a/docs/events/RevertedTransaction.json +++ b/docs/events/RevertedTransaction.json @@ -107,6 +107,9 @@ "postCommitEffectiveVolumes": { "$ref": "#/$defs/PostCommitVolumes" }, + "template": { + "type": "string" + }, "reverted": { "type": "boolean" }, @@ -123,7 +126,8 @@ "postings", "metadata", "timestamp", - "id" + "id", + "template" ] }, "Volumes": { diff --git a/internal/README.md b/internal/README.md index 49e7952b45..66035466b4 100644 --- a/internal/README.md +++ b/internal/README.md @@ -165,6 +165,7 @@ import "github.com/formancehq/ledger/internal" - [func \(tx Transaction\) WithPostings\(postings ...Posting\) Transaction](<#Transaction.WithPostings>) - [func \(tx Transaction\) WithReference\(ref string\) Transaction](<#Transaction.WithReference>) - [func \(tx Transaction\) WithRevertedAt\(timestamp time.Time\) Transaction](<#Transaction.WithRevertedAt>) + - [func \(tx Transaction\) WithTemplate\(template string\) Transaction](<#Transaction.WithTemplate>) - [func \(tx Transaction\) WithTimestamp\(ts time.Time\) Transaction](<#Transaction.WithTimestamp>) - [func \(tx Transaction\) WithUpdatedAt\(at time.Time\) Transaction](<#Transaction.WithUpdatedAt>) - [type TransactionData](<#TransactionData>) @@ -1665,7 +1666,7 @@ type SchemaData struct { ``` -## type [Transaction]() +## type [Transaction]() @@ -1684,11 +1685,12 @@ type Transaction struct { // PostCommitEffectiveVolumes are the volumes of each account/asset after the transaction TransactionData.Timestamp. // Those volumes are also computed in flight, but can be updated if a transaction is inserted in the past. PostCommitEffectiveVolumes PostCommitVolumes `json:"postCommitEffectiveVolumes,omitempty" bun:"post_commit_effective_volumes,type:jsonb,scanonly"` + Template string `json:"template" bun:"template,type:text"` } ``` -### func [NewTransaction]() +### func [NewTransaction]() ```go func NewTransaction() Transaction @@ -1706,7 +1708,7 @@ func (tx *Transaction) AccountsWithDefaultMetadata(schema *Schema, accountMetada -### func \(Transaction\) [InvolvedAccounts]() +### func \(Transaction\) [InvolvedAccounts]() ```go func (tx Transaction) InvolvedAccounts() []string @@ -1715,7 +1717,7 @@ func (tx Transaction) InvolvedAccounts() []string -### func \(Transaction\) [InvolvedDestinations]() +### func \(Transaction\) [InvolvedDestinations]() ```go func (tx Transaction) InvolvedDestinations() map[string][]string @@ -1724,7 +1726,7 @@ func (tx Transaction) InvolvedDestinations() map[string][]string -### func \(Transaction\) [IsReverted]() +### func \(Transaction\) [IsReverted]() ```go func (tx Transaction) IsReverted() bool @@ -1742,7 +1744,7 @@ func (Transaction) JSONSchemaExtend(schema *jsonschema.Schema) -### func \(Transaction\) [MarshalJSON]() +### func \(Transaction\) [MarshalJSON]() ```go func (tx Transaction) MarshalJSON() ([]byte, error) @@ -1760,7 +1762,7 @@ func (tx Transaction) Reverse() Transaction -### func \(Transaction\) [VolumeUpdates]() +### func \(Transaction\) [VolumeUpdates]() ```go func (tx Transaction) VolumeUpdates() []AccountsVolumes @@ -1796,7 +1798,7 @@ func (tx Transaction) WithMetadata(m metadata.Metadata) Transaction -### func \(Transaction\) [WithPostCommitEffectiveVolumes]() +### func \(Transaction\) [WithPostCommitEffectiveVolumes]() ```go func (tx Transaction) WithPostCommitEffectiveVolumes(volumes PostCommitVolumes) Transaction @@ -1805,7 +1807,7 @@ func (tx Transaction) WithPostCommitEffectiveVolumes(volumes PostCommitVolumes) -### func \(Transaction\) [WithPostCommitVolumes]() +### func \(Transaction\) [WithPostCommitVolumes]() ```go func (tx Transaction) WithPostCommitVolumes(volumes PostCommitVolumes) Transaction @@ -1832,7 +1834,7 @@ func (tx Transaction) WithReference(ref string) Transaction -### func \(Transaction\) [WithRevertedAt]() +### func \(Transaction\) [WithRevertedAt]() ```go func (tx Transaction) WithRevertedAt(timestamp time.Time) Transaction @@ -1840,6 +1842,15 @@ func (tx Transaction) WithRevertedAt(timestamp time.Time) Transaction + +### func \(Transaction\) [WithTemplate]() + +```go +func (tx Transaction) WithTemplate(template string) Transaction +``` + + + ### func \(Transaction\) [WithTimestamp]() @@ -1850,7 +1861,7 @@ func (tx Transaction) WithTimestamp(ts time.Time) Transaction -### func \(Transaction\) [WithUpdatedAt]() +### func \(Transaction\) [WithUpdatedAt]() ```go func (tx Transaction) WithUpdatedAt(at time.Time) Transaction diff --git a/internal/api/v2/controllers_bulk_test.go b/internal/api/v2/controllers_bulk_test.go index 05b23607e1..94960b8f38 100644 --- a/internal/api/v2/controllers_bulk_test.go +++ b/internal/api/v2/controllers_bulk_test.go @@ -100,6 +100,7 @@ func TestBulk(t *testing.T) { "metadata": map[string]any{}, "reverted": false, "id": float64(0), + "template": "", }, ResponseType: bulking.ActionCreateTransaction, }}, @@ -209,6 +210,7 @@ func TestBulk(t *testing.T) { }, "postings": nil, "reverted": false, + "template": "", "timestamp": "0001-01-01T00:00:00Z", }, ResponseType: bulking.ActionRevertTransaction, @@ -552,6 +554,7 @@ func TestBulk(t *testing.T) { "metadata": map[string]any{}, "reverted": false, "id": float64(0), + "template": "", }, ResponseType: bulking.ActionCreateTransaction, }}, diff --git a/internal/api/v2/views_test.go b/internal/api/v2/views_test.go index 1835d38f43..7ff81ffd51 100644 --- a/internal/api/v2/views_test.go +++ b/internal/api/v2/views_test.go @@ -153,6 +153,7 @@ func TestTransactionRender(t *testing.T) { }, }, }, + "template": "", }, }, { @@ -243,6 +244,7 @@ func TestTransactionRender(t *testing.T) { }, }, }, + "template": "", }, }, } { @@ -485,6 +487,7 @@ func TestLogRender(t *testing.T) { "updatedAt": now.Format(time.RFC3339Nano), "id": nil, "reverted": false, + "template": "", }, "accountMetadata": nil, }, @@ -528,6 +531,7 @@ func TestLogRender(t *testing.T) { "updatedAt": now.Format(time.RFC3339Nano), "id": nil, "reverted": false, + "template": "", }, "accountMetadata": nil, }, @@ -575,6 +579,7 @@ func TestLogRender(t *testing.T) { "updatedAt": now.Format(time.RFC3339Nano), "id": float64(1), "reverted": false, + "template": "", }, "transaction": map[string]any{ "postings": []any{ @@ -591,6 +596,7 @@ func TestLogRender(t *testing.T) { "updatedAt": now.Format(time.RFC3339Nano), "id": float64(2), "reverted": false, + "template": "", }, }, "date": now.Format(time.RFC3339Nano), @@ -640,6 +646,7 @@ func TestLogRender(t *testing.T) { "updatedAt": now.Format(time.RFC3339Nano), "id": float64(1), "reverted": false, + "template": "", }, "transaction": map[string]any{ "postings": []any{ @@ -656,6 +663,7 @@ func TestLogRender(t *testing.T) { "updatedAt": now.Format(time.RFC3339Nano), "id": float64(2), "reverted": false, + "template": "", }, }, "date": now.Format(time.RFC3339Nano), diff --git a/internal/controller/ledger/controller_default.go b/internal/controller/ledger/controller_default.go index 5770a8478a..f75af59f74 100644 --- a/internal/controller/ledger/controller_default.go +++ b/internal/controller/ledger/controller_default.go @@ -482,7 +482,8 @@ func (ctrl *DefaultController) createTransaction(ctx context.Context, store Stor WithPostings(result.Postings...). WithMetadata(finalMetadata). WithTimestamp(parameters.Input.Timestamp). - WithReference(parameters.Input.Reference) + WithReference(parameters.Input.Reference). + WithTemplate(parameters.Input.Template) err = store.CommitTransaction(ctx, &transaction) if err != nil { return nil, err diff --git a/internal/controller/ledger/controller_default_test.go b/internal/controller/ledger/controller_default_test.go index f962392fe5..17b7c6554a 100644 --- a/internal/controller/ledger/controller_default_test.go +++ b/internal/controller/ledger/controller_default_test.go @@ -121,10 +121,20 @@ send [EUR/2 100] ( Account: &ledger.ChartAccount{}, }, }, + Transactions: ledger.TransactionTemplates{ + "TRANSFER": { + Description: "Test tx template", + Script: script, + }, + }, }, Version: schemaVersion, } + parser.EXPECT(). + Parse(script). + Return(numscriptRuntime, nil) + store.EXPECT(). BeginTX(gomock.Any(), nil). Return(store, &bun.Tx{}, nil) diff --git a/internal/machine/vm/machine.go b/internal/machine/vm/machine.go index 83f9bba4e4..f9c29f5584 100644 --- a/internal/machine/vm/machine.go +++ b/internal/machine/vm/machine.go @@ -469,7 +469,6 @@ func (m *Machine) tick() (bool, error) { } func (m *Machine) Execute() error { - fmt.Printf("running machine!") go m.Printer(m.printChan) defer close(m.printChan) diff --git a/internal/storage/bucket/migrations/43-add-transaction-templates/notes.yaml b/internal/storage/bucket/migrations/43-add-transaction-templates/notes.yaml new file mode 100644 index 0000000000..b36c69d930 --- /dev/null +++ b/internal/storage/bucket/migrations/43-add-transaction-templates/notes.yaml @@ -0,0 +1 @@ +name: Add template column to transactions diff --git a/internal/storage/bucket/migrations/43-add-transaction-templates/up.sql b/internal/storage/bucket/migrations/43-add-transaction-templates/up.sql new file mode 100644 index 0000000000..408ef5dc6d --- /dev/null +++ b/internal/storage/bucket/migrations/43-add-transaction-templates/up.sql @@ -0,0 +1,8 @@ +do $$ + begin + set search_path = '{{ .Schema }}'; + + alter table transactions + add column template text; + end +$$; diff --git a/internal/transaction.go b/internal/transaction.go index 01ce42f881..bd92b9f458 100644 --- a/internal/transaction.go +++ b/internal/transaction.go @@ -49,6 +49,7 @@ type Transaction struct { // PostCommitEffectiveVolumes are the volumes of each account/asset after the transaction TransactionData.Timestamp. // Those volumes are also computed in flight, but can be updated if a transaction is inserted in the past. PostCommitEffectiveVolumes PostCommitVolumes `json:"postCommitEffectiveVolumes,omitempty" bun:"post_commit_effective_volumes,type:jsonb,scanonly"` + Template string `json:"template" bun:"template,type:text"` } func (Transaction) JSONSchemaExtend(schema *jsonschema.Schema) { @@ -92,7 +93,11 @@ func (tx Transaction) WithMetadata(m metadata.Metadata) Transaction { func (tx Transaction) WithInsertedAt(date time.Time) Transaction { tx.InsertedAt = date + return tx +} +func (tx Transaction) WithTemplate(template string) Transaction { + tx.Template = template return tx } diff --git a/pkg/client/.speakeasy/gen.lock b/pkg/client/.speakeasy/gen.lock index 629df5f59b..111aba34ab 100644 --- a/pkg/client/.speakeasy/gen.lock +++ b/pkg/client/.speakeasy/gen.lock @@ -1,7 +1,7 @@ lockVersion: 2.0.0 id: a9ac79e1-e429-4ee3-96c4-ec973f19bec3 management: - docChecksum: f44bd3cacac136edd368a7bccb00a9c7 + docChecksum: 2a7e02c05a30e75ae13f2c8269ca4cca docVersion: v2 speakeasyVersion: 1.563.0 generationVersion: 2.629.1 diff --git a/pkg/client/.speakeasy/logs/naming.log b/pkg/client/.speakeasy/logs/naming.log index cff16385ee..6feea579c0 100644 --- a/pkg/client/.speakeasy/logs/naming.log +++ b/pkg/client/.speakeasy/logs/naming.log @@ -112,7 +112,7 @@ V2InsertSchemaResponse (HttpMeta: HTTPMetadata) V2GetSchemaRequest (ledger: string, version: string) V2GetSchemaResponse (HttpMeta: HTTPMetadata, V2SchemaResponse: V2SchemaResponse) V2SchemaResponse (data: V2Schema) - V2Schema (version: string, createdAt: date-time, chart: map) + V2Schema (version: string, createdAt: date-time, chart: map ...) V2ListSchemasRequest (ledger: string, cursor: string, pageSize: integer ...) Sort (enum: created_at) Order (enum: asc, desc) diff --git a/pkg/client/docs/models/components/v2schema.md b/pkg/client/docs/models/components/v2schema.md index ea2b07070f..05750baea6 100644 --- a/pkg/client/docs/models/components/v2schema.md +++ b/pkg/client/docs/models/components/v2schema.md @@ -5,8 +5,9 @@ Complete schema structure with metadata ## Fields -| Field | Type | Required | Description | Example | -| --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | -| `Version` | *string* | :heavy_check_mark: | Schema version | v1.0.0 | -| `CreatedAt` | [time.Time](https://pkg.go.dev/time#Time) | :heavy_check_mark: | Schema creation timestamp | 2023-01-01T00:00:00Z | -| `Chart` | map[string][components.V2ChartSegment](../../models/components/v2chartsegment.md) | :heavy_check_mark: | Chart of account | {
"users": {
"$userID": {
".pattern": "^[0-9]{16}$"
}
}
} | \ No newline at end of file +| Field | Type | Required | Description | Example | +| -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | +| `Version` | *string* | :heavy_check_mark: | Schema version | v1.0.0 | +| `CreatedAt` | [time.Time](https://pkg.go.dev/time#Time) | :heavy_check_mark: | Schema creation timestamp | 2023-01-01T00:00:00Z | +| `Chart` | map[string][components.V2ChartSegment](../../models/components/v2chartsegment.md) | :heavy_check_mark: | Chart of account | {
"users": {
"$userID": {
".pattern": "^[0-9]{16}$"
}
}
} | +| `Transactions` | [][components.V2TransactionTemplate](../../models/components/v2transactiontemplate.md) | :heavy_minus_sign: | Transaction templates | | \ No newline at end of file diff --git a/pkg/client/models/components/v2schema.go b/pkg/client/models/components/v2schema.go index 3a2e3564af..d65b15a239 100644 --- a/pkg/client/models/components/v2schema.go +++ b/pkg/client/models/components/v2schema.go @@ -15,6 +15,8 @@ type V2Schema struct { CreatedAt time.Time `json:"createdAt"` // Chart of account Chart map[string]V2ChartSegment `json:"chart"` + // Transaction templates + Transactions []V2TransactionTemplate `json:"transactions,omitempty"` } func (v V2Schema) MarshalJSON() ([]byte, error) { @@ -48,3 +50,10 @@ func (o *V2Schema) GetChart() map[string]V2ChartSegment { } return o.Chart } + +func (o *V2Schema) GetTransactions() []V2TransactionTemplate { + if o == nil { + return nil + } + return o.Transactions +} diff --git a/pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log b/pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log index cff16385ee..6feea579c0 100644 --- a/pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log +++ b/pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log @@ -112,7 +112,7 @@ V2InsertSchemaResponse (HttpMeta: HTTPMetadata) V2GetSchemaRequest (ledger: string, version: string) V2GetSchemaResponse (HttpMeta: HTTPMetadata, V2SchemaResponse: V2SchemaResponse) V2SchemaResponse (data: V2Schema) - V2Schema (version: string, createdAt: date-time, chart: map) + V2Schema (version: string, createdAt: date-time, chart: map ...) V2ListSchemasRequest (ledger: string, cursor: string, pageSize: integer ...) Sort (enum: created_at) Order (enum: asc, desc) diff --git a/test/e2e/api_schema_test.go b/test/e2e/api_schema_test.go index d502ab9d61..93964e22a7 100644 --- a/test/e2e/api_schema_test.go +++ b/test/e2e/api_schema_test.go @@ -54,12 +54,42 @@ var _ = Context("Ledger schema API tests", func() { When("inserting schemas with different validation rules", func() { BeforeEach(func(specContext SpecContext) { + transactionTemplates := []components.V2TransactionTemplate{ + { + ID: pointer.For("WORLD_TO_BANK"), + Script: pointer.For(` + vars { + account $b + } + send [USD 100] ( + source = @world + destination = $b + )`), + }, + { + ID: pointer.For("A_TO_B"), + Script: pointer.For(` + vars { + account $a + account $b + } + send [USD 100] ( + source = $a allowing unbounded overdraft + destination = $b + ) + `), + }, + } + // Schema v1.0.0 - Basic validation _, err := Wait(specContext, DeferClient(testServer)).Ledger.V2.InsertSchema(ctx, operations.V2InsertSchemaRequest{ Ledger: "default", Version: "v1.0.0", V2SchemaData: components.V2SchemaData{ Chart: map[string]components.V2ChartSegment{ + "world": { + DotSelf: &components.DotSelf{}, + }, "users": { AdditionalProperties: map[string]components.V2ChartSegment{ "$userID": { @@ -81,20 +111,7 @@ var _ = Context("Ledger schema API tests", func() { }, }, }, - Transactions: []components.V2TransactionTemplate{ - { - ID: pointer.For("TEST_TEMPLATE"), - Script: pointer.For(` - vars { - account $a - account $b - } - send [USD 100] ( - source = $a - destination = $b - )`), - }, - }, + Transactions: transactionTemplates, }, }) Expect(err).To(BeNil()) @@ -124,6 +141,7 @@ var _ = Context("Ledger schema API tests", func() { }, }, }, + Transactions: transactionTemplates, }, }) Expect(err).To(BeNil()) @@ -153,6 +171,7 @@ var _ = Context("Ledger schema API tests", func() { }, }, }, + Transactions: transactionTemplates, }, }) Expect(err).To(BeNil()) @@ -219,9 +238,8 @@ var _ = Context("Ledger schema API tests", func() { V2PostTransaction: components.V2PostTransaction{ Force: pointer.For(true), Script: &components.V2PostTransactionScript{ - Template: pointer.For("TEST_TEMPLATE"), + Template: pointer.For("WORLD_TO_BANK"), Vars: map[string]string{ - "a": "world", "b": "bank:001", }, }, @@ -237,18 +255,11 @@ var _ = Context("Ledger schema API tests", func() { SchemaVersion: &schemaVersion, V2PostTransaction: components.V2PostTransaction{ Force: pointer.For(true), - Postings: []components.V2Posting{ - { - Source: "bank:001", - Destination: "users:001", - Amount: big.NewInt(100), - Asset: "USD", - }, - { - Source: "users:001", - Destination: "bank:001", - Amount: big.NewInt(100), - Asset: "USD", + Script: &components.V2PostTransactionScript{ + Template: pointer.For("A_TO_B"), + Vars: map[string]string{ + "a": "bank:001", + "b": "users:001", }, }, Metadata: map[string]string{ @@ -266,18 +277,11 @@ var _ = Context("Ledger schema API tests", func() { SchemaVersion: &schemaVersion, V2PostTransaction: components.V2PostTransaction{ Force: pointer.For(true), - Postings: []components.V2Posting{ - { - Source: "bank:001", - Destination: "users:001", - Amount: big.NewInt(100), - Asset: "USD", - }, - { - Source: "users:001", - Destination: "bank:001", - Amount: big.NewInt(100), - Asset: "USD", + Script: &components.V2PostTransactionScript{ + Template: pointer.For("A_TO_B"), + Vars: map[string]string{ + "a": "bank:001", + "b": "users:001", }, }, }, @@ -292,18 +296,11 @@ var _ = Context("Ledger schema API tests", func() { SchemaVersion: &schemaVersion, V2PostTransaction: components.V2PostTransaction{ Force: pointer.For(true), - Postings: []components.V2Posting{ - { - Source: "bank", - Destination: "users:001", - Amount: big.NewInt(100), - Asset: "USD", - }, - { - Source: "users:001", - Destination: "bank", - Amount: big.NewInt(100), - Asset: "USD", + Script: &components.V2PostTransactionScript{ + Template: pointer.For("A_TO_B"), + Vars: map[string]string{ + "a": "bank", + "b": "users:001", }, }, }, @@ -323,18 +320,11 @@ var _ = Context("Ledger schema API tests", func() { "bar": "test2", }, }, - Postings: []components.V2Posting{ - { - Source: "bank:001", - Destination: "users:001", - Amount: big.NewInt(100), - Asset: "USD", - }, - { - Source: "users:001", - Destination: "bank:001", - Amount: big.NewInt(100), - Asset: "USD", + Script: &components.V2PostTransactionScript{ + Template: pointer.For("A_TO_B"), + Vars: map[string]string{ + "a": "bank:001", + "b": "users:001", }, }, }, @@ -375,12 +365,11 @@ var _ = Context("Ledger schema API tests", func() { "foo": "preexisting_value", }, }, - Postings: []components.V2Posting{ - { - Source: "bank:001", - Destination: "users:001", - Amount: big.NewInt(100), - Asset: "USD", + Script: &components.V2PostTransactionScript{ + Template: pointer.For("A_TO_B"), + Vars: map[string]string{ + "a": "bank:001", + "b": "users:001", }, }, }, @@ -393,12 +382,11 @@ var _ = Context("Ledger schema API tests", func() { V2PostTransaction: components.V2PostTransaction{ Force: pointer.For(true), AccountMetadata: map[string]map[string]string{}, - Postings: []components.V2Posting{ - { - Source: "bank:001", - Destination: "users:001", - Amount: big.NewInt(100), - Asset: "USD", + Script: &components.V2PostTransactionScript{ + Template: pointer.For("A_TO_B"), + Vars: map[string]string{ + "a": "bank:001", + "b": "users:001", }, }, }, @@ -421,18 +409,11 @@ var _ = Context("Ledger schema API tests", func() { SchemaVersion: nil, V2PostTransaction: components.V2PostTransaction{ Force: pointer.For(true), - Postings: []components.V2Posting{ - { - Source: "bank:001", - Destination: "users:001", - Amount: big.NewInt(100), - Asset: "USD", - }, - { - Source: "users:001", - Destination: "bank:001", - Amount: big.NewInt(100), - Asset: "USD", + Script: &components.V2PostTransactionScript{ + Template: pointer.For("A_TO_B"), + Vars: map[string]string{ + "a": "bank:001", + "b": "users:001", }, }, }, @@ -447,18 +428,11 @@ var _ = Context("Ledger schema API tests", func() { SchemaVersion: &schemaVersion, V2PostTransaction: components.V2PostTransaction{ Force: pointer.For(true), - Postings: []components.V2Posting{ - { - Source: "bank:001", - Destination: "users:001", - Amount: big.NewInt(100), - Asset: "USD", - }, - { - Source: "users:001", - Destination: "bank:001", - Amount: big.NewInt(100), - Asset: "USD", + Script: &components.V2PostTransactionScript{ + Template: pointer.For("A_TO_B"), + Vars: map[string]string{ + "a": "bank:001", + "b": "users:001", }, }, }, @@ -501,10 +475,10 @@ var _ = Context("Ledger schema API tests", func() { Force: pointer.For(true), Script: pointer.For(components.V2PostTransactionScript{ Plain: ` -send [EUR/2] ( - source = $users:001 - destination = $bank:001 -)`, + send [EUR/2] ( + source = $users:001 + destination = $bank:001 + )`, }), }, }) @@ -522,9 +496,8 @@ send [EUR/2] ( V2PostTransaction: components.V2PostTransaction{ Force: pointer.For(true), Script: &components.V2PostTransactionScript{ - Template: pointer.For("TEST_TEMPLATE"), + Template: pointer.For("WORLD_TO_BANK"), Vars: map[string]string{ - "a": "world", "b": "users:001", }, }, From 7f47235fdc54d87fe720721fde2b7c5c83f870d4 Mon Sep 17 00:00:00 2001 From: Azorlogh Date: Fri, 5 Dec 2025 15:15:19 +0100 Subject: [PATCH 03/14] use map, fix tx template readback, runtime overwriting --- docs/api/README.md | 224 ++++++++++++------ internal/README.md | 20 +- internal/api/bulking/elements.go | 2 +- internal/controller/ledger/controller.go | 9 +- .../controller/ledger/controller_default.go | 11 +- .../storage/ledger/resource_transactions.go | 1 + internal/transaction_templates.go | 49 +--- openapi.yaml | 25 +- openapi/v2.yaml | 25 +- pkg/client/.speakeasy/gen.lock | 11 +- pkg/client/.speakeasy/logs/naming.log | 6 +- pkg/client/docs/models/components/v2schema.md | 12 +- .../docs/models/components/v2schemadata.md | 8 +- .../docs/models/components/v2transaction.md | 3 +- .../components/v2transactiontemplate.md | 10 +- pkg/client/models/components/runtime.go | 35 +++ .../models/components/v2posttransaction.go | 29 --- pkg/client/models/components/v2schema.go | 4 +- pkg/client/models/components/v2schemadata.go | 4 +- pkg/client/models/components/v2transaction.go | 8 + .../components/v2transactiontemplate.go | 19 +- .../.speakeasy/logs/naming.log | 6 +- test/e2e/api_schema_test.go | 51 ++-- 23 files changed, 320 insertions(+), 252 deletions(-) create mode 100644 pkg/client/models/components/runtime.go diff --git a/docs/api/README.md b/docs/api/README.md index 7b43affddd..8a5a4f6f99 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -343,13 +343,18 @@ Idempotency-Key: string } } }, - "transactions": [ - { - "id": "string", + "transactions": { + "property1": { "description": "string", - "script": "string" + "script": "string", + "runtime": "experimental-interpreter" + }, + "property2": { + "description": "string", + "script": "string", + "runtime": "experimental-interpreter" } - ] + } } ``` @@ -430,13 +435,18 @@ Accept: application/json } } }, - "transactions": [ - { - "id": "string", + "transactions": { + "property1": { "description": "string", - "script": "string" + "script": "string", + "runtime": "experimental-interpreter" + }, + "property2": { + "description": "string", + "script": "string", + "runtime": "experimental-interpreter" } - ] + } } } ``` @@ -504,13 +514,18 @@ Accept: application/json } } }, - "transactions": [ - { - "id": "string", + "transactions": { + "property1": { + "description": "string", + "script": "string", + "runtime": "experimental-interpreter" + }, + "property2": { "description": "string", - "script": "string" + "script": "string", + "runtime": "experimental-interpreter" } - ] + } } ], "hasMore": true, @@ -852,7 +867,8 @@ Accept: application/json "balance": 90 } } - } + }, + "template": "string" } } ], @@ -1521,7 +1537,8 @@ Format: `:`, where `` is the field name and `` is ei "balance": 90 } } - } + }, + "template": "string" } ] } @@ -1699,7 +1716,8 @@ Idempotency-Key: string "balance": 90 } } - } + }, + "template": "string" } } ``` @@ -1834,7 +1852,8 @@ Accept: application/json "balance": 90 } } - } + }, + "template": "string" } } ``` @@ -2048,7 +2067,8 @@ Idempotency-Key: string "balance": 90 } } - } + }, + "template": "string" } } ``` @@ -3523,7 +3543,8 @@ This operation does not require authentication "balance": 90 } } - } + }, + "template": "string" } ] } @@ -3960,7 +3981,8 @@ This operation does not require authentication "balance": 90 } } - } + }, + "template": "string" } ``` @@ -3982,6 +4004,7 @@ This operation does not require authentication |postCommitVolumes|[V2AggregatedVolumes](#schemav2aggregatedvolumes)|false|none|none| |preCommitEffectiveVolumes|[V2AggregatedVolumes](#schemav2aggregatedvolumes)|false|none|none| |postCommitEffectiveVolumes|[V2AggregatedVolumes](#schemav2aggregatedvolumes)|false|none|none| +|template|string|true|none|none|

V2PostTransaction

@@ -4037,20 +4060,13 @@ This operation does not require authentication |» plain|string|true|none|none| |» vars|object|false|none|none| |»» **additionalProperties**|string|false|none|none| -|runtime|string|false|none|The numscript runtime used to execute the script. Uses "machine" by default, unless the "--experimental-numscript-interpreter" feature flag is passed.| +|runtime|[Runtime](#schemaruntime)|false|none|The numscript runtime used to execute the script. Uses "machine" by default, unless the "--experimental-numscript-interpreter" feature flag is passed.| |reference|string|false|none|none| |metadata|[V2Metadata](#schemav2metadata)|true|none|none| |accountMetadata|object|false|none|none| |» **additionalProperties**|[V2Metadata](#schemav2metadata)|false|none|none| |force|boolean|false|none|none| -#### Enumerated Values - -|Property|Value| -|---|---| -|runtime|experimental-interpreter| -|runtime|machine| -

V2Stats

@@ -4204,7 +4220,8 @@ This operation does not require authentication "balance": 90 } } - } + }, + "template": "string" } } @@ -4307,7 +4324,8 @@ This operation does not require authentication "balance": 90 } } - } + }, + "template": "string" } } @@ -4408,7 +4426,8 @@ This operation does not require authentication "balance": 90 } } - } + }, + "template": "string" } } @@ -5193,7 +5212,8 @@ and "balance": 90 } } - } + }, + "template": "string" } } ], @@ -5304,7 +5324,8 @@ and "balance": 90 } } - } + }, + "template": "string" } } @@ -5457,7 +5478,8 @@ xor "balance": 90 } } - } + }, + "template": "string" } } @@ -5590,7 +5612,8 @@ and "balance": 90 } } - } + }, + "template": "string" } } @@ -5758,6 +5781,33 @@ Chart of account |---|---|---|---|---| |**additionalProperties**|[V2ChartSegment](#schemav2chartsegment)|false|none|Segment within a chart of accounts| +

Runtime

+ + + + + + +```json +"experimental-interpreter" + +``` + +The numscript runtime used to execute the script. Uses "machine" by default, unless the "--experimental-numscript-interpreter" feature flag is passed. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|*anonymous*|string|false|none|The numscript runtime used to execute the script. Uses "machine" by default, unless the "--experimental-numscript-interpreter" feature flag is passed.| + +#### Enumerated Values + +|Property|Value| +|---|---| +|*anonymous*|experimental-interpreter| +|*anonymous*|machine| +

V2TransactionTemplate

@@ -5767,9 +5817,9 @@ Chart of account ```json { - "id": "string", "description": "string", - "script": "string" + "script": "string", + "runtime": "experimental-interpreter" } ``` @@ -5778,9 +5828,9 @@ Chart of account |Name|Type|Required|Restrictions|Description| |---|---|---|---|---| -|id|string|false|none|none| |description|string|false|none|none| -|script|string|false|none|none| +|script|string|true|none|none| +|runtime|[Runtime](#schemaruntime)|false|none|The numscript runtime used to execute the script. Uses "machine" by default, unless the "--experimental-numscript-interpreter" feature flag is passed.|

V2TransactionTemplates

@@ -5790,13 +5840,18 @@ Chart of account ```json -[ - { - "id": "string", +{ + "property1": { "description": "string", - "script": "string" + "script": "string", + "runtime": "experimental-interpreter" + }, + "property2": { + "description": "string", + "script": "string", + "runtime": "experimental-interpreter" } -] +} ``` @@ -5806,7 +5861,7 @@ Transaction templates |Name|Type|Required|Restrictions|Description| |---|---|---|---|---| -|*anonymous*|[[V2TransactionTemplate](#schemav2transactiontemplate)]|false|none|Transaction templates| +|**additionalProperties**|[V2TransactionTemplate](#schemav2transactiontemplate)|false|none|none|

V2SchemaData

@@ -5824,13 +5879,18 @@ Transaction templates } } }, - "transactions": [ - { - "id": "string", + "transactions": { + "property1": { + "description": "string", + "script": "string", + "runtime": "experimental-interpreter" + }, + "property2": { "description": "string", - "script": "string" + "script": "string", + "runtime": "experimental-interpreter" } - ] + } } ``` @@ -5862,13 +5922,18 @@ Schema data structure for ledger schemas } } }, - "transactions": [ - { - "id": "string", + "transactions": { + "property1": { "description": "string", - "script": "string" + "script": "string", + "runtime": "experimental-interpreter" + }, + "property2": { + "description": "string", + "script": "string", + "runtime": "experimental-interpreter" } - ] + } } ``` @@ -5910,13 +5975,18 @@ and } } }, - "transactions": [ - { - "id": "string", + "transactions": { + "property1": { "description": "string", - "script": "string" + "script": "string", + "runtime": "experimental-interpreter" + }, + "property2": { + "description": "string", + "script": "string", + "runtime": "experimental-interpreter" } - ] + } } } @@ -5949,13 +6019,18 @@ and } } }, - "transactions": [ - { - "id": "string", + "transactions": { + "property1": { + "description": "string", + "script": "string", + "runtime": "experimental-interpreter" + }, + "property2": { "description": "string", - "script": "string" + "script": "string", + "runtime": "experimental-interpreter" } - ] + } } ], "hasMore": true, @@ -5993,13 +6068,18 @@ and } } }, - "transactions": [ - { - "id": "string", + "transactions": { + "property1": { + "description": "string", + "script": "string", + "runtime": "experimental-interpreter" + }, + "property2": { "description": "string", - "script": "string" + "script": "string", + "runtime": "experimental-interpreter" } - ] + } } ], "hasMore": true, diff --git a/internal/README.md b/internal/README.md index 66035466b4..88d3b49233 100644 --- a/internal/README.md +++ b/internal/README.md @@ -173,7 +173,6 @@ import "github.com/formancehq/ledger/internal" - [func \(data TransactionData\) WithPostings\(postings ...Posting\) TransactionData](<#TransactionData.WithPostings>) - [type TransactionTemplate](<#TransactionTemplate>) - [type TransactionTemplates](<#TransactionTemplates>) - - [func \(t TransactionTemplates\) MarshalJSON\(\) \(\[\]byte, error\)](<#TransactionTemplates.MarshalJSON>) - [func \(t \*TransactionTemplates\) UnmarshalJSON\(data \[\]byte\) error](<#TransactionTemplates.UnmarshalJSON>) - [type Transactions](<#Transactions>) - [type Volumes](<#Volumes>) @@ -1563,7 +1562,7 @@ func (r RevertedTransaction) ValidateWithSchema(schema Schema) error -## type [RuntimeType]() +## type [RuntimeType]() @@ -1902,7 +1901,7 @@ func (data TransactionData) WithPostings(postings ...Posting) TransactionData -## type [TransactionTemplate]() +## type [TransactionTemplate]() @@ -1915,7 +1914,7 @@ type TransactionTemplate struct { ``` -## type [TransactionTemplates]() +## type [TransactionTemplates]() @@ -1923,23 +1922,14 @@ type TransactionTemplate struct { type TransactionTemplates map[string]TransactionTemplate ``` - -### func \(TransactionTemplates\) [MarshalJSON]() - -```go -func (t TransactionTemplates) MarshalJSON() ([]byte, error) -``` - - - -### func \(\*TransactionTemplates\) [UnmarshalJSON]() +### func \(\*TransactionTemplates\) [UnmarshalJSON]() ```go func (t *TransactionTemplates) UnmarshalJSON(data []byte) error ``` -Marshal that transforms a list of transactions with ids into a map + ## type [Transactions]() diff --git a/internal/api/bulking/elements.go b/internal/api/bulking/elements.go index e1b6da6d28..65b28b26eb 100644 --- a/internal/api/bulking/elements.go +++ b/internal/api/bulking/elements.go @@ -103,7 +103,7 @@ type TransactionRequest struct { Reference string `json:"reference"` Metadata metadata.Metadata `json:"metadata" swaggertype:"object"` AccountMetadata map[string]metadata.Metadata `json:"accountMetadata"` - Runtime ledgercontroller.RuntimeType `json:"runtime,omitempty"` + Runtime ledger.RuntimeType `json:"runtime,omitempty"` Force bool `json:"force"` } diff --git a/internal/controller/ledger/controller.go b/internal/controller/ledger/controller.go index e2338eb0a2..6706bd2e9f 100644 --- a/internal/controller/ledger/controller.go +++ b/internal/controller/ledger/controller.go @@ -92,17 +92,10 @@ type RunScript = vm.RunScript type Script = vm.Script type ScriptV1 = vm.ScriptV1 -type RuntimeType string - -const ( - RuntimeExperimentalInterpreter RuntimeType = "experimental-interpreter" - RuntimeMachine RuntimeType = "machine" -) - type CreateTransaction struct { RunScript AccountMetadata map[string]metadata.Metadata - Runtime RuntimeType + Runtime ledger.RuntimeType } type RevertTransaction struct { diff --git a/internal/controller/ledger/controller_default.go b/internal/controller/ledger/controller_default.go index f75af59f74..962dd93d31 100644 --- a/internal/controller/ledger/controller_default.go +++ b/internal/controller/ledger/controller_default.go @@ -67,7 +67,7 @@ func (ctrl *DefaultController) insertSchema(ctx context.Context, store Store, _s } for id, template := range schema.Transactions { - parser := ctrl.getParser(RuntimeType(template.Runtime)) + parser := ctrl.getParser(ledger.RuntimeType(template.Runtime)) _, err := parser.Parse(template.Script) if err != nil { return nil, ledger.NewErrInvalidSchema(fmt.Errorf("invalid template %s: %w", id, err)) @@ -403,11 +403,11 @@ func (ctrl *DefaultController) Export(ctx context.Context, w ExportWriter) error ) } -func (ctrl *DefaultController) getParser(runtimeType RuntimeType) NumscriptParser { +func (ctrl *DefaultController) getParser(runtimeType ledger.RuntimeType) NumscriptParser { switch runtimeType { - case RuntimeExperimentalInterpreter: + case ledger.RuntimeExperimentalInterpreter: return ctrl.interpreterParser - case RuntimeMachine: + case ledger.RuntimeMachine: return ctrl.machineParser default: return ctrl.parser @@ -424,6 +424,9 @@ func (ctrl *DefaultController) createTransaction(ctx context.Context, store Stor } if template, ok := schema.SchemaData.Transactions[parameters.Input.Template]; ok { parameters.Input.Plain = template.Script + if parameters.Input.Runtime == "" { + parameters.Input.Runtime = ledger.RuntimeType(template.Runtime) + } } else { return nil, newErrSchemaValidationError(parameters.SchemaVersion, fmt.Errorf("failed to find transaction template `%s`", parameters.Input.Template)) } diff --git a/internal/storage/ledger/resource_transactions.go b/internal/storage/ledger/resource_transactions.go index 7cbe59f5e9..6c0c51e53e 100644 --- a/internal/storage/ledger/resource_transactions.go +++ b/internal/storage/ledger/resource_transactions.go @@ -47,6 +47,7 @@ func (h transactionsResourceHandler) BuildDataset(opts common.RepositoryHandlerB "destinations", "sources_arrays", "destinations_arrays", + "template", ) if slices.Contains(opts.Expand, "volumes") { diff --git a/internal/transaction_templates.go b/internal/transaction_templates.go index 21856aea58..d461582ace 100644 --- a/internal/transaction_templates.go +++ b/internal/transaction_templates.go @@ -2,8 +2,6 @@ package ledger import ( "encoding/json" - "errors" - "fmt" ) type RuntimeType string @@ -21,49 +19,18 @@ type TransactionTemplate struct { type TransactionTemplates map[string]TransactionTemplate -// Marshal that transforms a list of transactions with ids into a map func (t *TransactionTemplates) UnmarshalJSON(data []byte) error { - var rawList []struct { - ID string - Description string - Script string - } - if err := json.Unmarshal(data, &rawList); err != nil { + type Templates TransactionTemplates + var templates Templates + if err := json.Unmarshal(data, &templates); err != nil { return err } - out := make(map[string]TransactionTemplate) - for _, item := range rawList { - if item.ID == "" { - return errors.New("transaction template id cannot be empty") - } - if _, exists := out[item.ID]; exists { - return fmt.Errorf("duplicate transaction template id: %v", item.ID) - } - out[item.ID] = TransactionTemplate{ - Description: item.Description, - Script: item.Script, + for id, tmpl := range templates { + if tmpl.Runtime == "" { + tmpl.Runtime = RuntimeMachine + templates[id] = tmpl } } - *t = out + *t = TransactionTemplates(templates) return nil } - -func (t TransactionTemplates) MarshalJSON() ([]byte, error) { - var rawList []struct { - ID string - Description string - Script string - } - for id, item := range t { - rawList = append(rawList, struct { - ID string - Description string - Script string - }{ - ID: id, - Description: item.Description, - Script: item.Script, - }) - } - return json.Marshal(rawList) -} diff --git a/openapi.yaml b/openapi.yaml index 9e4e98ce4a..ff95c57ea2 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -4126,12 +4126,15 @@ components: $ref: "#/components/schemas/V2AggregatedVolumes" postCommitEffectiveVolumes: $ref: "#/components/schemas/V2AggregatedVolumes" + template: + type: string required: - postings - timestamp - id - metadata - reverted + - template V2PostTransaction: type: object required: @@ -4163,11 +4166,7 @@ components: required: - plain runtime: - description: The numscript runtime used to execute the script. Uses "machine" by default, unless the "--experimental-numscript-interpreter" feature flag is passed. - type: string - enum: - - experimental-interpreter - - machine + $ref: "#/components/schemas/Runtime" reference: type: string example: ref:001 @@ -4593,19 +4592,27 @@ components: users: $userID: .pattern: "^[0-9]{16}$" + Runtime: + type: string + description: The numscript runtime used to execute the script. Uses "machine" by default, unless the "--experimental-numscript-interpreter" feature flag is passed. + enum: + - experimental-interpreter + - machine V2TransactionTemplate: type: object properties: - id: - type: string description: type: string script: type: string + runtime: + $ref: "#/components/schemas/Runtime" + required: + - script V2TransactionTemplates: - type: array + type: object description: Transaction templates - items: + additionalProperties: $ref: "#/components/schemas/V2TransactionTemplate" V2SchemaData: type: object diff --git a/openapi/v2.yaml b/openapi/v2.yaml index 8e63dc1e5d..a477e8aeb3 100644 --- a/openapi/v2.yaml +++ b/openapi/v2.yaml @@ -2350,12 +2350,15 @@ components: $ref: "#/components/schemas/V2AggregatedVolumes" postCommitEffectiveVolumes: $ref: "#/components/schemas/V2AggregatedVolumes" + template: + type: string required: - postings - timestamp - id - metadata - reverted + - template V2PostTransaction: type: object required: @@ -2387,11 +2390,7 @@ components: required: - plain runtime: - description: The numscript runtime used to execute the script. Uses "machine" by default, unless the "--experimental-numscript-interpreter" feature flag is passed. - type: string - enum: - - experimental-interpreter - - machine + $ref: "#/components/schemas/Runtime" reference: type: string example: ref:001 @@ -2817,19 +2816,27 @@ components: users: $userID: .pattern: "^[0-9]{16}$" + Runtime: + type: string + description: The numscript runtime used to execute the script. Uses "machine" by default, unless the "--experimental-numscript-interpreter" feature flag is passed. + enum: + - experimental-interpreter + - machine V2TransactionTemplate: type: object properties: - id: - type: string description: type: string script: type: string + runtime: + $ref: "#/components/schemas/Runtime" + required: + - script V2TransactionTemplates: - type: array + type: object description: Transaction templates - items: + additionalProperties: $ref: "#/components/schemas/V2TransactionTemplate" V2SchemaData: type: object diff --git a/pkg/client/.speakeasy/gen.lock b/pkg/client/.speakeasy/gen.lock index 111aba34ab..e54e32005b 100644 --- a/pkg/client/.speakeasy/gen.lock +++ b/pkg/client/.speakeasy/gen.lock @@ -1,7 +1,7 @@ lockVersion: 2.0.0 id: a9ac79e1-e429-4ee3-96c4-ec973f19bec3 management: - docChecksum: 2a7e02c05a30e75ae13f2c8269ca4cca + docChecksum: 9409cd7280ab21943ae30fcf6d812289 docVersion: v2 speakeasyVersion: 1.563.0 generationVersion: 2.629.1 @@ -62,6 +62,7 @@ generatedFiles: - /models/components/migrationinfo.go - /models/components/posting.go - /models/components/posttransaction.go + - /models/components/runtime.go - /models/components/script.go - /models/components/scriptresponse.go - /models/components/security.go @@ -923,7 +924,7 @@ examples: application/json: {} responses: "200": - application/json: {"cursor": {"pageSize": 15, "hasMore": false, "previous": "YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol=", "next": "aW0gdmVuaWFtLCBxdWlzIG5vc3RydWQ=", "data": [{"timestamp": "2024-10-10T19:56:12.497Z", "postings": [], "reference": "ref:001", "metadata": {"admin": "true"}, "id": 177096, "reverted": true, "preCommitVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "postCommitVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "preCommitEffectiveVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "postCommitEffectiveVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}}]}} + application/json: {"cursor": {"pageSize": 15, "hasMore": false, "previous": "YXVsdCBhbmQgYSBtYXhpbXVtIG1heF9yZXN1bHRzLol=", "next": "aW0gdmVuaWFtLCBxdWlzIG5vc3RydWQ=", "data": [{"timestamp": "2024-10-10T19:56:12.497Z", "postings": [], "reference": "ref:001", "metadata": {"admin": "true"}, "id": 177096, "reverted": true, "preCommitVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "postCommitVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "preCommitEffectiveVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "postCommitEffectiveVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "template": ""}]}} default: application/json: {"errorCode": "VALIDATION", "errorMessage": "[VALIDATION] invalid 'cursor' query param", "details": "https://play.numscript.org/?payload=eyJlcnJvciI6ImFjY291bnQgaGFkIGluc3VmZmljaWVudCBmdW5kcyJ9"} v2CreateTransaction: @@ -939,7 +940,7 @@ examples: application/json: {"postings": [{"amount": 100, "asset": "COIN", "destination": "users:002", "source": "users:001"}], "script": {"template": "CUSTOMER_DEPOSIT", "plain": "vars {\naccount $user\n}\nsend [COIN 10] (\n\tsource = @world\n\tdestination = $user\n)\n", "vars": {"user": "users:042"}}, "reference": "ref:001", "metadata": {"admin": "true"}, "accountMetadata": {"key": {"admin": "true"}, "key1": {"admin": "true"}, "key2": {"admin": "true"}}} responses: "200": - application/json: {"data": {"timestamp": "2024-06-10T07:40:23.819Z", "postings": [{"amount": 100, "asset": "COIN", "destination": "users:002", "source": "users:001"}], "reference": "ref:001", "metadata": {"admin": "true"}, "id": 339560, "reverted": true, "preCommitVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "postCommitVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "preCommitEffectiveVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "postCommitEffectiveVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}}} + application/json: {"data": {"timestamp": "2024-06-10T07:40:23.819Z", "postings": [{"amount": 100, "asset": "COIN", "destination": "users:002", "source": "users:001"}], "reference": "ref:001", "metadata": {"admin": "true"}, "id": 339560, "reverted": true, "preCommitVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "postCommitVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "preCommitEffectiveVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "postCommitEffectiveVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "template": ""}} default: application/json: {"errorCode": "VALIDATION", "errorMessage": "[VALIDATION] invalid 'cursor' query param", "details": "https://play.numscript.org/?payload=eyJlcnJvciI6ImFjY291bnQgaGFkIGluc3VmZmljaWVudCBmdW5kcyJ9"} v2GetTransaction: @@ -950,7 +951,7 @@ examples: id: 1234 responses: "200": - application/json: {"data": {"timestamp": "2024-10-06T11:21:14.840Z", "postings": [], "reference": "ref:001", "metadata": {"admin": "true"}, "id": 232775, "reverted": false, "preCommitVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "postCommitVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "preCommitEffectiveVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "postCommitEffectiveVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}}} + application/json: {"data": {"timestamp": "2024-10-06T11:21:14.840Z", "postings": [], "reference": "ref:001", "metadata": {"admin": "true"}, "id": 232775, "reverted": false, "preCommitVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "postCommitVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "preCommitEffectiveVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "postCommitEffectiveVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "template": ""}} default: application/json: {"errorCode": "VALIDATION", "errorMessage": "[VALIDATION] invalid 'cursor' query param", "details": "https://play.numscript.org/?payload=eyJlcnJvciI6ImFjY291bnQgaGFkIGluc3VmZmljaWVudCBmdW5kcyJ9"} v2AddMetadataOnTransaction: @@ -988,7 +989,7 @@ examples: schemaVersion: "v1.0.0" responses: "201": - application/json: {"data": {"timestamp": "2025-01-07T04:49:40.482Z", "postings": [], "reference": "ref:001", "metadata": {"admin": "true"}, "id": 670435, "reverted": true, "preCommitVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "postCommitVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "preCommitEffectiveVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "postCommitEffectiveVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}}} + application/json: {"data": {"timestamp": "2025-01-07T04:49:40.482Z", "postings": [], "reference": "ref:001", "metadata": {"admin": "true"}, "id": 670435, "reverted": true, "preCommitVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "postCommitVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "preCommitEffectiveVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "postCommitEffectiveVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "template": ""}} default: application/json: {"errorCode": "VALIDATION", "errorMessage": "[VALIDATION] invalid 'cursor' query param", "details": "https://play.numscript.org/?payload=eyJlcnJvciI6ImFjY291bnQgaGFkIGluc3VmZmljaWVudCBmdW5kcyJ9"} v2GetBalancesAggregated: diff --git a/pkg/client/.speakeasy/logs/naming.log b/pkg/client/.speakeasy/logs/naming.log index 6feea579c0..e00d6547bb 100644 --- a/pkg/client/.speakeasy/logs/naming.log +++ b/pkg/client/.speakeasy/logs/naming.log @@ -102,12 +102,13 @@ V2CreateLedgerRequest (ledger: string, V2CreateLedgerRequest: V2CreateLedgerRequ V2CreateLedgerRequest (bucket: string, metadata: map, features: map) V2CreateLedgerResponse (HttpMeta: HTTPMetadata) V2InsertSchemaRequest (ledger: string, version: string, V2SchemaData: V2SchemaData) - V2SchemaData (chart: map, transactions: array) + V2SchemaData (chart: map, transactions: map) V2ChartSegment (.self: class, .pattern: string, .rules: V2ChartAccountRules ...) Self (empty) V2ChartAccountRules (empty) V2ChartAccountMetadata (default: string) - V2TransactionTemplate (id: string, description: string, script: string) + V2TransactionTemplate (description: string, script: string, runtime: Runtime) + Runtime (enum: experimental-interpreter, machine) V2InsertSchemaResponse (HttpMeta: HTTPMetadata) V2GetSchemaRequest (ledger: string, version: string) V2GetSchemaResponse (HttpMeta: HTTPMetadata, V2SchemaResponse: V2SchemaResponse) @@ -137,7 +138,6 @@ V2CreateBulkRequest (ledger: string, continueOnFailure: boolean, atomic: boolean V2PostTransaction (timestamp: date-time, postings: array, script: class ...) V2Posting (amount: bigint, asset: string, destination: string ...) V2PostTransactionScript (template: string, plain: string, vars: map) - Runtime (enum: experimental-interpreter, machine) V2BulkElementAddMetadata (action: string, ik: string, data: class) Data (targetId: V2TargetId, targetType: V2TargetType, metadata: map) V2TargetId (union) diff --git a/pkg/client/docs/models/components/v2schema.md b/pkg/client/docs/models/components/v2schema.md index 05750baea6..75e4fa8be6 100644 --- a/pkg/client/docs/models/components/v2schema.md +++ b/pkg/client/docs/models/components/v2schema.md @@ -5,9 +5,9 @@ Complete schema structure with metadata ## Fields -| Field | Type | Required | Description | Example | -| -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -| `Version` | *string* | :heavy_check_mark: | Schema version | v1.0.0 | -| `CreatedAt` | [time.Time](https://pkg.go.dev/time#Time) | :heavy_check_mark: | Schema creation timestamp | 2023-01-01T00:00:00Z | -| `Chart` | map[string][components.V2ChartSegment](../../models/components/v2chartsegment.md) | :heavy_check_mark: | Chart of account | {
"users": {
"$userID": {
".pattern": "^[0-9]{16}$"
}
}
} | -| `Transactions` | [][components.V2TransactionTemplate](../../models/components/v2transactiontemplate.md) | :heavy_minus_sign: | Transaction templates | | \ No newline at end of file +| Field | Type | Required | Description | Example | +| ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | +| `Version` | *string* | :heavy_check_mark: | Schema version | v1.0.0 | +| `CreatedAt` | [time.Time](https://pkg.go.dev/time#Time) | :heavy_check_mark: | Schema creation timestamp | 2023-01-01T00:00:00Z | +| `Chart` | map[string][components.V2ChartSegment](../../models/components/v2chartsegment.md) | :heavy_check_mark: | Chart of account | {
"users": {
"$userID": {
".pattern": "^[0-9]{16}$"
}
}
} | +| `Transactions` | map[string][components.V2TransactionTemplate](../../models/components/v2transactiontemplate.md) | :heavy_minus_sign: | Transaction templates | | \ No newline at end of file diff --git a/pkg/client/docs/models/components/v2schemadata.md b/pkg/client/docs/models/components/v2schemadata.md index da2e4ff4d5..a3be8db307 100644 --- a/pkg/client/docs/models/components/v2schemadata.md +++ b/pkg/client/docs/models/components/v2schemadata.md @@ -5,7 +5,7 @@ Schema data structure for ledger schemas ## Fields -| Field | Type | Required | Description | Example | -| -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -| `Chart` | map[string][components.V2ChartSegment](../../models/components/v2chartsegment.md) | :heavy_check_mark: | Chart of account | {
"users": {
"$userID": {
".pattern": "^[0-9]{16}$"
}
}
} | -| `Transactions` | [][components.V2TransactionTemplate](../../models/components/v2transactiontemplate.md) | :heavy_minus_sign: | Transaction templates | | \ No newline at end of file +| Field | Type | Required | Description | Example | +| ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | +| `Chart` | map[string][components.V2ChartSegment](../../models/components/v2chartsegment.md) | :heavy_check_mark: | Chart of account | {
"users": {
"$userID": {
".pattern": "^[0-9]{16}$"
}
}
} | +| `Transactions` | map[string][components.V2TransactionTemplate](../../models/components/v2transactiontemplate.md) | :heavy_minus_sign: | Transaction templates | | \ No newline at end of file diff --git a/pkg/client/docs/models/components/v2transaction.md b/pkg/client/docs/models/components/v2transaction.md index 2356479c23..1b721eadcd 100644 --- a/pkg/client/docs/models/components/v2transaction.md +++ b/pkg/client/docs/models/components/v2transaction.md @@ -17,4 +17,5 @@ | `PreCommitVolumes` | map[string]map[string][components.V2Volume](../../models/components/v2volume.md) | :heavy_minus_sign: | N/A | {
"orders:1": {
"USD": {
"input": 100,
"output": 10,
"balance": 90
}
},
"orders:2": {
"USD": {
"input": 100,
"output": 10,
"balance": 90
}
}
} | | `PostCommitVolumes` | map[string]map[string][components.V2Volume](../../models/components/v2volume.md) | :heavy_minus_sign: | N/A | {
"orders:1": {
"USD": {
"input": 100,
"output": 10,
"balance": 90
}
},
"orders:2": {
"USD": {
"input": 100,
"output": 10,
"balance": 90
}
}
} | | `PreCommitEffectiveVolumes` | map[string]map[string][components.V2Volume](../../models/components/v2volume.md) | :heavy_minus_sign: | N/A | {
"orders:1": {
"USD": {
"input": 100,
"output": 10,
"balance": 90
}
},
"orders:2": {
"USD": {
"input": 100,
"output": 10,
"balance": 90
}
}
} | -| `PostCommitEffectiveVolumes` | map[string]map[string][components.V2Volume](../../models/components/v2volume.md) | :heavy_minus_sign: | N/A | {
"orders:1": {
"USD": {
"input": 100,
"output": 10,
"balance": 90
}
},
"orders:2": {
"USD": {
"input": 100,
"output": 10,
"balance": 90
}
}
} | \ No newline at end of file +| `PostCommitEffectiveVolumes` | map[string]map[string][components.V2Volume](../../models/components/v2volume.md) | :heavy_minus_sign: | N/A | {
"orders:1": {
"USD": {
"input": 100,
"output": 10,
"balance": 90
}
},
"orders:2": {
"USD": {
"input": 100,
"output": 10,
"balance": 90
}
}
} | +| `Template` | *string* | :heavy_check_mark: | N/A | | \ No newline at end of file diff --git a/pkg/client/docs/models/components/v2transactiontemplate.md b/pkg/client/docs/models/components/v2transactiontemplate.md index 3bca0b9ea1..cdee2feba3 100644 --- a/pkg/client/docs/models/components/v2transactiontemplate.md +++ b/pkg/client/docs/models/components/v2transactiontemplate.md @@ -3,8 +3,8 @@ ## Fields -| Field | Type | Required | Description | -| ------------------ | ------------------ | ------------------ | ------------------ | -| `ID` | **string* | :heavy_minus_sign: | N/A | -| `Description` | **string* | :heavy_minus_sign: | N/A | -| `Script` | **string* | :heavy_minus_sign: | N/A | \ No newline at end of file +| Field | Type | Required | Description | +| ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `Description` | **string* | :heavy_minus_sign: | N/A | +| `Script` | *string* | :heavy_check_mark: | N/A | +| `Runtime` | [*components.Runtime](../../models/components/runtime.md) | :heavy_minus_sign: | The numscript runtime used to execute the script. Uses "machine" by default, unless the "--experimental-numscript-interpreter" feature flag is passed. | \ No newline at end of file diff --git a/pkg/client/models/components/runtime.go b/pkg/client/models/components/runtime.go new file mode 100644 index 0000000000..29e68ed291 --- /dev/null +++ b/pkg/client/models/components/runtime.go @@ -0,0 +1,35 @@ +// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + +package components + +import ( + "encoding/json" + "fmt" +) + +// Runtime - The numscript runtime used to execute the script. Uses "machine" by default, unless the "--experimental-numscript-interpreter" feature flag is passed. +type Runtime string + +const ( + RuntimeExperimentalInterpreter Runtime = "experimental-interpreter" + RuntimeMachine Runtime = "machine" +) + +func (e Runtime) ToPointer() *Runtime { + return &e +} +func (e *Runtime) UnmarshalJSON(data []byte) error { + var v string + if err := json.Unmarshal(data, &v); err != nil { + return err + } + switch v { + case "experimental-interpreter": + fallthrough + case "machine": + *e = Runtime(v) + return nil + default: + return fmt.Errorf("invalid value for Runtime: %v", v) + } +} diff --git a/pkg/client/models/components/v2posttransaction.go b/pkg/client/models/components/v2posttransaction.go index 1951120cb9..0ec5359914 100644 --- a/pkg/client/models/components/v2posttransaction.go +++ b/pkg/client/models/components/v2posttransaction.go @@ -3,8 +3,6 @@ package components import ( - "encoding/json" - "fmt" "github.com/formancehq/ledger/pkg/client/internal/utils" "time" ) @@ -36,33 +34,6 @@ func (o *V2PostTransactionScript) GetVars() map[string]string { return o.Vars } -// Runtime - The numscript runtime used to execute the script. Uses "machine" by default, unless the "--experimental-numscript-interpreter" feature flag is passed. -type Runtime string - -const ( - RuntimeExperimentalInterpreter Runtime = "experimental-interpreter" - RuntimeMachine Runtime = "machine" -) - -func (e Runtime) ToPointer() *Runtime { - return &e -} -func (e *Runtime) UnmarshalJSON(data []byte) error { - var v string - if err := json.Unmarshal(data, &v); err != nil { - return err - } - switch v { - case "experimental-interpreter": - fallthrough - case "machine": - *e = Runtime(v) - return nil - default: - return fmt.Errorf("invalid value for Runtime: %v", v) - } -} - type V2PostTransaction struct { Timestamp *time.Time `json:"timestamp,omitempty"` Postings []V2Posting `json:"postings,omitempty"` diff --git a/pkg/client/models/components/v2schema.go b/pkg/client/models/components/v2schema.go index d65b15a239..da590480c3 100644 --- a/pkg/client/models/components/v2schema.go +++ b/pkg/client/models/components/v2schema.go @@ -16,7 +16,7 @@ type V2Schema struct { // Chart of account Chart map[string]V2ChartSegment `json:"chart"` // Transaction templates - Transactions []V2TransactionTemplate `json:"transactions,omitempty"` + Transactions map[string]V2TransactionTemplate `json:"transactions,omitempty"` } func (v V2Schema) MarshalJSON() ([]byte, error) { @@ -51,7 +51,7 @@ func (o *V2Schema) GetChart() map[string]V2ChartSegment { return o.Chart } -func (o *V2Schema) GetTransactions() []V2TransactionTemplate { +func (o *V2Schema) GetTransactions() map[string]V2TransactionTemplate { if o == nil { return nil } diff --git a/pkg/client/models/components/v2schemadata.go b/pkg/client/models/components/v2schemadata.go index f51fb8d2b1..d0b68044f2 100644 --- a/pkg/client/models/components/v2schemadata.go +++ b/pkg/client/models/components/v2schemadata.go @@ -7,7 +7,7 @@ type V2SchemaData struct { // Chart of account Chart map[string]V2ChartSegment `json:"chart"` // Transaction templates - Transactions []V2TransactionTemplate `json:"transactions,omitempty"` + Transactions map[string]V2TransactionTemplate `json:"transactions,omitempty"` } func (o *V2SchemaData) GetChart() map[string]V2ChartSegment { @@ -17,7 +17,7 @@ func (o *V2SchemaData) GetChart() map[string]V2ChartSegment { return o.Chart } -func (o *V2SchemaData) GetTransactions() []V2TransactionTemplate { +func (o *V2SchemaData) GetTransactions() map[string]V2TransactionTemplate { if o == nil { return nil } diff --git a/pkg/client/models/components/v2transaction.go b/pkg/client/models/components/v2transaction.go index 62fc1bf788..dc84d51535 100644 --- a/pkg/client/models/components/v2transaction.go +++ b/pkg/client/models/components/v2transaction.go @@ -22,6 +22,7 @@ type V2Transaction struct { PostCommitVolumes map[string]map[string]V2Volume `json:"postCommitVolumes,omitempty"` PreCommitEffectiveVolumes map[string]map[string]V2Volume `json:"preCommitEffectiveVolumes,omitempty"` PostCommitEffectiveVolumes map[string]map[string]V2Volume `json:"postCommitEffectiveVolumes,omitempty"` + Template string `json:"template"` } func (v V2Transaction) MarshalJSON() ([]byte, error) { @@ -125,3 +126,10 @@ func (o *V2Transaction) GetPostCommitEffectiveVolumes() map[string]map[string]V2 } return o.PostCommitEffectiveVolumes } + +func (o *V2Transaction) GetTemplate() string { + if o == nil { + return "" + } + return o.Template +} diff --git a/pkg/client/models/components/v2transactiontemplate.go b/pkg/client/models/components/v2transactiontemplate.go index 95ee23a8cd..d77fdc0c1a 100644 --- a/pkg/client/models/components/v2transactiontemplate.go +++ b/pkg/client/models/components/v2transactiontemplate.go @@ -3,28 +3,29 @@ package components type V2TransactionTemplate struct { - ID *string `json:"id,omitempty"` Description *string `json:"description,omitempty"` - Script *string `json:"script,omitempty"` + Script string `json:"script"` + // The numscript runtime used to execute the script. Uses "machine" by default, unless the "--experimental-numscript-interpreter" feature flag is passed. + Runtime *Runtime `json:"runtime,omitempty"` } -func (o *V2TransactionTemplate) GetID() *string { +func (o *V2TransactionTemplate) GetDescription() *string { if o == nil { return nil } - return o.ID + return o.Description } -func (o *V2TransactionTemplate) GetDescription() *string { +func (o *V2TransactionTemplate) GetScript() string { if o == nil { - return nil + return "" } - return o.Description + return o.Script } -func (o *V2TransactionTemplate) GetScript() *string { +func (o *V2TransactionTemplate) GetRuntime() *Runtime { if o == nil { return nil } - return o.Script + return o.Runtime } diff --git a/pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log b/pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log index 6feea579c0..e00d6547bb 100644 --- a/pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log +++ b/pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log @@ -102,12 +102,13 @@ V2CreateLedgerRequest (ledger: string, V2CreateLedgerRequest: V2CreateLedgerRequ V2CreateLedgerRequest (bucket: string, metadata: map, features: map) V2CreateLedgerResponse (HttpMeta: HTTPMetadata) V2InsertSchemaRequest (ledger: string, version: string, V2SchemaData: V2SchemaData) - V2SchemaData (chart: map, transactions: array) + V2SchemaData (chart: map, transactions: map) V2ChartSegment (.self: class, .pattern: string, .rules: V2ChartAccountRules ...) Self (empty) V2ChartAccountRules (empty) V2ChartAccountMetadata (default: string) - V2TransactionTemplate (id: string, description: string, script: string) + V2TransactionTemplate (description: string, script: string, runtime: Runtime) + Runtime (enum: experimental-interpreter, machine) V2InsertSchemaResponse (HttpMeta: HTTPMetadata) V2GetSchemaRequest (ledger: string, version: string) V2GetSchemaResponse (HttpMeta: HTTPMetadata, V2SchemaResponse: V2SchemaResponse) @@ -137,7 +138,6 @@ V2CreateBulkRequest (ledger: string, continueOnFailure: boolean, atomic: boolean V2PostTransaction (timestamp: date-time, postings: array, script: class ...) V2Posting (amount: bigint, asset: string, destination: string ...) V2PostTransactionScript (template: string, plain: string, vars: map) - Runtime (enum: experimental-interpreter, machine) V2BulkElementAddMetadata (action: string, ik: string, data: class) Data (targetId: V2TargetId, targetType: V2TargetType, metadata: map) V2TargetId (union) diff --git a/test/e2e/api_schema_test.go b/test/e2e/api_schema_test.go index 93964e22a7..6be5ef7f86 100644 --- a/test/e2e/api_schema_test.go +++ b/test/e2e/api_schema_test.go @@ -54,30 +54,27 @@ var _ = Context("Ledger schema API tests", func() { When("inserting schemas with different validation rules", func() { BeforeEach(func(specContext SpecContext) { - transactionTemplates := []components.V2TransactionTemplate{ - { - ID: pointer.For("WORLD_TO_BANK"), - Script: pointer.For(` - vars { - account $b - } - send [USD 100] ( - source = @world - destination = $b - )`), + transactionTemplates := map[string]components.V2TransactionTemplate{ + "WORLD_TO_BANK": { + Script: ` + vars { + account $b + } + send [USD 100] ( + source = @world + destination = $b + )`, }, - { - ID: pointer.For("A_TO_B"), - Script: pointer.For(` - vars { - account $a - account $b - } - send [USD 100] ( - source = $a allowing unbounded overdraft - destination = $b - ) - `), + "A_TO_B": { + Script: ` + vars { + account $a + account $b + } + send [USD 100] ( + source = $a allowing unbounded overdraft + destination = $b + )`, }, } @@ -232,7 +229,7 @@ var _ = Context("Ledger schema API tests", func() { When("testing transaction creation with schema validation", func() { It("should create transaction with v1.0.0 schema", func(specContext SpecContext) { schemaVersion := "v1.0.0" - _, err := Wait(specContext, DeferClient(testServer)).Ledger.V2.CreateTransaction(ctx, operations.V2CreateTransactionRequest{ + res, err := Wait(specContext, DeferClient(testServer)).Ledger.V2.CreateTransaction(ctx, operations.V2CreateTransactionRequest{ Ledger: "default", SchemaVersion: &schemaVersion, V2PostTransaction: components.V2PostTransaction{ @@ -246,6 +243,12 @@ var _ = Context("Ledger schema API tests", func() { }, }) Expect(err).To(BeNil()) + getTxRes, err := Wait(specContext, DeferClient(testServer)).Ledger.V2.GetTransaction(ctx, operations.V2GetTransactionRequest{ + Ledger: "default", + ID: res.V2CreateTransactionResponse.Data.ID, + }) + Expect(err).To(BeNil()) + Expect(getTxRes.V2GetTransactionResponse.Data.Template).To(Equal("WORLD_TO_BANK")) }) It("should create transaction with v2.0.0 schema", func(specContext SpecContext) { From c8c57cde163cd300618e42b1786b247ea6d4fa05 Mon Sep 17 00:00:00 2001 From: Azorlogh Date: Mon, 8 Dec 2025 17:01:53 +0100 Subject: [PATCH 04/14] fix missing struct tags --- docs/events/InsertedSchema.json | 12 ++++++------ internal/README.md | 6 +++--- internal/transaction_templates.go | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/events/InsertedSchema.json b/docs/events/InsertedSchema.json index ef49b13810..6015f2f179 100644 --- a/docs/events/InsertedSchema.json +++ b/docs/events/InsertedSchema.json @@ -143,22 +143,22 @@ }, "TransactionTemplate": { "properties": { - "Description": { + "description": { "type": "string" }, - "Script": { + "script": { "type": "string" }, - "Runtime": { + "runtime": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ - "Description", - "Script", - "Runtime" + "description", + "script", + "runtime" ] }, "TransactionTemplates": { diff --git a/internal/README.md b/internal/README.md index 88d3b49233..fbec8f5569 100644 --- a/internal/README.md +++ b/internal/README.md @@ -1907,9 +1907,9 @@ func (data TransactionData) WithPostings(postings ...Posting) TransactionData ```go type TransactionTemplate struct { - Description string - Script string - Runtime RuntimeType + Description string `json:"description"` + Script string `json:"script"` + Runtime RuntimeType `json:"runtime"` } ``` diff --git a/internal/transaction_templates.go b/internal/transaction_templates.go index d461582ace..95f1b3ca44 100644 --- a/internal/transaction_templates.go +++ b/internal/transaction_templates.go @@ -12,9 +12,9 @@ const ( ) type TransactionTemplate struct { - Description string - Script string - Runtime RuntimeType + Description string `json:"description"` + Script string `json:"script"` + Runtime RuntimeType `json:"runtime"` } type TransactionTemplates map[string]TransactionTemplate From b7e22be69523f407ec42a828154e389b9c040753 Mon Sep 17 00:00:00 2001 From: Azorlogh Date: Mon, 8 Dec 2025 17:21:12 +0100 Subject: [PATCH 05/14] fix migration numbers --- .../notes.yaml | 0 .../up.sql | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename internal/storage/bucket/migrations/{43-add-transaction-templates => 45-add-transaction-templates}/notes.yaml (100%) rename internal/storage/bucket/migrations/{43-add-transaction-templates => 45-add-transaction-templates}/up.sql (100%) diff --git a/internal/storage/bucket/migrations/43-add-transaction-templates/notes.yaml b/internal/storage/bucket/migrations/45-add-transaction-templates/notes.yaml similarity index 100% rename from internal/storage/bucket/migrations/43-add-transaction-templates/notes.yaml rename to internal/storage/bucket/migrations/45-add-transaction-templates/notes.yaml diff --git a/internal/storage/bucket/migrations/43-add-transaction-templates/up.sql b/internal/storage/bucket/migrations/45-add-transaction-templates/up.sql similarity index 100% rename from internal/storage/bucket/migrations/43-add-transaction-templates/up.sql rename to internal/storage/bucket/migrations/45-add-transaction-templates/up.sql From 309a6f2001595d30e6f95e77298bf3424d9db18c Mon Sep 17 00:00:00 2001 From: Azorlogh Date: Mon, 8 Dec 2025 20:05:27 +0100 Subject: [PATCH 06/14] keep in migration 44 --- internal/storage/bucket/migrations/44-add-schema/up.sql | 3 +++ .../migrations/45-add-transaction-templates/notes.yaml | 1 - .../bucket/migrations/45-add-transaction-templates/up.sql | 8 -------- pkg/client/.speakeasy/gen.lock | 2 +- pkg/client/.speakeasy/logs/naming.log | 4 ++-- pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log | 4 ++-- 6 files changed, 8 insertions(+), 14 deletions(-) delete mode 100644 internal/storage/bucket/migrations/45-add-transaction-templates/notes.yaml delete mode 100644 internal/storage/bucket/migrations/45-add-transaction-templates/up.sql diff --git a/internal/storage/bucket/migrations/44-add-schema/up.sql b/internal/storage/bucket/migrations/44-add-schema/up.sql index 221b063391..98dbb64392 100644 --- a/internal/storage/bucket/migrations/44-add-schema/up.sql +++ b/internal/storage/bucket/migrations/44-add-schema/up.sql @@ -15,6 +15,9 @@ do $$ alter table logs add column schema_version text; + + alter table transactions + add column template text; end $$; diff --git a/internal/storage/bucket/migrations/45-add-transaction-templates/notes.yaml b/internal/storage/bucket/migrations/45-add-transaction-templates/notes.yaml deleted file mode 100644 index b36c69d930..0000000000 --- a/internal/storage/bucket/migrations/45-add-transaction-templates/notes.yaml +++ /dev/null @@ -1 +0,0 @@ -name: Add template column to transactions diff --git a/internal/storage/bucket/migrations/45-add-transaction-templates/up.sql b/internal/storage/bucket/migrations/45-add-transaction-templates/up.sql deleted file mode 100644 index 408ef5dc6d..0000000000 --- a/internal/storage/bucket/migrations/45-add-transaction-templates/up.sql +++ /dev/null @@ -1,8 +0,0 @@ -do $$ - begin - set search_path = '{{ .Schema }}'; - - alter table transactions - add column template text; - end -$$; diff --git a/pkg/client/.speakeasy/gen.lock b/pkg/client/.speakeasy/gen.lock index e54e32005b..e99bfff90c 100644 --- a/pkg/client/.speakeasy/gen.lock +++ b/pkg/client/.speakeasy/gen.lock @@ -1,7 +1,7 @@ lockVersion: 2.0.0 id: a9ac79e1-e429-4ee3-96c4-ec973f19bec3 management: - docChecksum: 9409cd7280ab21943ae30fcf6d812289 + docChecksum: ea032aaa7f9e405bacc8804fdc420d05 docVersion: v2 speakeasyVersion: 1.563.0 generationVersion: 2.629.1 diff --git a/pkg/client/.speakeasy/logs/naming.log b/pkg/client/.speakeasy/logs/naming.log index e00d6547bb..c5b1d9602d 100644 --- a/pkg/client/.speakeasy/logs/naming.log +++ b/pkg/client/.speakeasy/logs/naming.log @@ -101,7 +101,7 @@ V2GetLedgerResponse (HttpMeta: HTTPMetadata, V2GetLedgerResponse: V2GetLedgerRes V2CreateLedgerRequest (ledger: string, V2CreateLedgerRequest: V2CreateLedgerRequest) V2CreateLedgerRequest (bucket: string, metadata: map, features: map) V2CreateLedgerResponse (HttpMeta: HTTPMetadata) -V2InsertSchemaRequest (ledger: string, version: string, V2SchemaData: V2SchemaData) +V2InsertSchemaRequest (ledger: string, version: string, Idempotency-Key: string ...) V2SchemaData (chart: map, transactions: map) V2ChartSegment (.self: class, .pattern: string, .rules: V2ChartAccountRules ...) Self (empty) @@ -109,7 +109,7 @@ V2InsertSchemaRequest (ledger: string, version: string, V2SchemaData: V2SchemaDa V2ChartAccountMetadata (default: string) V2TransactionTemplate (description: string, script: string, runtime: Runtime) Runtime (enum: experimental-interpreter, machine) -V2InsertSchemaResponse (HttpMeta: HTTPMetadata) +V2InsertSchemaResponse (HttpMeta: HTTPMetadata, Headers: map) V2GetSchemaRequest (ledger: string, version: string) V2GetSchemaResponse (HttpMeta: HTTPMetadata, V2SchemaResponse: V2SchemaResponse) V2SchemaResponse (data: V2Schema) diff --git a/pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log b/pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log index e00d6547bb..c5b1d9602d 100644 --- a/pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log +++ b/pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log @@ -101,7 +101,7 @@ V2GetLedgerResponse (HttpMeta: HTTPMetadata, V2GetLedgerResponse: V2GetLedgerRes V2CreateLedgerRequest (ledger: string, V2CreateLedgerRequest: V2CreateLedgerRequest) V2CreateLedgerRequest (bucket: string, metadata: map, features: map) V2CreateLedgerResponse (HttpMeta: HTTPMetadata) -V2InsertSchemaRequest (ledger: string, version: string, V2SchemaData: V2SchemaData) +V2InsertSchemaRequest (ledger: string, version: string, Idempotency-Key: string ...) V2SchemaData (chart: map, transactions: map) V2ChartSegment (.self: class, .pattern: string, .rules: V2ChartAccountRules ...) Self (empty) @@ -109,7 +109,7 @@ V2InsertSchemaRequest (ledger: string, version: string, V2SchemaData: V2SchemaDa V2ChartAccountMetadata (default: string) V2TransactionTemplate (description: string, script: string, runtime: Runtime) Runtime (enum: experimental-interpreter, machine) -V2InsertSchemaResponse (HttpMeta: HTTPMetadata) +V2InsertSchemaResponse (HttpMeta: HTTPMetadata, Headers: map) V2GetSchemaRequest (ledger: string, version: string) V2GetSchemaResponse (HttpMeta: HTTPMetadata, V2SchemaResponse: V2SchemaResponse) V2SchemaResponse (data: V2Schema) From 3c240dfdcbe473c50bf501ae43a35795e1dea2b6 Mon Sep 17 00:00:00 2001 From: Azorlogh Date: Tue, 9 Dec 2025 11:40:55 +0100 Subject: [PATCH 07/14] remove custom unmarshal, omit empty template --- docs/events/CommittedTransactions.json | 3 +-- docs/events/InsertedSchema.json | 3 +-- docs/events/RevertedTransaction.json | 3 +-- internal/README.md | 22 +++++++++---------- internal/api/v2/controllers_bulk_test.go | 3 --- internal/api/v2/views_test.go | 8 ------- .../controller/ledger/controller_default.go | 2 +- internal/schema.go | 3 +++ internal/transaction.go | 2 +- internal/transaction_templates.go | 22 +++++++------------ 10 files changed, 27 insertions(+), 44 deletions(-) diff --git a/docs/events/CommittedTransactions.json b/docs/events/CommittedTransactions.json index 80d6db65d5..31601d367b 100644 --- a/docs/events/CommittedTransactions.json +++ b/docs/events/CommittedTransactions.json @@ -132,8 +132,7 @@ "postings", "metadata", "timestamp", - "id", - "template" + "id" ] }, "Volumes": { diff --git a/docs/events/InsertedSchema.json b/docs/events/InsertedSchema.json index 6015f2f179..be583229c7 100644 --- a/docs/events/InsertedSchema.json +++ b/docs/events/InsertedSchema.json @@ -157,8 +157,7 @@ "type": "object", "required": [ "description", - "script", - "runtime" + "script" ] }, "TransactionTemplates": { diff --git a/docs/events/RevertedTransaction.json b/docs/events/RevertedTransaction.json index 664bf78728..b0b26b3d93 100644 --- a/docs/events/RevertedTransaction.json +++ b/docs/events/RevertedTransaction.json @@ -126,8 +126,7 @@ "postings", "metadata", "timestamp", - "id", - "template" + "id" ] }, "Volumes": { diff --git a/internal/README.md b/internal/README.md index fbec8f5569..32fc224f2e 100644 --- a/internal/README.md +++ b/internal/README.md @@ -173,7 +173,7 @@ import "github.com/formancehq/ledger/internal" - [func \(data TransactionData\) WithPostings\(postings ...Posting\) TransactionData](<#TransactionData.WithPostings>) - [type TransactionTemplate](<#TransactionTemplate>) - [type TransactionTemplates](<#TransactionTemplates>) - - [func \(t \*TransactionTemplates\) UnmarshalJSON\(data \[\]byte\) error](<#TransactionTemplates.UnmarshalJSON>) + - [func \(t TransactionTemplates\) Validate\(\) error](<#TransactionTemplates.Validate>) - [type Transactions](<#Transactions>) - [type Volumes](<#Volumes>) - [func NewEmptyVolumes\(\) Volumes](<#NewEmptyVolumes>) @@ -1562,7 +1562,7 @@ func (r RevertedTransaction) ValidateWithSchema(schema Schema) error -## type [RuntimeType]() +## type [RuntimeType]() @@ -1570,12 +1570,12 @@ func (r RevertedTransaction) ValidateWithSchema(schema Schema) error type RuntimeType string ``` - + ```go const ( - RuntimeExperimentalInterpreter RuntimeType = "experimental-interpreter" RuntimeMachine RuntimeType = "machine" + RuntimeExperimentalInterpreter RuntimeType = "experimental-interpreter" ) ``` @@ -1684,7 +1684,7 @@ type Transaction struct { // PostCommitEffectiveVolumes are the volumes of each account/asset after the transaction TransactionData.Timestamp. // Those volumes are also computed in flight, but can be updated if a transaction is inserted in the past. PostCommitEffectiveVolumes PostCommitVolumes `json:"postCommitEffectiveVolumes,omitempty" bun:"post_commit_effective_volumes,type:jsonb,scanonly"` - Template string `json:"template" bun:"template,type:text"` + Template string `json:"template,omitempty" bun:"template,type:text"` } ``` @@ -1901,7 +1901,7 @@ func (data TransactionData) WithPostings(postings ...Posting) TransactionData -## type [TransactionTemplate]() +## type [TransactionTemplate]() @@ -1909,12 +1909,12 @@ func (data TransactionData) WithPostings(postings ...Posting) TransactionData type TransactionTemplate struct { Description string `json:"description"` Script string `json:"script"` - Runtime RuntimeType `json:"runtime"` + Runtime RuntimeType `json:"runtime,omitempty"` } ``` -## type [TransactionTemplates]() +## type [TransactionTemplates]() @@ -1922,11 +1922,11 @@ type TransactionTemplate struct { type TransactionTemplates map[string]TransactionTemplate ``` - -### func \(\*TransactionTemplates\) [UnmarshalJSON]() + +### func \(TransactionTemplates\) [Validate]() ```go -func (t *TransactionTemplates) UnmarshalJSON(data []byte) error +func (t TransactionTemplates) Validate() error ``` diff --git a/internal/api/v2/controllers_bulk_test.go b/internal/api/v2/controllers_bulk_test.go index 94960b8f38..05b23607e1 100644 --- a/internal/api/v2/controllers_bulk_test.go +++ b/internal/api/v2/controllers_bulk_test.go @@ -100,7 +100,6 @@ func TestBulk(t *testing.T) { "metadata": map[string]any{}, "reverted": false, "id": float64(0), - "template": "", }, ResponseType: bulking.ActionCreateTransaction, }}, @@ -210,7 +209,6 @@ func TestBulk(t *testing.T) { }, "postings": nil, "reverted": false, - "template": "", "timestamp": "0001-01-01T00:00:00Z", }, ResponseType: bulking.ActionRevertTransaction, @@ -554,7 +552,6 @@ func TestBulk(t *testing.T) { "metadata": map[string]any{}, "reverted": false, "id": float64(0), - "template": "", }, ResponseType: bulking.ActionCreateTransaction, }}, diff --git a/internal/api/v2/views_test.go b/internal/api/v2/views_test.go index 7ff81ffd51..1835d38f43 100644 --- a/internal/api/v2/views_test.go +++ b/internal/api/v2/views_test.go @@ -153,7 +153,6 @@ func TestTransactionRender(t *testing.T) { }, }, }, - "template": "", }, }, { @@ -244,7 +243,6 @@ func TestTransactionRender(t *testing.T) { }, }, }, - "template": "", }, }, } { @@ -487,7 +485,6 @@ func TestLogRender(t *testing.T) { "updatedAt": now.Format(time.RFC3339Nano), "id": nil, "reverted": false, - "template": "", }, "accountMetadata": nil, }, @@ -531,7 +528,6 @@ func TestLogRender(t *testing.T) { "updatedAt": now.Format(time.RFC3339Nano), "id": nil, "reverted": false, - "template": "", }, "accountMetadata": nil, }, @@ -579,7 +575,6 @@ func TestLogRender(t *testing.T) { "updatedAt": now.Format(time.RFC3339Nano), "id": float64(1), "reverted": false, - "template": "", }, "transaction": map[string]any{ "postings": []any{ @@ -596,7 +591,6 @@ func TestLogRender(t *testing.T) { "updatedAt": now.Format(time.RFC3339Nano), "id": float64(2), "reverted": false, - "template": "", }, }, "date": now.Format(time.RFC3339Nano), @@ -646,7 +640,6 @@ func TestLogRender(t *testing.T) { "updatedAt": now.Format(time.RFC3339Nano), "id": float64(1), "reverted": false, - "template": "", }, "transaction": map[string]any{ "postings": []any{ @@ -663,7 +656,6 @@ func TestLogRender(t *testing.T) { "updatedAt": now.Format(time.RFC3339Nano), "id": float64(2), "reverted": false, - "template": "", }, }, "date": now.Format(time.RFC3339Nano), diff --git a/internal/controller/ledger/controller_default.go b/internal/controller/ledger/controller_default.go index 962dd93d31..5b6db4f174 100644 --- a/internal/controller/ledger/controller_default.go +++ b/internal/controller/ledger/controller_default.go @@ -425,7 +425,7 @@ func (ctrl *DefaultController) createTransaction(ctx context.Context, store Stor if template, ok := schema.SchemaData.Transactions[parameters.Input.Template]; ok { parameters.Input.Plain = template.Script if parameters.Input.Runtime == "" { - parameters.Input.Runtime = ledger.RuntimeType(template.Runtime) + parameters.Input.Runtime = template.Runtime } } else { return nil, newErrSchemaValidationError(parameters.SchemaVersion, fmt.Errorf("failed to find transaction template `%s`", parameters.Input.Template)) diff --git a/internal/schema.go b/internal/schema.go index fed1b0ec3e..09eb36fc83 100644 --- a/internal/schema.go +++ b/internal/schema.go @@ -27,6 +27,9 @@ func NewSchema(version string, data SchemaData) (Schema, error) { err: errors.New("missing chart of accounts"), } } + if err := data.Transactions.Validate(); err != nil { + return Schema{}, err + } return Schema{ Version: version, SchemaData: data, diff --git a/internal/transaction.go b/internal/transaction.go index bd92b9f458..67361a2062 100644 --- a/internal/transaction.go +++ b/internal/transaction.go @@ -49,7 +49,7 @@ type Transaction struct { // PostCommitEffectiveVolumes are the volumes of each account/asset after the transaction TransactionData.Timestamp. // Those volumes are also computed in flight, but can be updated if a transaction is inserted in the past. PostCommitEffectiveVolumes PostCommitVolumes `json:"postCommitEffectiveVolumes,omitempty" bun:"post_commit_effective_volumes,type:jsonb,scanonly"` - Template string `json:"template" bun:"template,type:text"` + Template string `json:"template,omitempty" bun:"template,type:text"` } func (Transaction) JSONSchemaExtend(schema *jsonschema.Schema) { diff --git a/internal/transaction_templates.go b/internal/transaction_templates.go index 95f1b3ca44..88beae8ffa 100644 --- a/internal/transaction_templates.go +++ b/internal/transaction_templates.go @@ -1,36 +1,30 @@ package ledger import ( - "encoding/json" + "fmt" + "slices" ) type RuntimeType string const ( - RuntimeExperimentalInterpreter RuntimeType = "experimental-interpreter" RuntimeMachine RuntimeType = "machine" + RuntimeExperimentalInterpreter RuntimeType = "experimental-interpreter" ) type TransactionTemplate struct { Description string `json:"description"` Script string `json:"script"` - Runtime RuntimeType `json:"runtime"` + Runtime RuntimeType `json:"runtime,omitempty"` } type TransactionTemplates map[string]TransactionTemplate -func (t *TransactionTemplates) UnmarshalJSON(data []byte) error { - type Templates TransactionTemplates - var templates Templates - if err := json.Unmarshal(data, &templates); err != nil { - return err - } - for id, tmpl := range templates { - if tmpl.Runtime == "" { - tmpl.Runtime = RuntimeMachine - templates[id] = tmpl +func (t TransactionTemplates) Validate() error { + for _, t := range t { + if !slices.Contains([]RuntimeType{"", RuntimeMachine, RuntimeExperimentalInterpreter}, t.Runtime) { + return fmt.Errorf("unexpected runtime `%s`: should be `%s` or `%s`", t.Runtime, RuntimeMachine, RuntimeExperimentalInterpreter) } } - *t = TransactionTemplates(templates) return nil } From 5d6812e655bf856b485a382fef749d570bbaf740 Mon Sep 17 00:00:00 2001 From: Azorlogh Date: Tue, 9 Dec 2025 12:21:21 +0100 Subject: [PATCH 08/14] remove required for plain --- docs/api/README.md | 4 +-- .../controller/ledger/controller_default.go | 2 +- openapi.yaml | 3 -- openapi/v2.yaml | 3 -- pkg/client/.speakeasy/gen.lock | 8 +++--- .../components/v2posttransactionscript.md | 2 +- .../docs/models/components/v2transaction.md | 2 +- pkg/client/docs/sdks/v2/README.md | 4 +-- .../models/components/v2posttransaction.go | 6 ++-- pkg/client/models/components/v2transaction.go | 6 ++-- pkg/generate/generator.go | 2 +- test/e2e/api_ledgers_import_test.go | 12 ++++---- test/e2e/api_schema_test.go | 6 ++-- test/e2e/api_transactions_create_test.go | 28 +++++++++---------- 14 files changed, 41 insertions(+), 47 deletions(-) diff --git a/docs/api/README.md b/docs/api/README.md index 8a5a4f6f99..fa44ed60fa 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -4004,7 +4004,7 @@ This operation does not require authentication |postCommitVolumes|[V2AggregatedVolumes](#schemav2aggregatedvolumes)|false|none|none| |preCommitEffectiveVolumes|[V2AggregatedVolumes](#schemav2aggregatedvolumes)|false|none|none| |postCommitEffectiveVolumes|[V2AggregatedVolumes](#schemav2aggregatedvolumes)|false|none|none| -|template|string|true|none|none| +|template|string|false|none|none|

V2PostTransaction

@@ -4057,7 +4057,7 @@ This operation does not require authentication |postings|[[V2Posting](#schemav2posting)]|false|none|none| |script|object|false|none|none| |» template|string|false|none|none| -|» plain|string|true|none|none| +|» plain|string|false|none|none| |» vars|object|false|none|none| |»» **additionalProperties**|string|false|none|none| |runtime|[Runtime](#schemaruntime)|false|none|The numscript runtime used to execute the script. Uses "machine" by default, unless the "--experimental-numscript-interpreter" feature flag is passed.| diff --git a/internal/controller/ledger/controller_default.go b/internal/controller/ledger/controller_default.go index 5b6db4f174..c75f124d9f 100644 --- a/internal/controller/ledger/controller_default.go +++ b/internal/controller/ledger/controller_default.go @@ -506,7 +506,7 @@ func (ctrl *DefaultController) CreateTransaction(ctx context.Context, parameters return ctrl.createTransactionLp.forgeLog(ctx, ctrl.store, parameters, ctrl.createTransaction) } -func (ctrl *DefaultController) revertTransaction(ctx context.Context, store Store, schema *ledger.Schema, parameters Parameters[RevertTransaction]) (*ledger.RevertedTransaction, error) { +func (ctrl *DefaultController) revertTransaction(ctx context.Context, store Store, _schema *ledger.Schema, parameters Parameters[RevertTransaction]) (*ledger.RevertedTransaction, error) { var ( hasBeenReverted bool err error diff --git a/openapi.yaml b/openapi.yaml index ff95c57ea2..6f3241be72 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -4134,7 +4134,6 @@ components: - id - metadata - reverted - - template V2PostTransaction: type: object required: @@ -4163,8 +4162,6 @@ components: type: string example: user: users:042 - required: - - plain runtime: $ref: "#/components/schemas/Runtime" reference: diff --git a/openapi/v2.yaml b/openapi/v2.yaml index a477e8aeb3..dec4c61f77 100644 --- a/openapi/v2.yaml +++ b/openapi/v2.yaml @@ -2358,7 +2358,6 @@ components: - id - metadata - reverted - - template V2PostTransaction: type: object required: @@ -2387,8 +2386,6 @@ components: type: string example: user: users:042 - required: - - plain runtime: $ref: "#/components/schemas/Runtime" reference: diff --git a/pkg/client/.speakeasy/gen.lock b/pkg/client/.speakeasy/gen.lock index e99bfff90c..a879ac1e02 100644 --- a/pkg/client/.speakeasy/gen.lock +++ b/pkg/client/.speakeasy/gen.lock @@ -1,7 +1,7 @@ lockVersion: 2.0.0 id: a9ac79e1-e429-4ee3-96c4-ec973f19bec3 management: - docChecksum: ea032aaa7f9e405bacc8804fdc420d05 + docChecksum: 7fc617ae7b7e3f9f38a7208da96d6503 docVersion: v2 speakeasyVersion: 1.563.0 generationVersion: 2.629.1 @@ -940,7 +940,7 @@ examples: application/json: {"postings": [{"amount": 100, "asset": "COIN", "destination": "users:002", "source": "users:001"}], "script": {"template": "CUSTOMER_DEPOSIT", "plain": "vars {\naccount $user\n}\nsend [COIN 10] (\n\tsource = @world\n\tdestination = $user\n)\n", "vars": {"user": "users:042"}}, "reference": "ref:001", "metadata": {"admin": "true"}, "accountMetadata": {"key": {"admin": "true"}, "key1": {"admin": "true"}, "key2": {"admin": "true"}}} responses: "200": - application/json: {"data": {"timestamp": "2024-06-10T07:40:23.819Z", "postings": [{"amount": 100, "asset": "COIN", "destination": "users:002", "source": "users:001"}], "reference": "ref:001", "metadata": {"admin": "true"}, "id": 339560, "reverted": true, "preCommitVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "postCommitVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "preCommitEffectiveVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "postCommitEffectiveVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "template": ""}} + application/json: {"data": {"timestamp": "2024-06-10T07:40:23.819Z", "postings": [{"amount": 100, "asset": "COIN", "destination": "users:002", "source": "users:001"}], "reference": "ref:001", "metadata": {"admin": "true"}, "id": 339560, "reverted": true, "preCommitVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "postCommitVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "preCommitEffectiveVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "postCommitEffectiveVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}}} default: application/json: {"errorCode": "VALIDATION", "errorMessage": "[VALIDATION] invalid 'cursor' query param", "details": "https://play.numscript.org/?payload=eyJlcnJvciI6ImFjY291bnQgaGFkIGluc3VmZmljaWVudCBmdW5kcyJ9"} v2GetTransaction: @@ -951,7 +951,7 @@ examples: id: 1234 responses: "200": - application/json: {"data": {"timestamp": "2024-10-06T11:21:14.840Z", "postings": [], "reference": "ref:001", "metadata": {"admin": "true"}, "id": 232775, "reverted": false, "preCommitVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "postCommitVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "preCommitEffectiveVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "postCommitEffectiveVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "template": ""}} + application/json: {"data": {"timestamp": "2024-10-06T11:21:14.840Z", "postings": [], "reference": "ref:001", "metadata": {"admin": "true"}, "id": 232775, "reverted": false, "preCommitVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "postCommitVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "preCommitEffectiveVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "postCommitEffectiveVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}}} default: application/json: {"errorCode": "VALIDATION", "errorMessage": "[VALIDATION] invalid 'cursor' query param", "details": "https://play.numscript.org/?payload=eyJlcnJvciI6ImFjY291bnQgaGFkIGluc3VmZmljaWVudCBmdW5kcyJ9"} v2AddMetadataOnTransaction: @@ -989,7 +989,7 @@ examples: schemaVersion: "v1.0.0" responses: "201": - application/json: {"data": {"timestamp": "2025-01-07T04:49:40.482Z", "postings": [], "reference": "ref:001", "metadata": {"admin": "true"}, "id": 670435, "reverted": true, "preCommitVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "postCommitVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "preCommitEffectiveVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "postCommitEffectiveVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "template": ""}} + application/json: {"data": {"timestamp": "2025-01-07T04:49:40.482Z", "postings": [], "reference": "ref:001", "metadata": {"admin": "true"}, "id": 670435, "reverted": true, "preCommitVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "postCommitVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "preCommitEffectiveVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}, "postCommitEffectiveVolumes": {"orders:1": {"USD": {"input": 100, "output": 10, "balance": 90}}, "orders:2": {"USD": {"input": 100, "output": 10, "balance": 90}}}}} default: application/json: {"errorCode": "VALIDATION", "errorMessage": "[VALIDATION] invalid 'cursor' query param", "details": "https://play.numscript.org/?payload=eyJlcnJvciI6ImFjY291bnQgaGFkIGluc3VmZmljaWVudCBmdW5kcyJ9"} v2GetBalancesAggregated: diff --git a/pkg/client/docs/models/components/v2posttransactionscript.md b/pkg/client/docs/models/components/v2posttransactionscript.md index 48b20a151b..dd05ffc21e 100644 --- a/pkg/client/docs/models/components/v2posttransactionscript.md +++ b/pkg/client/docs/models/components/v2posttransactionscript.md @@ -6,5 +6,5 @@ | Field | Type | Required | Description | Example | | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | | `Template` | **string* | :heavy_minus_sign: | N/A | CUSTOMER_DEPOSIT | -| `Plain` | *string* | :heavy_check_mark: | N/A | vars {
account $user
}
send [COIN 10] (
source = @world
destination = $user
)
| +| `Plain` | **string* | :heavy_minus_sign: | N/A | vars {
account $user
}
send [COIN 10] (
source = @world
destination = $user
)
| | `Vars` | map[string]*string* | :heavy_minus_sign: | N/A | {
"user": "users:042"
} | \ No newline at end of file diff --git a/pkg/client/docs/models/components/v2transaction.md b/pkg/client/docs/models/components/v2transaction.md index 1b721eadcd..37b415dd90 100644 --- a/pkg/client/docs/models/components/v2transaction.md +++ b/pkg/client/docs/models/components/v2transaction.md @@ -18,4 +18,4 @@ | `PostCommitVolumes` | map[string]map[string][components.V2Volume](../../models/components/v2volume.md) | :heavy_minus_sign: | N/A | {
"orders:1": {
"USD": {
"input": 100,
"output": 10,
"balance": 90
}
},
"orders:2": {
"USD": {
"input": 100,
"output": 10,
"balance": 90
}
}
} | | `PreCommitEffectiveVolumes` | map[string]map[string][components.V2Volume](../../models/components/v2volume.md) | :heavy_minus_sign: | N/A | {
"orders:1": {
"USD": {
"input": 100,
"output": 10,
"balance": 90
}
},
"orders:2": {
"USD": {
"input": 100,
"output": 10,
"balance": 90
}
}
} | | `PostCommitEffectiveVolumes` | map[string]map[string][components.V2Volume](../../models/components/v2volume.md) | :heavy_minus_sign: | N/A | {
"orders:1": {
"USD": {
"input": 100,
"output": 10,
"balance": 90
}
},
"orders:2": {
"USD": {
"input": 100,
"output": 10,
"balance": 90
}
}
} | -| `Template` | *string* | :heavy_check_mark: | N/A | | \ No newline at end of file +| `Template` | **string* | :heavy_minus_sign: | N/A | | \ 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 e8c79f07a4..bac3235e7c 100644 --- a/pkg/client/docs/sdks/v2/README.md +++ b/pkg/client/docs/sdks/v2/README.md @@ -1224,14 +1224,14 @@ func main() { }, Script: &components.V2PostTransactionScript{ Template: client.String("CUSTOMER_DEPOSIT"), - Plain: "vars {\n" + + Plain: client.String("vars {\n" + "account $user\n" + "}\n" + "send [COIN 10] (\n" + " source = @world\n" + " destination = $user\n" + ")\n" + - "", + ""), Vars: map[string]string{ "user": "users:042", }, diff --git a/pkg/client/models/components/v2posttransaction.go b/pkg/client/models/components/v2posttransaction.go index 0ec5359914..579c8a44a3 100644 --- a/pkg/client/models/components/v2posttransaction.go +++ b/pkg/client/models/components/v2posttransaction.go @@ -9,7 +9,7 @@ import ( type V2PostTransactionScript struct { Template *string `json:"template,omitempty"` - Plain string `json:"plain"` + Plain *string `json:"plain,omitempty"` Vars map[string]string `json:"vars,omitempty"` } @@ -20,9 +20,9 @@ func (o *V2PostTransactionScript) GetTemplate() *string { return o.Template } -func (o *V2PostTransactionScript) GetPlain() string { +func (o *V2PostTransactionScript) GetPlain() *string { if o == nil { - return "" + return nil } return o.Plain } diff --git a/pkg/client/models/components/v2transaction.go b/pkg/client/models/components/v2transaction.go index dc84d51535..7a5d8850cc 100644 --- a/pkg/client/models/components/v2transaction.go +++ b/pkg/client/models/components/v2transaction.go @@ -22,7 +22,7 @@ type V2Transaction struct { PostCommitVolumes map[string]map[string]V2Volume `json:"postCommitVolumes,omitempty"` PreCommitEffectiveVolumes map[string]map[string]V2Volume `json:"preCommitEffectiveVolumes,omitempty"` PostCommitEffectiveVolumes map[string]map[string]V2Volume `json:"postCommitEffectiveVolumes,omitempty"` - Template string `json:"template"` + Template *string `json:"template,omitempty"` } func (v V2Transaction) MarshalJSON() ([]byte, error) { @@ -127,9 +127,9 @@ func (o *V2Transaction) GetPostCommitEffectiveVolumes() map[string]map[string]V2 return o.PostCommitEffectiveVolumes } -func (o *V2Transaction) GetTemplate() string { +func (o *V2Transaction) GetTemplate() *string { if o == nil { - return "" + return nil } return o.Template } diff --git a/pkg/generate/generator.go b/pkg/generate/generator.go index 14db87c0e3..fc4c9926c9 100644 --- a/pkg/generate/generator.go +++ b/pkg/generate/generator.go @@ -48,7 +48,7 @@ func (r Action) Apply(ctx context.Context, client *client.V2, l string) ([]compo return &transactionRequest.Timestamp.Time }(), Script: &components.V2PostTransactionScript{ - Plain: transactionRequest.Script.Plain, + Plain: pointer.For(transactionRequest.Script.Plain), Vars: collectionutils.ConvertMap(transactionRequest.Script.Vars, func(from any) string { return fmt.Sprint(from) }), diff --git a/test/e2e/api_ledgers_import_test.go b/test/e2e/api_ledgers_import_test.go index 6a3217bbea..572eb7ce3b 100644 --- a/test/e2e/api_ledgers_import_test.go +++ b/test/e2e/api_ledgers_import_test.go @@ -187,12 +187,12 @@ var _ = Context("Ledger engine tests", func() { Ledger: createLedgerRequest.Ledger, V2PostTransaction: components.V2PostTransaction{ Script: &components.V2PostTransactionScript{ - Plain: `send [COIN 100] ( + Plain: pointer.For(`send [COIN 100] ( source = @world destination = @bob ) set_account_meta(@world, "foo", "bar") - `, + `), }, }, }) @@ -204,12 +204,12 @@ var _ = Context("Ledger engine tests", func() { DryRun: pointer.For(true), V2PostTransaction: components.V2PostTransaction{ Script: &components.V2PostTransactionScript{ - Plain: `send [COIN 100] ( + Plain: pointer.For(`send [COIN 100] ( source = @world destination = @bob ) set_account_meta(@world, "foo", "bar") - `, + `), }, }, }) @@ -219,12 +219,12 @@ var _ = Context("Ledger engine tests", func() { Ledger: createLedgerRequest.Ledger, V2PostTransaction: components.V2PostTransaction{ Script: &components.V2PostTransactionScript{ - Plain: `send [COIN 100] ( + Plain: pointer.For(`send [COIN 100] ( source = @world destination = @bob ) set_account_meta(@world, "foo", "bar") - `, + `), }, }, }) diff --git a/test/e2e/api_schema_test.go b/test/e2e/api_schema_test.go index 6be5ef7f86..58bd855750 100644 --- a/test/e2e/api_schema_test.go +++ b/test/e2e/api_schema_test.go @@ -248,7 +248,7 @@ var _ = Context("Ledger schema API tests", func() { ID: res.V2CreateTransactionResponse.Data.ID, }) Expect(err).To(BeNil()) - Expect(getTxRes.V2GetTransactionResponse.Data.Template).To(Equal("WORLD_TO_BANK")) + Expect(getTxRes.V2GetTransactionResponse.Data.Template).To(Equal(pointer.For("WORLD_TO_BANK"))) }) It("should create transaction with v2.0.0 schema", func(specContext SpecContext) { @@ -477,11 +477,11 @@ var _ = Context("Ledger schema API tests", func() { V2PostTransaction: components.V2PostTransaction{ Force: pointer.For(true), Script: pointer.For(components.V2PostTransactionScript{ - Plain: ` + Plain: pointer.For(` send [EUR/2] ( source = $users:001 destination = $bank:001 - )`, + )`), }), }, }) diff --git a/test/e2e/api_transactions_create_test.go b/test/e2e/api_transactions_create_test.go index 177e44fd82..f00607c3c4 100644 --- a/test/e2e/api_transactions_create_test.go +++ b/test/e2e/api_transactions_create_test.go @@ -100,14 +100,14 @@ var _ = Context("Ledger transactions create API tests", func() { Expect(err).ToNot(HaveOccurred()) req.V2PostTransaction.Script = &components.V2PostTransactionScript{ - Plain: ` + Plain: pointer.For(` send [USD 100] ( source = @world destination = @alice ) set_account_meta(@alice, "clientType", "silver") set_account_meta(@foo, "status", "pending") - `, + `), } }) It("should override account metadata", func(specContext SpecContext) { @@ -324,7 +324,7 @@ var _ = Context("Ledger transactions create API tests", func() { Script: &components.V2PostTransactionScript{ // note that we're missing newlines here, // so this is only accepted by the "interpreter" runtime - Plain: `send [USD 100] ( source = @world destination = @alice )`, + Plain: pointer.For(`send [USD 100] ( source = @world destination = @alice )`), Vars: map[string]string{}, }, }, @@ -492,10 +492,10 @@ var _ = Context("Ledger transactions create API tests", func() { V2PostTransaction: components.V2PostTransaction{ Metadata: map[string]string{}, Script: &components.V2PostTransactionScript{ - Plain: `send [COIN -100] ( + Plain: pointer.For(`send [COIN -100] ( source = @world destination = @bob - )`, + )`), Vars: map[string]string{}, }, }, @@ -522,13 +522,13 @@ var _ = Context("Ledger transactions create API tests", func() { V2PostTransaction: components.V2PostTransaction{ Metadata: map[string]string{}, Script: &components.V2PostTransactionScript{ - Plain: `vars { + Plain: pointer.For(`vars { monetary $amount } send $amount ( source = @world destination = @bob - )`, + )`), Vars: map[string]string{ "amount": "USD -100", }, @@ -556,7 +556,7 @@ var _ = Context("Ledger transactions create API tests", func() { V2PostTransaction: components.V2PostTransaction{ Metadata: map[string]string{}, Script: &components.V2PostTransactionScript{ - Plain: `XXX`, + Plain: pointer.For(`XXX`), Vars: map[string]string{}, }, }, @@ -581,11 +581,11 @@ var _ = Context("Ledger transactions create API tests", func() { V2PostTransaction: components.V2PostTransaction{ Metadata: map[string]string{}, Script: &components.V2PostTransactionScript{ - Plain: `vars { + Plain: pointer.For(`vars { monetary $amount } set_tx_meta("foo", "bar") - `, + `), Vars: map[string]string{ "amount": "USD 100", }, @@ -608,11 +608,11 @@ var _ = Context("Ledger transactions create API tests", func() { "foo": "baz", }, Script: &components.V2PostTransactionScript{ - Plain: `send [COIN 100] ( + Plain: pointer.For(`send [COIN 100] ( source = @world destination = @bob ) - set_tx_meta("foo", "bar")`, + set_tx_meta("foo", "bar")`), Vars: map[string]string{}, }, }, @@ -631,10 +631,10 @@ var _ = Context("Ledger transactions create API tests", func() { V2PostTransaction: components.V2PostTransaction{ Metadata: map[string]string{}, Script: &components.V2PostTransactionScript{ - Plain: `send [COIN 100] ( + Plain: pointer.For(`send [COIN 100] ( source = @world destination = @bob - )`, + )`), Vars: map[string]string{}, }, }, From 25a2e1fd768f65b06be65c24ec250411cfee6a8f Mon Sep 17 00:00:00 2001 From: Azorlogh Date: Wed, 10 Dec 2025 10:59:12 +0100 Subject: [PATCH 09/14] respect schema enforcement mode --- .../controller/ledger/controller_default.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/internal/controller/ledger/controller_default.go b/internal/controller/ledger/controller_default.go index c75f124d9f..8c658a5f2a 100644 --- a/internal/controller/ledger/controller_default.go +++ b/internal/controller/ledger/controller_default.go @@ -10,6 +10,7 @@ import ( "github.com/google/uuid" "github.com/uptrace/bun" + "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" noopmetrics "go.opentelemetry.io/otel/metric/noop" "go.opentelemetry.io/otel/trace" @@ -420,7 +421,13 @@ func (ctrl *DefaultController) createTransaction(ctx context.Context, store Stor if schema != nil { if parameters.Input.Template == "" { - return nil, newErrSchemaValidationError(parameters.SchemaVersion, fmt.Errorf("transactions on this ledger must use a template")) + err := newErrSchemaValidationError(parameters.SchemaVersion, fmt.Errorf("transactions on this ledger must use a template")) + if ctrl.schemaEnforcementMode == SchemaEnforcementStrict { + return nil, err + } else { + trace.SpanFromContext(ctx).SetAttributes(attribute.String("schema_validation_failed", err.Error())) + logging.FromContext(ctx).Errorf("schema validation failed: %s", err) + } } if template, ok := schema.SchemaData.Transactions[parameters.Input.Template]; ok { parameters.Input.Plain = template.Script @@ -428,7 +435,13 @@ func (ctrl *DefaultController) createTransaction(ctx context.Context, store Stor parameters.Input.Runtime = template.Runtime } } else { - return nil, newErrSchemaValidationError(parameters.SchemaVersion, fmt.Errorf("failed to find transaction template `%s`", parameters.Input.Template)) + err := newErrSchemaValidationError(parameters.SchemaVersion, fmt.Errorf("failed to find transaction template `%s`", parameters.Input.Template)) + if ctrl.schemaEnforcementMode == SchemaEnforcementStrict { + return nil, err + } else { + trace.SpanFromContext(ctx).SetAttributes(attribute.String("schema_validation_failed", err.Error())) + logging.FromContext(ctx).Errorf("schema validation failed: %s", err) + } } } From 23f781a728b43b97088163f760f81decb10e6400 Mon Sep 17 00:00:00 2001 From: Azorlogh Date: Fri, 12 Dec 2025 16:23:30 +0100 Subject: [PATCH 10/14] force presence of `transactions`, omitempty on vm.Script --- docs/api/README.md | 2 +- internal/machine/vm/run.go | 4 ++-- internal/schema.go | 9 +++++---- openapi.yaml | 1 + openapi/v2.yaml | 1 + pkg/client/.speakeasy/gen.lock | 6 +++--- pkg/client/docs/models/components/v2schema.md | 2 +- pkg/client/docs/models/components/v2schemadata.md | 2 +- pkg/client/docs/sdks/v2/README.md | 5 +++++ pkg/client/models/components/v2schema.go | 4 ++-- pkg/client/models/components/v2schemadata.go | 4 ++-- test/e2e/api_idempotency_hit_header_test.go | 1 + test/e2e/api_schema_test.go | 3 ++- 13 files changed, 27 insertions(+), 17 deletions(-) diff --git a/docs/api/README.md b/docs/api/README.md index fa44ed60fa..e20c9ba9b9 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -5902,7 +5902,7 @@ Schema data structure for ledger schemas |Name|Type|Required|Restrictions|Description| |---|---|---|---|---| |chart|[V2ChartOfAccounts](#schemav2chartofaccounts)|true|none|Chart of account| -|transactions|[V2TransactionTemplates](#schemav2transactiontemplates)|false|none|Transaction templates| +|transactions|[V2TransactionTemplates](#schemav2transactiontemplates)|true|none|Transaction templates|

V2Schema

diff --git a/internal/machine/vm/run.go b/internal/machine/vm/run.go index 8d0ad3a168..322f64cb46 100644 --- a/internal/machine/vm/run.go +++ b/internal/machine/vm/run.go @@ -19,8 +19,8 @@ type RunScript struct { } type Script struct { - Plain string `json:"plain"` - Template string `json:"template"` + Plain string `json:"plain,omitempty"` + Template string `json:"template,omitempty"` Vars map[string]string `json:"vars" swaggertype:"object"` } diff --git a/internal/schema.go b/internal/schema.go index 09eb36fc83..f588d6429b 100644 --- a/internal/schema.go +++ b/internal/schema.go @@ -23,12 +23,13 @@ type Schema struct { func NewSchema(version string, data SchemaData) (Schema, error) { if data.Chart == nil { - return Schema{}, ErrInvalidSchema{ - err: errors.New("missing chart of accounts"), - } + return Schema{}, NewErrInvalidSchema(errors.New("missing chart of accounts")) + } + if data.Transactions == nil { + return Schema{}, NewErrInvalidSchema(errors.New("missing transaction templates")) } if err := data.Transactions.Validate(); err != nil { - return Schema{}, err + return Schema{}, NewErrInvalidSchema(err) } return Schema{ Version: version, diff --git a/openapi.yaml b/openapi.yaml index 6f3241be72..7b1b8be817 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -4621,6 +4621,7 @@ components: $ref: "#/components/schemas/V2TransactionTemplates" required: - chart + - transactions V2Schema: type: object description: Complete schema structure with metadata diff --git a/openapi/v2.yaml b/openapi/v2.yaml index dec4c61f77..b2e77ebbc5 100644 --- a/openapi/v2.yaml +++ b/openapi/v2.yaml @@ -2845,6 +2845,7 @@ components: $ref: "#/components/schemas/V2TransactionTemplates" required: - chart + - transactions V2Schema: type: object description: Complete schema structure with metadata diff --git a/pkg/client/.speakeasy/gen.lock b/pkg/client/.speakeasy/gen.lock index a879ac1e02..a7ce3fc306 100644 --- a/pkg/client/.speakeasy/gen.lock +++ b/pkg/client/.speakeasy/gen.lock @@ -1,7 +1,7 @@ lockVersion: 2.0.0 id: a9ac79e1-e429-4ee3-96c4-ec973f19bec3 management: - docChecksum: 7fc617ae7b7e3f9f38a7208da96d6503 + docChecksum: ea145c98b439cd8986d2f3f6160fe430 docVersion: v2 speakeasyVersion: 1.563.0 generationVersion: 2.629.1 @@ -1226,7 +1226,7 @@ examples: ledger: "ledger001" version: "v1.0.0" requestBody: - application/json: {"chart": {"users": {"$userID": {".pattern": "^[0-9]{16}$"}}}} + application/json: {"chart": {"users": {"$userID": {".pattern": "^[0-9]{16}$"}}}, "transactions": {"key": {"script": ""}}} responses: default: application/json: {"errorCode": "VALIDATION", "errorMessage": "[VALIDATION] invalid 'cursor' query param", "details": "https://play.numscript.org/?payload=eyJlcnJvciI6ImFjY291bnQgaGFkIGluc3VmZmljaWVudCBmdW5kcyJ9"} @@ -1238,7 +1238,7 @@ examples: version: "v1.0.0" responses: "200": - application/json: {"data": {"version": "v1.0.0", "createdAt": "2023-01-01T00:00:00Z", "chart": {"users": {"$userID": {".pattern": "^[0-9]{16}$"}}}}} + application/json: {"data": {"version": "v1.0.0", "createdAt": "2023-01-01T00:00:00Z", "chart": {"users": {"$userID": {".pattern": "^[0-9]{16}$"}}}, "transactions": {"key": {"script": ""}}}} default: application/json: {"errorCode": "VALIDATION", "errorMessage": "[VALIDATION] invalid 'cursor' query param", "details": "https://play.numscript.org/?payload=eyJlcnJvciI6ImFjY291bnQgaGFkIGluc3VmZmljaWVudCBmdW5kcyJ9"} v2ListSchemas: diff --git a/pkg/client/docs/models/components/v2schema.md b/pkg/client/docs/models/components/v2schema.md index 75e4fa8be6..022e5d2cfb 100644 --- a/pkg/client/docs/models/components/v2schema.md +++ b/pkg/client/docs/models/components/v2schema.md @@ -10,4 +10,4 @@ Complete schema structure with metadata | `Version` | *string* | :heavy_check_mark: | Schema version | v1.0.0 | | `CreatedAt` | [time.Time](https://pkg.go.dev/time#Time) | :heavy_check_mark: | Schema creation timestamp | 2023-01-01T00:00:00Z | | `Chart` | map[string][components.V2ChartSegment](../../models/components/v2chartsegment.md) | :heavy_check_mark: | Chart of account | {
"users": {
"$userID": {
".pattern": "^[0-9]{16}$"
}
}
} | -| `Transactions` | map[string][components.V2TransactionTemplate](../../models/components/v2transactiontemplate.md) | :heavy_minus_sign: | Transaction templates | | \ No newline at end of file +| `Transactions` | map[string][components.V2TransactionTemplate](../../models/components/v2transactiontemplate.md) | :heavy_check_mark: | Transaction templates | | \ No newline at end of file diff --git a/pkg/client/docs/models/components/v2schemadata.md b/pkg/client/docs/models/components/v2schemadata.md index a3be8db307..9f0044ccfa 100644 --- a/pkg/client/docs/models/components/v2schemadata.md +++ b/pkg/client/docs/models/components/v2schemadata.md @@ -8,4 +8,4 @@ Schema data structure for ledger schemas | Field | Type | Required | Description | Example | | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | `Chart` | map[string][components.V2ChartSegment](../../models/components/v2chartsegment.md) | :heavy_check_mark: | Chart of account | {
"users": {
"$userID": {
".pattern": "^[0-9]{16}$"
}
}
} | -| `Transactions` | map[string][components.V2TransactionTemplate](../../models/components/v2transactiontemplate.md) | :heavy_minus_sign: | Transaction templates | | \ No newline at end of file +| `Transactions` | map[string][components.V2TransactionTemplate](../../models/components/v2transactiontemplate.md) | :heavy_check_mark: | Transaction templates | | \ 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 bac3235e7c..ada8e8d3d1 100644 --- a/pkg/client/docs/sdks/v2/README.md +++ b/pkg/client/docs/sdks/v2/README.md @@ -278,6 +278,11 @@ func main() { }, }, }, + Transactions: map[string]components.V2TransactionTemplate{ + "key": components.V2TransactionTemplate{ + Script: "", + }, + }, }, }) if err != nil { diff --git a/pkg/client/models/components/v2schema.go b/pkg/client/models/components/v2schema.go index da590480c3..062d5ab543 100644 --- a/pkg/client/models/components/v2schema.go +++ b/pkg/client/models/components/v2schema.go @@ -16,7 +16,7 @@ type V2Schema struct { // Chart of account Chart map[string]V2ChartSegment `json:"chart"` // Transaction templates - Transactions map[string]V2TransactionTemplate `json:"transactions,omitempty"` + Transactions map[string]V2TransactionTemplate `json:"transactions"` } func (v V2Schema) MarshalJSON() ([]byte, error) { @@ -53,7 +53,7 @@ func (o *V2Schema) GetChart() map[string]V2ChartSegment { func (o *V2Schema) GetTransactions() map[string]V2TransactionTemplate { if o == nil { - return nil + return map[string]V2TransactionTemplate{} } return o.Transactions } diff --git a/pkg/client/models/components/v2schemadata.go b/pkg/client/models/components/v2schemadata.go index d0b68044f2..f5ef95b556 100644 --- a/pkg/client/models/components/v2schemadata.go +++ b/pkg/client/models/components/v2schemadata.go @@ -7,7 +7,7 @@ type V2SchemaData struct { // Chart of account Chart map[string]V2ChartSegment `json:"chart"` // Transaction templates - Transactions map[string]V2TransactionTemplate `json:"transactions,omitempty"` + Transactions map[string]V2TransactionTemplate `json:"transactions"` } func (o *V2SchemaData) GetChart() map[string]V2ChartSegment { @@ -19,7 +19,7 @@ func (o *V2SchemaData) GetChart() map[string]V2ChartSegment { func (o *V2SchemaData) GetTransactions() map[string]V2TransactionTemplate { if o == nil { - return nil + return map[string]V2TransactionTemplate{} } return o.Transactions } diff --git a/test/e2e/api_idempotency_hit_header_test.go b/test/e2e/api_idempotency_hit_header_test.go index 2d25932e13..75e598624a 100644 --- a/test/e2e/api_idempotency_hit_header_test.go +++ b/test/e2e/api_idempotency_hit_header_test.go @@ -483,6 +483,7 @@ var _ = Context("Idempotency-Hit header tests", func() { Chart: map[string]components.V2ChartSegment{ "bank": {}, }, + Transactions: map[string]components.V2TransactionTemplate{}, }, Version: "v1.0.0", Ledger: "default", diff --git a/test/e2e/api_schema_test.go b/test/e2e/api_schema_test.go index 58bd855750..9f98975b7f 100644 --- a/test/e2e/api_schema_test.go +++ b/test/e2e/api_schema_test.go @@ -179,7 +179,8 @@ var _ = Context("Ledger schema API tests", func() { Ledger: "default", Version: "v3.0.0", V2SchemaData: components.V2SchemaData{ - Chart: map[string]components.V2ChartSegment{}, + Chart: map[string]components.V2ChartSegment{}, + Transactions: map[string]components.V2TransactionTemplate{}, }, }) Expect(err).To(HaveErrorCode(string(components.V2ErrorsEnumSchemaAlreadyExists))) From 8924428d1e556df32113b5d266b6e6e75b7f106b Mon Sep 17 00:00:00 2001 From: Azorlogh Date: Fri, 12 Dec 2025 16:43:38 +0100 Subject: [PATCH 11/14] fix test --- internal/storage/ledger/schema_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/storage/ledger/schema_test.go b/internal/storage/ledger/schema_test.go index 2314e3c114..7e468c8c4f 100644 --- a/internal/storage/ledger/schema_test.go +++ b/internal/storage/ledger/schema_test.go @@ -20,7 +20,8 @@ func TestSchemaInsert(t *testing.T) { store := newLedgerStore(t) schema, err := ledger.NewSchema("1.0", ledger.SchemaData{ - Chart: map[string]ledger.ChartSegment{}, + Chart: map[string]ledger.ChartSegment{}, + Transactions: map[string]ledger.TransactionTemplate{}, }) require.NoError(t, err) err = store.InsertSchema(ctx, &schema) From c30c80478b607f64c942ac924f1c16e97b9692fa Mon Sep 17 00:00:00 2001 From: Azorlogh Date: Thu, 18 Dec 2025 11:15:22 +0100 Subject: [PATCH 12/14] pre-commit --- internal/README.md | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/internal/README.md b/internal/README.md index 32fc224f2e..cd10496acc 100644 --- a/internal/README.md +++ b/internal/README.md @@ -1665,7 +1665,7 @@ type SchemaData struct { ``` -## type [Transaction]() +## type [Transaction]() @@ -1689,7 +1689,7 @@ type Transaction struct { ``` -### func [NewTransaction]() +### func [NewTransaction]() ```go func NewTransaction() Transaction @@ -1698,7 +1698,7 @@ func NewTransaction() Transaction -### func \(\*Transaction\) [AccountsWithDefaultMetadata]() +### func \(\*Transaction\) [AccountsWithDefaultMetadata]() ```go func (tx *Transaction) AccountsWithDefaultMetadata(schema *Schema, accountMetadata map[string]metadata.Metadata) []AccountWithDefaultMetadata @@ -1707,7 +1707,7 @@ func (tx *Transaction) AccountsWithDefaultMetadata(schema *Schema, accountMetada -### func \(Transaction\) [InvolvedAccounts]() +### func \(Transaction\) [InvolvedAccounts]() ```go func (tx Transaction) InvolvedAccounts() []string @@ -1716,7 +1716,7 @@ func (tx Transaction) InvolvedAccounts() []string -### func \(Transaction\) [InvolvedDestinations]() +### func \(Transaction\) [InvolvedDestinations]() ```go func (tx Transaction) InvolvedDestinations() map[string][]string @@ -1725,7 +1725,7 @@ func (tx Transaction) InvolvedDestinations() map[string][]string -### func \(Transaction\) [IsReverted]() +### func \(Transaction\) [IsReverted]() ```go func (tx Transaction) IsReverted() bool @@ -1734,7 +1734,7 @@ func (tx Transaction) IsReverted() bool -### func \(Transaction\) [JSONSchemaExtend]() +### func \(Transaction\) [JSONSchemaExtend]() ```go func (Transaction) JSONSchemaExtend(schema *jsonschema.Schema) @@ -1743,7 +1743,7 @@ func (Transaction) JSONSchemaExtend(schema *jsonschema.Schema) -### func \(Transaction\) [MarshalJSON]() +### func \(Transaction\) [MarshalJSON]() ```go func (tx Transaction) MarshalJSON() ([]byte, error) @@ -1752,7 +1752,7 @@ func (tx Transaction) MarshalJSON() ([]byte, error) -### func \(Transaction\) [Reverse]() +### func \(Transaction\) [Reverse]() ```go func (tx Transaction) Reverse() Transaction @@ -1761,7 +1761,7 @@ func (tx Transaction) Reverse() Transaction -### func \(Transaction\) [VolumeUpdates]() +### func \(Transaction\) [VolumeUpdates]() ```go func (tx Transaction) VolumeUpdates() []AccountsVolumes @@ -1770,7 +1770,7 @@ func (tx Transaction) VolumeUpdates() []AccountsVolumes -### func \(Transaction\) [WithID]() +### func \(Transaction\) [WithID]() ```go func (tx Transaction) WithID(id uint64) Transaction @@ -1779,7 +1779,7 @@ func (tx Transaction) WithID(id uint64) Transaction -### func \(Transaction\) [WithInsertedAt]() +### func \(Transaction\) [WithInsertedAt]() ```go func (tx Transaction) WithInsertedAt(date time.Time) Transaction @@ -1788,7 +1788,7 @@ func (tx Transaction) WithInsertedAt(date time.Time) Transaction -### func \(Transaction\) [WithMetadata]() +### func \(Transaction\) [WithMetadata]() ```go func (tx Transaction) WithMetadata(m metadata.Metadata) Transaction @@ -1797,7 +1797,7 @@ func (tx Transaction) WithMetadata(m metadata.Metadata) Transaction -### func \(Transaction\) [WithPostCommitEffectiveVolumes]() +### func \(Transaction\) [WithPostCommitEffectiveVolumes]() ```go func (tx Transaction) WithPostCommitEffectiveVolumes(volumes PostCommitVolumes) Transaction @@ -1806,7 +1806,7 @@ func (tx Transaction) WithPostCommitEffectiveVolumes(volumes PostCommitVolumes) -### func \(Transaction\) [WithPostCommitVolumes]() +### func \(Transaction\) [WithPostCommitVolumes]() ```go func (tx Transaction) WithPostCommitVolumes(volumes PostCommitVolumes) Transaction @@ -1815,7 +1815,7 @@ func (tx Transaction) WithPostCommitVolumes(volumes PostCommitVolumes) Transacti -### func \(Transaction\) [WithPostings]() +### func \(Transaction\) [WithPostings]() ```go func (tx Transaction) WithPostings(postings ...Posting) Transaction @@ -1824,7 +1824,7 @@ func (tx Transaction) WithPostings(postings ...Posting) Transaction -### func \(Transaction\) [WithReference]() +### func \(Transaction\) [WithReference]() ```go func (tx Transaction) WithReference(ref string) Transaction @@ -1833,7 +1833,7 @@ func (tx Transaction) WithReference(ref string) Transaction -### func \(Transaction\) [WithRevertedAt]() +### func \(Transaction\) [WithRevertedAt]() ```go func (tx Transaction) WithRevertedAt(timestamp time.Time) Transaction @@ -1842,7 +1842,7 @@ func (tx Transaction) WithRevertedAt(timestamp time.Time) Transaction -### func \(Transaction\) [WithTemplate]() +### func \(Transaction\) [WithTemplate]() ```go func (tx Transaction) WithTemplate(template string) Transaction @@ -1851,7 +1851,7 @@ func (tx Transaction) WithTemplate(template string) Transaction -### func \(Transaction\) [WithTimestamp]() +### func \(Transaction\) [WithTimestamp]() ```go func (tx Transaction) WithTimestamp(ts time.Time) Transaction @@ -1860,7 +1860,7 @@ func (tx Transaction) WithTimestamp(ts time.Time) Transaction -### func \(Transaction\) [WithUpdatedAt]() +### func \(Transaction\) [WithUpdatedAt]() ```go func (tx Transaction) WithUpdatedAt(at time.Time) Transaction From 482ef49dcf6a164810d872b567676eaaecf61666 Mon Sep 17 00:00:00 2001 From: Azorlogh Date: Thu, 18 Dec 2025 11:24:03 +0100 Subject: [PATCH 13/14] migration 45 --- internal/storage/bucket/default_bucket.go | 2 +- .../storage/bucket/migrations/44-add-schema/up.sql | 4 ---- .../45-add-transaction-templates/notes.yaml | 1 + .../migrations/45-add-transaction-templates/up.sql | 11 +++++++++++ 4 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 internal/storage/bucket/migrations/45-add-transaction-templates/notes.yaml create mode 100644 internal/storage/bucket/migrations/45-add-transaction-templates/up.sql diff --git a/internal/storage/bucket/default_bucket.go b/internal/storage/bucket/default_bucket.go index a41bc7b048..ae69788904 100644 --- a/internal/storage/bucket/default_bucket.go +++ b/internal/storage/bucket/default_bucket.go @@ -18,7 +18,7 @@ import ( ) // stateless version (+1 regarding directory name, as migrations start from 1 in the lib) -const MinimalSchemaVersion = 45 +const MinimalSchemaVersion = 46 type DefaultBucket struct { name string diff --git a/internal/storage/bucket/migrations/44-add-schema/up.sql b/internal/storage/bucket/migrations/44-add-schema/up.sql index 98dbb64392..e20743c63e 100644 --- a/internal/storage/bucket/migrations/44-add-schema/up.sql +++ b/internal/storage/bucket/migrations/44-add-schema/up.sql @@ -7,7 +7,6 @@ do $$ version text not null, created_at timestamp without time zone not null default now(), chart jsonb not null, - transactions jsonb not null, primary key (ledger, version) ); @@ -15,9 +14,6 @@ do $$ alter table logs add column schema_version text; - - alter table transactions - add column template text; end $$; diff --git a/internal/storage/bucket/migrations/45-add-transaction-templates/notes.yaml b/internal/storage/bucket/migrations/45-add-transaction-templates/notes.yaml new file mode 100644 index 0000000000..b36c69d930 --- /dev/null +++ b/internal/storage/bucket/migrations/45-add-transaction-templates/notes.yaml @@ -0,0 +1 @@ +name: Add template column to transactions diff --git a/internal/storage/bucket/migrations/45-add-transaction-templates/up.sql b/internal/storage/bucket/migrations/45-add-transaction-templates/up.sql new file mode 100644 index 0000000000..3c927c3acd --- /dev/null +++ b/internal/storage/bucket/migrations/45-add-transaction-templates/up.sql @@ -0,0 +1,11 @@ +do $$ + begin + set search_path = '{{ .Schema }}'; + + alter table schemas + add column transactions jsonb not null default '{}'::jsonb; + + alter table transactions + add column template text; + end +$$; From cf359a79d2547235a7a8ba5d61aa4a2ac314bb06 Mon Sep 17 00:00:00 2001 From: Azorlogh Date: Fri, 19 Dec 2025 16:51:54 +0100 Subject: [PATCH 14/14] expost schemaEnforcementMode on /_info --- .../common/mocks_system_controller_test.go | 14 +++++++ internal/api/v1/controllers_config.go | 5 ++- internal/api/v1/controllers_config_test.go | 7 ++++ .../api/v1/mocks_system_controller_test.go | 38 +++++++++++++++++++ .../api/v2/mocks_system_controller_test.go | 38 +++++++++++++++++++ internal/controller/system/controller.go | 17 ++++++--- 6 files changed, 113 insertions(+), 6 deletions(-) diff --git a/internal/api/common/mocks_system_controller_test.go b/internal/api/common/mocks_system_controller_test.go index f0d6217735..87e6ec9b81 100644 --- a/internal/api/common/mocks_system_controller_test.go +++ b/internal/api/common/mocks_system_controller_test.go @@ -401,6 +401,20 @@ func (mr *SystemControllerMockRecorder) GetPipeline(ctx, id any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPipeline", reflect.TypeOf((*SystemController)(nil).GetPipeline), ctx, id) } +// GetSchemaEnforcementMode mocks base method. +func (m *SystemController) GetSchemaEnforcementMode(ctx context.Context) ledger0.SchemaEnforcementMode { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSchemaEnforcementMode", ctx) + ret0, _ := ret[0].(ledger0.SchemaEnforcementMode) + return ret0 +} + +// GetSchemaEnforcementMode indicates an expected call of GetSchemaEnforcementMode. +func (mr *SystemControllerMockRecorder) GetSchemaEnforcementMode(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSchemaEnforcementMode", reflect.TypeOf((*SystemController)(nil).GetSchemaEnforcementMode), ctx) +} + // ListExporters mocks base method. func (m *SystemController) ListExporters(ctx context.Context) (*bunpaginate.Cursor[ledger.Exporter], error) { m.ctrl.T.Helper() diff --git a/internal/api/v1/controllers_config.go b/internal/api/v1/controllers_config.go index 8fe95e6ef8..666df720c9 100644 --- a/internal/api/v1/controllers_config.go +++ b/internal/api/v1/controllers_config.go @@ -10,6 +10,7 @@ import ( ledger "github.com/formancehq/ledger/internal" "github.com/formancehq/ledger/internal/api/common" + ledgercontroller "github.com/formancehq/ledger/internal/controller/ledger" "github.com/formancehq/ledger/internal/controller/system" storagecommon "github.com/formancehq/ledger/internal/storage/common" systemstore "github.com/formancehq/ledger/internal/storage/system" @@ -22,7 +23,8 @@ type ConfigInfo struct { } type LedgerConfig struct { - LedgerStorage *LedgerStorage `json:"storage"` + LedgerStorage *LedgerStorage `json:"storage"` + SchemaEnforcementMode ledgercontroller.SchemaEnforcementMode `json:"schemaEnforcementMode"` } type LedgerStorage struct { @@ -53,6 +55,7 @@ func GetInfo(systemController system.Controller, version string) func(w http.Res Server: "ledger", Version: version, Config: &LedgerConfig{ + SchemaEnforcementMode: systemController.GetSchemaEnforcementMode(r.Context()), LedgerStorage: &LedgerStorage{ Driver: "postgres", Ledgers: ledgerNames, diff --git a/internal/api/v1/controllers_config_test.go b/internal/api/v1/controllers_config_test.go index a09e72ed6f..301b19fbc5 100644 --- a/internal/api/v1/controllers_config_test.go +++ b/internal/api/v1/controllers_config_test.go @@ -14,6 +14,7 @@ import ( "github.com/formancehq/go-libs/v3/bun/bunpaginate" ledger "github.com/formancehq/ledger/internal" + ledgercontroller "github.com/formancehq/ledger/internal/controller/ledger" ) func TestGetInfo(t *testing.T) { @@ -36,6 +37,11 @@ func TestGetInfo(t *testing.T) { }, }, nil) + systemController. + EXPECT(). + GetSchemaEnforcementMode(gomock.Any()). + Return(ledgercontroller.SchemaEnforcementAudit) + req := httptest.NewRequest(http.MethodGet, "/_info", nil) rec := httptest.NewRecorder() @@ -49,6 +55,7 @@ func TestGetInfo(t *testing.T) { Server: "ledger", Version: "develop", Config: &LedgerConfig{ + SchemaEnforcementMode: ledgercontroller.SchemaEnforcementAudit, LedgerStorage: &LedgerStorage{ Driver: "postgres", Ledgers: []string{"a", "b"}, diff --git a/internal/api/v1/mocks_system_controller_test.go b/internal/api/v1/mocks_system_controller_test.go index e41d9fe829..64df6f37be 100644 --- a/internal/api/v1/mocks_system_controller_test.go +++ b/internal/api/v1/mocks_system_controller_test.go @@ -953,6 +953,44 @@ func (c *SystemControllerGetPipelineCall) DoAndReturn(f func(context.Context, st return c } +// GetSchemaEnforcementMode mocks base method. +func (m *SystemController) GetSchemaEnforcementMode(ctx context.Context) ledger0.SchemaEnforcementMode { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSchemaEnforcementMode", ctx) + ret0, _ := ret[0].(ledger0.SchemaEnforcementMode) + return ret0 +} + +// GetSchemaEnforcementMode indicates an expected call of GetSchemaEnforcementMode. +func (mr *SystemControllerMockRecorder) GetSchemaEnforcementMode(ctx any) *SystemControllerGetSchemaEnforcementModeCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSchemaEnforcementMode", reflect.TypeOf((*SystemController)(nil).GetSchemaEnforcementMode), ctx) + return &SystemControllerGetSchemaEnforcementModeCall{Call: call} +} + +// SystemControllerGetSchemaEnforcementModeCall wrap *gomock.Call +type SystemControllerGetSchemaEnforcementModeCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *SystemControllerGetSchemaEnforcementModeCall) Return(arg0 ledger0.SchemaEnforcementMode) *SystemControllerGetSchemaEnforcementModeCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *SystemControllerGetSchemaEnforcementModeCall) Do(f func(context.Context) ledger0.SchemaEnforcementMode) *SystemControllerGetSchemaEnforcementModeCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *SystemControllerGetSchemaEnforcementModeCall) DoAndReturn(f func(context.Context) ledger0.SchemaEnforcementMode) *SystemControllerGetSchemaEnforcementModeCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // ListExporters mocks base method. func (m *SystemController) ListExporters(ctx context.Context) (*bunpaginate.Cursor[ledger.Exporter], error) { m.ctrl.T.Helper() diff --git a/internal/api/v2/mocks_system_controller_test.go b/internal/api/v2/mocks_system_controller_test.go index 816207d13b..6777fbfc96 100644 --- a/internal/api/v2/mocks_system_controller_test.go +++ b/internal/api/v2/mocks_system_controller_test.go @@ -953,6 +953,44 @@ func (c *SystemControllerGetPipelineCall) DoAndReturn(f func(context.Context, st return c } +// GetSchemaEnforcementMode mocks base method. +func (m *SystemController) GetSchemaEnforcementMode(ctx context.Context) ledger0.SchemaEnforcementMode { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSchemaEnforcementMode", ctx) + ret0, _ := ret[0].(ledger0.SchemaEnforcementMode) + return ret0 +} + +// GetSchemaEnforcementMode indicates an expected call of GetSchemaEnforcementMode. +func (mr *SystemControllerMockRecorder) GetSchemaEnforcementMode(ctx any) *SystemControllerGetSchemaEnforcementModeCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSchemaEnforcementMode", reflect.TypeOf((*SystemController)(nil).GetSchemaEnforcementMode), ctx) + return &SystemControllerGetSchemaEnforcementModeCall{Call: call} +} + +// SystemControllerGetSchemaEnforcementModeCall wrap *gomock.Call +type SystemControllerGetSchemaEnforcementModeCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *SystemControllerGetSchemaEnforcementModeCall) Return(arg0 ledger0.SchemaEnforcementMode) *SystemControllerGetSchemaEnforcementModeCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *SystemControllerGetSchemaEnforcementModeCall) Do(f func(context.Context) ledger0.SchemaEnforcementMode) *SystemControllerGetSchemaEnforcementModeCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *SystemControllerGetSchemaEnforcementModeCall) DoAndReturn(f func(context.Context) ledger0.SchemaEnforcementMode) *SystemControllerGetSchemaEnforcementModeCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + // ListExporters mocks base method. func (m *SystemController) ListExporters(ctx context.Context) (*bunpaginate.Cursor[ledger.Exporter], error) { m.ctrl.T.Helper() diff --git a/internal/controller/system/controller.go b/internal/controller/system/controller.go index 7fd1761787..09b5cf5bea 100644 --- a/internal/controller/system/controller.go +++ b/internal/controller/system/controller.go @@ -52,6 +52,8 @@ type Controller interface { DeleteLedgerMetadata(ctx context.Context, param string, key string) error DeleteBucket(ctx context.Context, bucket string) error RestoreBucket(ctx context.Context, bucket string) error + + GetSchemaEnforcementMode(ctx context.Context) ledgercontroller.SchemaEnforcementMode } type DefaultController struct { @@ -246,6 +248,10 @@ func (ctrl *DefaultController) RestoreBucket(ctx context.Context, bucket string) }))) } +func (ctrl *DefaultController) GetSchemaEnforcementMode(ctx context.Context) ledgercontroller.SchemaEnforcementMode { + return ctrl.schemaEnforcementMode +} + // NewDefaultController creates a DefaultController configured with the provided // store, listener, replication backend, and optional functional options. // @@ -259,11 +265,12 @@ func NewDefaultController( opts ...Option, ) *DefaultController { ret := &DefaultController{ - driver: store, - listener: listener, - registry: ledgercontroller.NewStateRegistry(), - defaultParser: ledgercontroller.NewDefaultNumscriptParser(), - replicationBackend: replicationBackend, + driver: store, + listener: listener, + registry: ledgercontroller.NewStateRegistry(), + defaultParser: ledgercontroller.NewDefaultNumscriptParser(), + replicationBackend: replicationBackend, + schemaEnforcementMode: ledgercontroller.SchemaEnforcementAudit, } for _, opt := range append(defaultOptions, opts...) { opt(ret)