From 2d6fa09357485fea8f3710e529c214211529ad73 Mon Sep 17 00:00:00 2001 From: Kaijun Zhang Date: Wed, 1 Apr 2026 13:40:13 -0400 Subject: [PATCH 1/3] -cherrypick: custom-topics Signed-off-by: Kaijun Zhang --- doc-site/docs/swagger/swagger.yaml | 70 ++++++++++ .../route_post_contract_api_publish.go | 3 +- .../route_post_contract_api_publish_test.go | 21 ++- .../route_post_contract_interface_publish.go | 3 +- ...te_post_contract_interface_publish_test.go | 21 ++- .../apiserver/route_post_new_contract_api.go | 3 +- .../route_post_new_contract_api_test.go | 23 +++- .../route_post_new_contract_interface.go | 3 +- .../route_post_new_contract_interface_test.go | 23 +++- internal/apiserver/route_put_contract_api.go | 3 +- .../apiserver/route_put_contract_api_test.go | 23 +++- internal/coremsgs/en_api_translations.go | 2 + internal/definitions/sender.go | 31 +++-- internal/definitions/sender_contracts.go | 24 ++-- internal/definitions/sender_contracts_test.go | 126 +++++++++++++----- internal/definitions/sender_datatype.go | 2 +- internal/definitions/sender_identity.go | 6 +- internal/definitions/sender_test.go | 6 +- internal/definitions/sender_tokenpool.go | 2 +- mocks/definitionsmocks/sender.go | 56 ++++---- 20 files changed, 348 insertions(+), 103 deletions(-) diff --git a/doc-site/docs/swagger/swagger.yaml b/doc-site/docs/swagger/swagger.yaml index d4e4e5ee79..1d7ca1811a 100644 --- a/doc-site/docs/swagger/swagger.yaml +++ b/doc-site/docs/swagger/swagger.yaml @@ -171,6 +171,13 @@ paths: name: publish schema: type: string + - description: Custom topics for ordering definition broadcast messages + in: query + name: topics + schema: + items: + type: string + type: array - description: Server-side request timeout (milliseconds, or set a custom suffix like 10s) in: header @@ -1800,6 +1807,13 @@ paths: name: confirm schema: type: string + - description: Custom topics for ordering definition broadcast messages + in: query + name: topics + schema: + items: + type: string + type: array - description: Server-side request timeout (milliseconds, or set a custom suffix like 10s) in: header @@ -2411,6 +2425,13 @@ paths: schema: example: "true" type: string + - description: Custom topics for ordering definition broadcast messages + in: query + name: topics + schema: + items: + type: string + type: array - description: Server-side request timeout (milliseconds, or set a custom suffix like 10s) in: header @@ -3828,6 +3849,13 @@ paths: name: publish schema: type: string + - description: Custom topics for ordering definition broadcast messages + in: query + name: topics + schema: + items: + type: string + type: array - description: Server-side request timeout (milliseconds, or set a custom suffix like 10s) in: header @@ -4856,6 +4884,13 @@ paths: name: confirm schema: type: string + - description: Custom topics for ordering definition broadcast messages + in: query + name: topics + schema: + items: + type: string + type: array - description: Server-side request timeout (milliseconds, or set a custom suffix like 10s) in: header @@ -13721,6 +13756,13 @@ paths: name: publish schema: type: string + - description: Custom topics for ordering definition broadcast messages + in: query + name: topics + schema: + items: + type: string + type: array - description: Server-side request timeout (milliseconds, or set a custom suffix like 10s) in: header @@ -15610,6 +15652,13 @@ paths: name: confirm schema: type: string + - description: Custom topics for ordering definition broadcast messages + in: query + name: topics + schema: + items: + type: string + type: array - description: Server-side request timeout (milliseconds, or set a custom suffix like 10s) in: header @@ -16501,6 +16550,13 @@ paths: schema: example: "true" type: string + - description: Custom topics for ordering definition broadcast messages + in: query + name: topics + schema: + items: + type: string + type: array - description: Server-side request timeout (milliseconds, or set a custom suffix like 10s) in: header @@ -17981,6 +18037,13 @@ paths: name: publish schema: type: string + - description: Custom topics for ordering definition broadcast messages + in: query + name: topics + schema: + items: + type: string + type: array - description: Server-side request timeout (milliseconds, or set a custom suffix like 10s) in: header @@ -19037,6 +19100,13 @@ paths: name: confirm schema: type: string + - description: Custom topics for ordering definition broadcast messages + in: query + name: topics + schema: + items: + type: string + type: array - description: Server-side request timeout (milliseconds, or set a custom suffix like 10s) in: header diff --git a/internal/apiserver/route_post_contract_api_publish.go b/internal/apiserver/route_post_contract_api_publish.go index f9c4602a8b..21de711157 100644 --- a/internal/apiserver/route_post_contract_api_publish.go +++ b/internal/apiserver/route_post_contract_api_publish.go @@ -35,6 +35,7 @@ var postContractAPIPublish = &ffapi.Route{ }, QueryParams: []*ffapi.QueryParam{ {Name: "confirm", Description: coremsgs.APIConfirmMsgQueryParam, IsBool: true}, + {Name: "topics", Description: coremsgs.APICustomTopicsQueryParam, IsArray: true}, }, Description: coremsgs.APIEndpointsPostContractAPIPublish, JSONInputValue: func() interface{} { return &core.DefinitionPublish{} }, @@ -45,7 +46,7 @@ var postContractAPIPublish = &ffapi.Route{ waitConfirm := strings.EqualFold(r.QP["confirm"], "true") r.SuccessStatus = syncRetcode(waitConfirm) input := r.Input.(*core.DefinitionPublish) - return cr.or.DefinitionSender().PublishContractAPI(cr.ctx, cr.apiBaseURL, r.PP["apiName"], input.NetworkName, waitConfirm) + return cr.or.DefinitionSender().PublishContractAPI(cr.ctx, cr.apiBaseURL, r.PP["apiName"], input.NetworkName, waitConfirm, r.QAP["topics"]) }, }, } diff --git a/internal/apiserver/route_post_contract_api_publish_test.go b/internal/apiserver/route_post_contract_api_publish_test.go index d40529ac94..3e460e1adf 100644 --- a/internal/apiserver/route_post_contract_api_publish_test.go +++ b/internal/apiserver/route_post_contract_api_publish_test.go @@ -41,7 +41,26 @@ func TestPostContractAPIPublish(t *testing.T) { res := httptest.NewRecorder() api := &core.ContractAPI{} - mds.On("PublishContractAPI", mock.Anything, "http://127.0.0.1:5000/api/v1/namespaces/ns1", "banana", "banana-net", false).Return(api, nil) + mds.On("PublishContractAPI", mock.Anything, "http://127.0.0.1:5000/api/v1/namespaces/ns1", "banana", "banana-net", false, mock.Anything).Return(api, nil) + r.ServeHTTP(res, req) + + assert.Equal(t, 202, res.Result().StatusCode) +} + +func TestPostContractAPIPublishWithTopics(t *testing.T) { + o, r := newTestAPIServer() + o.On("Authorize", mock.Anything, mock.Anything).Return(nil) + mds := &definitionsmocks.Sender{} + o.On("DefinitionSender").Return(mds) + input := core.DefinitionPublish{NetworkName: "banana-net"} + var buf bytes.Buffer + json.NewEncoder(&buf).Encode(&input) + req := httptest.NewRequest("POST", "/api/v1/namespaces/ns1/apis/banana/publish?topics=my-topic", &buf) + req.Header.Set("Content-Type", "application/json; charset=utf-8") + res := httptest.NewRecorder() + api := &core.ContractAPI{} + + mds.On("PublishContractAPI", mock.Anything, "http://127.0.0.1:5000/api/v1/namespaces/ns1", "banana", "banana-net", false, []string{"my-topic"}).Return(api, nil) r.ServeHTTP(res, req) assert.Equal(t, 202, res.Result().StatusCode) diff --git a/internal/apiserver/route_post_contract_interface_publish.go b/internal/apiserver/route_post_contract_interface_publish.go index 56790402a4..9c3ecb5096 100644 --- a/internal/apiserver/route_post_contract_interface_publish.go +++ b/internal/apiserver/route_post_contract_interface_publish.go @@ -36,6 +36,7 @@ var postContractInterfacePublish = &ffapi.Route{ }, QueryParams: []*ffapi.QueryParam{ {Name: "confirm", Description: coremsgs.APIConfirmMsgQueryParam, IsBool: true}, + {Name: "topics", Description: coremsgs.APICustomTopicsQueryParam, IsArray: true}, }, Description: coremsgs.APIEndpointsPostContractInterfacePublish, JSONInputValue: func() interface{} { return &core.DefinitionPublish{} }, @@ -46,7 +47,7 @@ var postContractInterfacePublish = &ffapi.Route{ waitConfirm := strings.EqualFold(r.QP["confirm"], "true") r.SuccessStatus = syncRetcode(waitConfirm) input := r.Input.(*core.DefinitionPublish) - return cr.or.DefinitionSender().PublishFFI(cr.ctx, r.PP["name"], r.PP["version"], input.NetworkName, waitConfirm) + return cr.or.DefinitionSender().PublishFFI(cr.ctx, r.PP["name"], r.PP["version"], input.NetworkName, waitConfirm, r.QAP["topics"]) }, }, } diff --git a/internal/apiserver/route_post_contract_interface_publish_test.go b/internal/apiserver/route_post_contract_interface_publish_test.go index 16f9279c83..541e9c23ac 100644 --- a/internal/apiserver/route_post_contract_interface_publish_test.go +++ b/internal/apiserver/route_post_contract_interface_publish_test.go @@ -42,7 +42,26 @@ func TestPostContractInterfacePublish(t *testing.T) { res := httptest.NewRecorder() ffi := &fftypes.FFI{} - mds.On("PublishFFI", mock.Anything, "ffi1", "1.0", "", false).Return(ffi, nil) + mds.On("PublishFFI", mock.Anything, "ffi1", "1.0", "", false, mock.Anything).Return(ffi, nil) + r.ServeHTTP(res, req) + + assert.Equal(t, 202, res.Result().StatusCode) +} + +func TestPostContractInterfacePublishWithTopics(t *testing.T) { + o, r := newTestAPIServer() + o.On("Authorize", mock.Anything, mock.Anything).Return(nil) + mds := &definitionsmocks.Sender{} + o.On("DefinitionSender").Return(mds) + input := core.TokenPool{} + var buf bytes.Buffer + json.NewEncoder(&buf).Encode(&input) + req := httptest.NewRequest("POST", "/api/v1/namespaces/ns1/contracts/interfaces/ffi1/1.0/publish?topics=my-topic", &buf) + req.Header.Set("Content-Type", "application/json; charset=utf-8") + res := httptest.NewRecorder() + ffi := &fftypes.FFI{} + + mds.On("PublishFFI", mock.Anything, "ffi1", "1.0", "", false, []string{"my-topic"}).Return(ffi, nil) r.ServeHTTP(res, req) assert.Equal(t, 202, res.Result().StatusCode) diff --git a/internal/apiserver/route_post_new_contract_api.go b/internal/apiserver/route_post_new_contract_api.go index e9bfe34879..422a34a2d8 100644 --- a/internal/apiserver/route_post_new_contract_api.go +++ b/internal/apiserver/route_post_new_contract_api.go @@ -34,6 +34,7 @@ var postNewContractAPI = &ffapi.Route{ QueryParams: []*ffapi.QueryParam{ {Name: "confirm", Description: coremsgs.APIConfirmMsgQueryParam, IsBool: true, Example: "true"}, {Name: "publish", Description: coremsgs.APIPublishQueryParam, IsBool: true}, + {Name: "topics", Description: coremsgs.APICustomTopicsQueryParam, IsArray: true}, }, Description: coremsgs.APIEndpointsPostNewContractAPI, JSONInputValue: func() interface{} { return &core.ContractAPI{} }, @@ -49,7 +50,7 @@ var postNewContractAPI = &ffapi.Route{ api := r.Input.(*core.ContractAPI) api.ID = nil api.Published = strings.EqualFold(r.QP["publish"], "true") - err = cr.or.DefinitionSender().DefineContractAPI(cr.ctx, cr.apiBaseURL, api, waitConfirm) + err = cr.or.DefinitionSender().DefineContractAPI(cr.ctx, cr.apiBaseURL, api, waitConfirm, r.QAP["topics"]) return api, err }, }, diff --git a/internal/apiserver/route_post_new_contract_api_test.go b/internal/apiserver/route_post_new_contract_api_test.go index 97c668e6cd..05d138ac42 100644 --- a/internal/apiserver/route_post_new_contract_api_test.go +++ b/internal/apiserver/route_post_new_contract_api_test.go @@ -42,7 +42,7 @@ func TestPostNewContractAPI(t *testing.T) { req.Header.Set("Content-Type", "application/json; charset=utf-8") res := httptest.NewRecorder() - mds.On("DefineContractAPI", mock.Anything, mock.Anything, mock.AnythingOfType("*core.ContractAPI"), false).Return(nil) + mds.On("DefineContractAPI", mock.Anything, mock.Anything, mock.AnythingOfType("*core.ContractAPI"), false, mock.Anything).Return(nil) r.ServeHTTP(res, req) assert.Equal(t, 202, res.Result().StatusCode) @@ -61,8 +61,27 @@ func TestPostNewContractAPISync(t *testing.T) { req.Header.Set("Content-Type", "application/json; charset=utf-8") res := httptest.NewRecorder() - mds.On("DefineContractAPI", mock.Anything, mock.Anything, mock.AnythingOfType("*core.ContractAPI"), true).Return(nil) + mds.On("DefineContractAPI", mock.Anything, mock.Anything, mock.AnythingOfType("*core.ContractAPI"), true, mock.Anything).Return(nil) r.ServeHTTP(res, req) assert.Equal(t, 200, res.Result().StatusCode) } + +func TestPostNewContractAPIWithTopics(t *testing.T) { + o, r := newTestAPIServer() + o.On("Authorize", mock.Anything, mock.Anything).Return(nil) + mds := &definitionsmocks.Sender{} + o.On("Contracts").Return(&contractmocks.Manager{}) + o.On("DefinitionSender").Return(mds) + input := core.Datatype{} + var buf bytes.Buffer + json.NewEncoder(&buf).Encode(&input) + req := httptest.NewRequest("POST", "/api/v1/namespaces/ns1/apis?topics=my-topic", &buf) + req.Header.Set("Content-Type", "application/json; charset=utf-8") + res := httptest.NewRecorder() + + mds.On("DefineContractAPI", mock.Anything, mock.Anything, mock.AnythingOfType("*core.ContractAPI"), false, []string{"my-topic"}).Return(nil) + r.ServeHTTP(res, req) + + assert.Equal(t, 202, res.Result().StatusCode) +} diff --git a/internal/apiserver/route_post_new_contract_interface.go b/internal/apiserver/route_post_new_contract_interface.go index abfd08acea..848177962a 100644 --- a/internal/apiserver/route_post_new_contract_interface.go +++ b/internal/apiserver/route_post_new_contract_interface.go @@ -34,6 +34,7 @@ var postNewContractInterface = &ffapi.Route{ QueryParams: []*ffapi.QueryParam{ {Name: "confirm", Description: coremsgs.APIConfirmMsgQueryParam, IsBool: true, Example: "true"}, {Name: "publish", Description: coremsgs.APIPublishQueryParam, IsBool: true}, + {Name: "topics", Description: coremsgs.APICustomTopicsQueryParam, IsArray: true}, }, Description: coremsgs.APIEndpointsPostNewContractInterface, JSONInputValue: func() interface{} { return &fftypes.FFI{} }, @@ -48,7 +49,7 @@ var postNewContractInterface = &ffapi.Route{ r.SuccessStatus = syncRetcode(waitConfirm) ffi := r.Input.(*fftypes.FFI) ffi.Published = strings.EqualFold(r.QP["publish"], "true") - err = cr.or.DefinitionSender().DefineFFI(cr.ctx, ffi, waitConfirm) + err = cr.or.DefinitionSender().DefineFFI(cr.ctx, ffi, waitConfirm, r.QAP["topics"]) return ffi, err }, }, diff --git a/internal/apiserver/route_post_new_contract_interface_test.go b/internal/apiserver/route_post_new_contract_interface_test.go index 34b4794600..777880c094 100644 --- a/internal/apiserver/route_post_new_contract_interface_test.go +++ b/internal/apiserver/route_post_new_contract_interface_test.go @@ -42,7 +42,7 @@ func TestPostNewContractInterface(t *testing.T) { req.Header.Set("Content-Type", "application/json; charset=utf-8") res := httptest.NewRecorder() - mds.On("DefineFFI", mock.Anything, mock.AnythingOfType("*fftypes.FFI"), false).Return(nil) + mds.On("DefineFFI", mock.Anything, mock.AnythingOfType("*fftypes.FFI"), false, mock.Anything).Return(nil) r.ServeHTTP(res, req) assert.Equal(t, 202, res.Result().StatusCode) @@ -61,8 +61,27 @@ func TestPostNewContractInterfaceSync(t *testing.T) { req.Header.Set("Content-Type", "application/json; charset=utf-8") res := httptest.NewRecorder() - mds.On("DefineFFI", mock.Anything, mock.AnythingOfType("*fftypes.FFI"), true).Return(nil) + mds.On("DefineFFI", mock.Anything, mock.AnythingOfType("*fftypes.FFI"), true, mock.Anything).Return(nil) r.ServeHTTP(res, req) assert.Equal(t, 200, res.Result().StatusCode) } + +func TestPostNewContractInterfaceWithTopics(t *testing.T) { + o, r := newTestAPIServer() + o.On("Authorize", mock.Anything, mock.Anything).Return(nil) + mds := &definitionsmocks.Sender{} + o.On("Contracts").Return(&contractmocks.Manager{}) + o.On("DefinitionSender").Return(mds) + input := core.Datatype{} + var buf bytes.Buffer + json.NewEncoder(&buf).Encode(&input) + req := httptest.NewRequest("POST", "/api/v1/namespaces/ns1/contracts/interfaces?topics=my-topic", &buf) + req.Header.Set("Content-Type", "application/json; charset=utf-8") + res := httptest.NewRecorder() + + mds.On("DefineFFI", mock.Anything, mock.AnythingOfType("*fftypes.FFI"), false, []string{"my-topic"}).Return(nil) + r.ServeHTTP(res, req) + + assert.Equal(t, 202, res.Result().StatusCode) +} diff --git a/internal/apiserver/route_put_contract_api.go b/internal/apiserver/route_put_contract_api.go index 7cbd0b5f72..0e8f90c3ec 100644 --- a/internal/apiserver/route_put_contract_api.go +++ b/internal/apiserver/route_put_contract_api.go @@ -36,6 +36,7 @@ var putContractAPI = &ffapi.Route{ }, QueryParams: []*ffapi.QueryParam{ {Name: "confirm", Description: coremsgs.APIConfirmMsgQueryParam, IsBool: true, Example: "true"}, + {Name: "topics", Description: coremsgs.APICustomTopicsQueryParam, IsArray: true}, }, Description: coremsgs.APIParamsContractAPIID, JSONInputValue: func() interface{} { return &core.ContractAPI{} }, @@ -51,7 +52,7 @@ var putContractAPI = &ffapi.Route{ api := r.Input.(*core.ContractAPI) api.ID, err = fftypes.ParseUUID(cr.ctx, r.PP["id"]) if err == nil { - err = cr.or.DefinitionSender().DefineContractAPI(cr.ctx, cr.apiBaseURL, api, waitConfirm) + err = cr.or.DefinitionSender().DefineContractAPI(cr.ctx, cr.apiBaseURL, api, waitConfirm, r.QAP["topics"]) } return api, err }, diff --git a/internal/apiserver/route_put_contract_api_test.go b/internal/apiserver/route_put_contract_api_test.go index eb6369c03c..3e4801d6b2 100644 --- a/internal/apiserver/route_put_contract_api_test.go +++ b/internal/apiserver/route_put_contract_api_test.go @@ -42,7 +42,7 @@ func TestPutContractAPI(t *testing.T) { req.Header.Set("Content-Type", "application/json; charset=utf-8") res := httptest.NewRecorder() - mds.On("DefineContractAPI", mock.Anything, mock.Anything, mock.AnythingOfType("*core.ContractAPI"), false).Return(nil) + mds.On("DefineContractAPI", mock.Anything, mock.Anything, mock.AnythingOfType("*core.ContractAPI"), false, mock.Anything).Return(nil) r.ServeHTTP(res, req) assert.Equal(t, 202, res.Result().StatusCode) @@ -61,8 +61,27 @@ func TestPutContractAPISync(t *testing.T) { req.Header.Set("Content-Type", "application/json; charset=utf-8") res := httptest.NewRecorder() - mds.On("DefineContractAPI", mock.Anything, mock.Anything, mock.AnythingOfType("*core.ContractAPI"), true).Return(nil) + mds.On("DefineContractAPI", mock.Anything, mock.Anything, mock.AnythingOfType("*core.ContractAPI"), true, mock.Anything).Return(nil) r.ServeHTTP(res, req) assert.Equal(t, 200, res.Result().StatusCode) } + +func TestPutContractAPIWithTopics(t *testing.T) { + o, r := newTestAPIServer() + o.On("Authorize", mock.Anything, mock.Anything).Return(nil) + mds := &definitionsmocks.Sender{} + o.On("DefinitionSender").Return(mds) + o.On("Contracts").Return(&contractmocks.Manager{}) + input := core.Datatype{} + var buf bytes.Buffer + json.NewEncoder(&buf).Encode(&input) + req := httptest.NewRequest("PUT", "/api/v1/namespaces/ns1/apis/99EEE458-037C-4C78-B66B-31E52F93D2E9?topics=my-topic", &buf) + req.Header.Set("Content-Type", "application/json; charset=utf-8") + res := httptest.NewRecorder() + + mds.On("DefineContractAPI", mock.Anything, mock.Anything, mock.AnythingOfType("*core.ContractAPI"), false, []string{"my-topic"}).Return(nil) + r.ServeHTTP(res, req) + + assert.Equal(t, 202, res.Result().StatusCode) +} diff --git a/internal/coremsgs/en_api_translations.go b/internal/coremsgs/en_api_translations.go index 02f1a96d5c..4d3c5d84a7 100644 --- a/internal/coremsgs/en_api_translations.go +++ b/internal/coremsgs/en_api_translations.go @@ -216,4 +216,6 @@ var ( APISubscriptionStartSequenceID = ffm("api.startsequenceid", "The sequence ID in the raw event stream to start indexing through events from. Leave blank to start indexing from the most recent events") APISubscriptionEndSequenceID = ffm("api.endsequenceid", "The sequence ID in the raw event stream to stop indexing through events at. Leave blank to start indexing from the most recent events") + + APICustomTopicsQueryParam = ffm("api.customTopicsQueryParam", "Custom topics for ordering definition broadcast messages") ) diff --git a/internal/definitions/sender.go b/internal/definitions/sender.go index 70f3664095..5e3f401fb1 100644 --- a/internal/definitions/sender.go +++ b/internal/definitions/sender.go @@ -43,10 +43,10 @@ type Sender interface { DefineDatatype(ctx context.Context, datatype *core.Datatype, waitConfirm bool) error DefineTokenPool(ctx context.Context, pool *core.TokenPool, waitConfirm bool) error PublishTokenPool(ctx context.Context, poolNameOrID, networkName string, waitConfirm bool) (*core.TokenPool, error) - DefineFFI(ctx context.Context, ffi *fftypes.FFI, waitConfirm bool) error - PublishFFI(ctx context.Context, name, version, networkName string, waitConfirm bool) (*fftypes.FFI, error) - DefineContractAPI(ctx context.Context, httpServerURL string, api *core.ContractAPI, waitConfirm bool) error - PublishContractAPI(ctx context.Context, httpServerURL, name, networkName string, waitConfirm bool) (api *core.ContractAPI, err error) + DefineFFI(ctx context.Context, ffi *fftypes.FFI, waitConfirm bool, topics []string) error + PublishFFI(ctx context.Context, name, version, networkName string, waitConfirm bool, topics []string) (*fftypes.FFI, error) + DefineContractAPI(ctx context.Context, httpServerURL string, api *core.ContractAPI, waitConfirm bool, topics []string) error + PublishContractAPI(ctx context.Context, httpServerURL, name, networkName string, waitConfirm bool, topics []string) (api *core.ContractAPI, err error) } type definitionSender struct { @@ -135,35 +135,46 @@ func (w *sendWrapper) send(ctx context.Context, waitConfirm bool) (*core.Message } } -func (ds *definitionSender) getSenderDefault(ctx context.Context, def core.Definition, tag string) *sendWrapper { +func (ds *definitionSender) getSenderDefault(ctx context.Context, def core.Definition, tag string, customTopics []string) *sendWrapper { org, err := ds.identity.GetRootOrg(ctx) if err != nil { return wrapSendError(err) } return ds.getSender(ctx, def, &core.SignerRef{ /* resolve to node default */ Author: org.DID, - }, tag) + }, tag, customTopics) } -func (ds *definitionSender) getSender(ctx context.Context, def core.Definition, signingIdentity *core.SignerRef, tag string) *sendWrapper { +func (ds *definitionSender) getSender(ctx context.Context, def core.Definition, signingIdentity *core.SignerRef, tag string, customTopics []string) *sendWrapper { err := ds.identity.ResolveInputSigningIdentity(ctx, signingIdentity) if err != nil { return wrapSendError(err) } - return ds.getSenderResolved(ctx, def, signingIdentity, tag) + return ds.getSenderResolved(ctx, def, signingIdentity, tag, customTopics) } -func (ds *definitionSender) getSenderResolved(ctx context.Context, def core.Definition, signingIdentity *core.SignerRef, tag string) *sendWrapper { +func (ds *definitionSender) getSenderResolved(ctx context.Context, def core.Definition, signingIdentity *core.SignerRef, tag string, customTopics []string) *sendWrapper { b, err := json.Marshal(&def) if err != nil { return wrapSendError(i18n.WrapError(ctx, err, coremsgs.MsgSerializationFailed)) } + topics := fftypes.FFStringArray{def.Topic()} + + var filtered []string + for _, t := range customTopics { + if t != "" { + filtered = append(filtered, t) + } + } + if len(filtered) > 0 { + topics = fftypes.FFStringArray(filtered) + } dataValue := fftypes.JSONAnyPtrBytes(b) message := &core.MessageInOut{ Message: core.Message{ Header: core.MessageHeader{ Type: core.MessageTypeDefinition, - Topics: fftypes.FFStringArray{def.Topic()}, + Topics: topics, Tag: tag, TxType: core.TransactionTypeBatchPin, SignerRef: *signingIdentity, diff --git a/internal/definitions/sender_contracts.go b/internal/definitions/sender_contracts.go index 7264a9f926..6c83a9562a 100644 --- a/internal/definitions/sender_contracts.go +++ b/internal/definitions/sender_contracts.go @@ -26,7 +26,7 @@ import ( "github.com/hyperledger/firefly/pkg/core" ) -func (ds *definitionSender) DefineFFI(ctx context.Context, ffi *fftypes.FFI, waitConfirm bool) error { +func (ds *definitionSender) DefineFFI(ctx context.Context, ffi *fftypes.FFI, waitConfirm bool, topics []string) error { ffi.ID = fftypes.NewUUID() ffi.Namespace = ds.namespace for _, method := range ffi.Methods { @@ -43,7 +43,7 @@ func (ds *definitionSender) DefineFFI(ctx context.Context, ffi *fftypes.FFI, wai if !ds.multiparty { return i18n.NewError(ctx, coremsgs.MsgActionNotSupported) } - _, err := ds.getFFISender(ctx, ffi).send(ctx, waitConfirm) + _, err := ds.getFFISender(ctx, ffi, topics).send(ctx, waitConfirm) return err } @@ -60,7 +60,7 @@ func (ds *definitionSender) DefineFFI(ctx context.Context, ffi *fftypes.FFI, wai }) } -func (ds *definitionSender) getFFISender(ctx context.Context, ffi *fftypes.FFI) *sendWrapper { +func (ds *definitionSender) getFFISender(ctx context.Context, ffi *fftypes.FFI, topics []string) *sendWrapper { if err := ds.contracts.ResolveFFI(ctx, ffi); err != nil { return wrapSendError(err) } @@ -82,7 +82,7 @@ func (ds *definitionSender) getFFISender(ctx context.Context, ffi *fftypes.FFI) ffi.Namespace = "" ffi.Published = true - sender := ds.getSenderDefault(ctx, ffi, core.SystemTagDefineFFI) + sender := ds.getSenderDefault(ctx, ffi, core.SystemTagDefineFFI, topics) if sender.message != nil { ffi.Message = sender.message.Header.ID } @@ -92,7 +92,7 @@ func (ds *definitionSender) getFFISender(ctx context.Context, ffi *fftypes.FFI) return sender } -func (ds *definitionSender) PublishFFI(ctx context.Context, name, version, networkName string, waitConfirm bool) (ffi *fftypes.FFI, err error) { +func (ds *definitionSender) PublishFFI(ctx context.Context, name, version, networkName string, waitConfirm bool, topics []string) (ffi *fftypes.FFI, err error) { if !ds.multiparty { return nil, i18n.NewError(ctx, coremsgs.MsgActionNotSupported) } @@ -106,7 +106,7 @@ func (ds *definitionSender) PublishFFI(ctx context.Context, name, version, netwo return i18n.NewError(ctx, coremsgs.MsgAlreadyPublished) } ffi.NetworkName = networkName - sender = ds.getFFISender(ctx, ffi) + sender = ds.getFFISender(ctx, ffi, topics) if sender.err != nil { return sender.err } @@ -120,7 +120,7 @@ func (ds *definitionSender) PublishFFI(ctx context.Context, name, version, netwo return ffi, err } -func (ds *definitionSender) DefineContractAPI(ctx context.Context, httpServerURL string, api *core.ContractAPI, waitConfirm bool) error { +func (ds *definitionSender) DefineContractAPI(ctx context.Context, httpServerURL string, api *core.ContractAPI, waitConfirm bool, topics []string) error { if api.ID == nil { api.ID = fftypes.NewUUID() } @@ -130,7 +130,7 @@ func (ds *definitionSender) DefineContractAPI(ctx context.Context, httpServerURL if !ds.multiparty { return i18n.NewError(ctx, coremsgs.MsgActionNotSupported) } - _, err := ds.getContractAPISender(ctx, httpServerURL, api).send(ctx, waitConfirm) + _, err := ds.getContractAPISender(ctx, httpServerURL, api, topics).send(ctx, waitConfirm) return err } @@ -141,7 +141,7 @@ func (ds *definitionSender) DefineContractAPI(ctx context.Context, httpServerURL }) } -func (ds *definitionSender) getContractAPISender(ctx context.Context, httpServerURL string, api *core.ContractAPI) *sendWrapper { +func (ds *definitionSender) getContractAPISender(ctx context.Context, httpServerURL string, api *core.ContractAPI, topics []string) *sendWrapper { if err := ds.contracts.ResolveContractAPI(ctx, httpServerURL, api); err != nil { return wrapSendError(err) } @@ -174,7 +174,7 @@ func (ds *definitionSender) getContractAPISender(ctx context.Context, httpServer api.Namespace = "" api.Published = true - sender := ds.getSenderDefault(ctx, api, core.SystemTagDefineContractAPI) + sender := ds.getSenderDefault(ctx, api, core.SystemTagDefineContractAPI, topics) if sender.message != nil { api.Message = sender.message.Header.ID } @@ -184,7 +184,7 @@ func (ds *definitionSender) getContractAPISender(ctx context.Context, httpServer return sender } -func (ds *definitionSender) PublishContractAPI(ctx context.Context, httpServerURL, name, networkName string, waitConfirm bool) (api *core.ContractAPI, err error) { +func (ds *definitionSender) PublishContractAPI(ctx context.Context, httpServerURL, name, networkName string, waitConfirm bool, topics []string) (api *core.ContractAPI, err error) { if !ds.multiparty { return nil, i18n.NewError(ctx, coremsgs.MsgActionNotSupported) } @@ -198,7 +198,7 @@ func (ds *definitionSender) PublishContractAPI(ctx context.Context, httpServerUR return i18n.NewError(ctx, coremsgs.MsgAlreadyPublished) } api.NetworkName = networkName - sender = ds.getContractAPISender(ctx, httpServerURL, api) + sender = ds.getContractAPISender(ctx, httpServerURL, api, topics) if sender.err != nil { return sender.err } diff --git a/internal/definitions/sender_contracts_test.go b/internal/definitions/sender_contracts_test.go index b0717cc460..9f71de6b56 100644 --- a/internal/definitions/sender_contracts_test.go +++ b/internal/definitions/sender_contracts_test.go @@ -44,7 +44,7 @@ func TestDefineFFIResolveFail(t *testing.T) { ds.mcm.On("ResolveFFI", context.Background(), ffi).Return(fmt.Errorf("pop")) - err := ds.DefineFFI(context.Background(), ffi, false) + err := ds.DefineFFI(context.Background(), ffi, false, nil) assert.EqualError(t, err, "pop") } @@ -63,7 +63,7 @@ func TestDefineFFIFail(t *testing.T) { ds.mcm.On("ResolveFFI", context.Background(), ffi).Return(nil) ds.mim.On("GetRootOrg", context.Background()).Return(nil, fmt.Errorf("pop")) - err := ds.DefineFFI(context.Background(), ffi, false) + err := ds.DefineFFI(context.Background(), ffi, false, nil) assert.EqualError(t, err, "pop") } @@ -80,7 +80,7 @@ func TestDefineFFIFailInnerError(t *testing.T) { ds.mcm.On("ResolveFFI", context.Background(), ffi).Return(nil) ds.mdi.On("InsertOrGetFFI", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("error2: [%w]", fmt.Errorf("pop"))) - err := ds.DefineFFI(context.Background(), ffi, false) + err := ds.DefineFFI(context.Background(), ffi, false, nil) assert.Regexp(t, "pop", err) } @@ -98,7 +98,7 @@ func TestDefineFFIExists(t *testing.T) { ds.mdi.On("GetFFIByNetworkName", context.Background(), "ns1", "ffi1", "1.0").Return(&fftypes.FFI{}, nil) ds.mcm.On("ResolveFFI", context.Background(), ffi).Return(nil) - err := ds.DefineFFI(context.Background(), ffi, false) + err := ds.DefineFFI(context.Background(), ffi, false, nil) assert.Regexp(t, "FF10448", err) } @@ -116,7 +116,7 @@ func TestDefineFFIQueryFail(t *testing.T) { ds.mdi.On("GetFFIByNetworkName", context.Background(), "ns1", "ffi1", "1.0").Return(nil, fmt.Errorf("pop")) ds.mcm.On("ResolveFFI", context.Background(), ffi).Return(nil) - err := ds.DefineFFI(context.Background(), ffi, false) + err := ds.DefineFFI(context.Background(), ffi, false, nil) assert.EqualError(t, err, "pop") } @@ -144,7 +144,7 @@ func TestDefineFFIOk(t *testing.T) { ds.mbm.On("NewBroadcast", mock.Anything).Return(mms) mms.On("Send", context.Background()).Return(nil) - err := ds.DefineFFI(context.Background(), ffi, false) + err := ds.DefineFFI(context.Background(), ffi, false, nil) assert.NoError(t, err) mms.AssertExpectations(t) @@ -174,7 +174,7 @@ func TestDefineFFIConfirm(t *testing.T) { ds.mbm.On("NewBroadcast", mock.Anything).Return(mms) mms.On("SendAndWait", context.Background()).Return(nil) - err := ds.DefineFFI(context.Background(), ffi, true) + err := ds.DefineFFI(context.Background(), ffi, true, nil) assert.NoError(t, err) mms.AssertExpectations(t) @@ -191,7 +191,7 @@ func TestDefineFFIPublishNonMultiparty(t *testing.T) { Published: true, } - err := ds.DefineFFI(context.Background(), ffi, false) + err := ds.DefineFFI(context.Background(), ffi, false, nil) assert.Regexp(t, "FF10414", err) } @@ -208,7 +208,7 @@ func TestDefineFFINonMultiparty(t *testing.T) { ds.mdi.On("InsertOrGetFFI", context.Background(), ffi).Return(nil, nil) ds.mdi.On("InsertEvent", context.Background(), mock.Anything).Return(nil) - err := ds.DefineFFI(context.Background(), ffi, false) + err := ds.DefineFFI(context.Background(), ffi, false, nil) assert.NoError(t, err) } @@ -223,10 +223,41 @@ func TestDefineFFINonMultipartyFail(t *testing.T) { ds.mcm.On("ResolveFFI", context.Background(), ffi).Return(fmt.Errorf("pop")) - err := ds.DefineFFI(context.Background(), ffi, false) + err := ds.DefineFFI(context.Background(), ffi, false, nil) assert.Regexp(t, "FF10403", err) } +// test that we can call defineFFI with custom topics +func TestDefineFFICustomTopics(t *testing.T) { + ds := newTestDefinitionSender(t) + defer ds.cleanup(t) + ds.multiparty = true + + ffi := &fftypes.FFI{ + Name: "ffi1", + Version: "1.0", + Published: true, + } + + ds.mdi.On("GetFFIByNetworkName", context.Background(), "ns1", "ffi1", "1.0").Return(nil, nil) + ds.mcm.On("ResolveFFI", context.Background(), ffi).Return(nil) + ds.mim.On("GetRootOrg", context.Background()).Return(&core.Identity{ + IdentityBase: core.IdentityBase{ + DID: "firefly:org1", + }, + }, nil) + ds.mim.On("ResolveInputSigningIdentity", context.Background(), mock.Anything).Return(nil) + + mms := &syncasyncmocks.Sender{} + ds.mbm.On("NewBroadcast", mock.Anything).Return(mms) + mms.On("Send", context.Background()).Return(nil) + + err := ds.DefineFFI(context.Background(), ffi, false, []string{"my-custom-topic"}) + assert.NoError(t, err) + + mms.AssertExpectations(t) +} + func TestDefineContractAPIResolveFail(t *testing.T) { ds := newTestDefinitionSender(t) defer ds.cleanup(t) @@ -240,7 +271,7 @@ func TestDefineContractAPIResolveFail(t *testing.T) { ds.mcm.On("ResolveContractAPI", context.Background(), url, api).Return(fmt.Errorf("pop")) - err := ds.DefineContractAPI(context.Background(), url, api, false) + err := ds.DefineContractAPI(context.Background(), url, api, false, nil) assert.EqualError(t, err, "pop") } @@ -259,7 +290,7 @@ func TestDefineContractAPIFail(t *testing.T) { ds.mim.On("GetRootOrg", context.Background()).Return(nil, fmt.Errorf("pop")) ds.mdi.On("GetContractAPIByNetworkName", context.Background(), "ns1", "banana").Return(nil, nil) - err := ds.DefineContractAPI(context.Background(), url, api, false) + err := ds.DefineContractAPI(context.Background(), url, api, false, nil) assert.EqualError(t, err, "pop") } @@ -287,7 +318,7 @@ func TestDefineContractAPIOk(t *testing.T) { ds.mbm.On("NewBroadcast", mock.Anything).Return(mms) mms.On("Send", context.Background()).Return(nil) - err := ds.DefineContractAPI(context.Background(), url, api, false) + err := ds.DefineContractAPI(context.Background(), url, api, false, nil) assert.NoError(t, err) mms.AssertExpectations(t) @@ -304,7 +335,7 @@ func TestDefineContractAPINonMultiparty(t *testing.T) { ds.mdi.On("InsertOrGetContractAPI", mock.Anything, mock.Anything).Return(nil, nil) ds.mdi.On("InsertEvent", mock.Anything, mock.Anything).Return(nil) - err := ds.DefineContractAPI(context.Background(), url, api, false) + err := ds.DefineContractAPI(context.Background(), url, api, false, nil) assert.NoError(t, err) } @@ -319,7 +350,7 @@ func TestDefineContractAPIPublishNonMultiparty(t *testing.T) { Published: true, } - err := ds.DefineContractAPI(context.Background(), url, api, false) + err := ds.DefineContractAPI(context.Background(), url, api, false, nil) assert.Regexp(t, "FF10414", err) } @@ -340,10 +371,41 @@ func TestDefineContractAPINonMultipartyUpdate(t *testing.T) { ds.mdi.On("UpsertContractAPI", mock.Anything, mock.Anything, mock.Anything).Return(nil) ds.mdi.On("InsertEvent", mock.Anything, mock.Anything).Return(nil) - err := ds.DefineContractAPI(context.Background(), url, api, false) + err := ds.DefineContractAPI(context.Background(), url, api, false, nil) assert.NoError(t, err) } +// test that we can call defineContractAPI with custom topics +func TestDefineContractAPICustomTopics(t *testing.T) { + ds := newTestDefinitionSender(t) + defer ds.cleanup(t) + ds.multiparty = true + + url := "http://firefly" + api := &core.ContractAPI{ + Name: "banana", + Published: true, + } + + ds.mcm.On("ResolveContractAPI", context.Background(), url, api).Return(nil) + ds.mim.On("GetRootOrg", context.Background()).Return(&core.Identity{ + IdentityBase: core.IdentityBase{ + DID: "firefly:org1", + }, + }, nil) + ds.mim.On("ResolveInputSigningIdentity", context.Background(), mock.Anything).Return(nil) + ds.mdi.On("GetContractAPIByNetworkName", context.Background(), "ns1", "banana").Return(nil, nil) + + mms := &syncasyncmocks.Sender{} + ds.mbm.On("NewBroadcast", mock.Anything).Return(mms) + mms.On("Send", context.Background()).Return(nil) + + err := ds.DefineContractAPI(context.Background(), url, api, false, []string{"my-custom-topic"}) + assert.NoError(t, err) + + mms.AssertExpectations(t) +} + func TestPublishFFI(t *testing.T) { ds := newTestDefinitionSender(t) defer ds.cleanup(t) @@ -372,7 +434,7 @@ func TestPublishFFI(t *testing.T) { mms.On("Send", context.Background()).Return(nil) mockRunAsGroupPassthrough(ds.mdi) - result, err := ds.PublishFFI(context.Background(), "ffi1", "1.0", "ffi1-shared", false) + result, err := ds.PublishFFI(context.Background(), "ffi1", "1.0", "ffi1-shared", false, nil) assert.NoError(t, err) assert.Equal(t, ffi, result) assert.True(t, ffi.Published) @@ -395,7 +457,7 @@ func TestPublishFFIAlreadyPublished(t *testing.T) { ds.mcm.On("GetFFIWithChildren", context.Background(), "ffi1", "1.0").Return(ffi, nil) mockRunAsGroupPassthrough(ds.mdi) - _, err := ds.PublishFFI(context.Background(), "ffi1", "1.0", "ffi1-shared", false) + _, err := ds.PublishFFI(context.Background(), "ffi1", "1.0", "ffi1-shared", false, nil) assert.Regexp(t, "FF10450", err) } @@ -407,7 +469,7 @@ func TestPublishFFIQueryFail(t *testing.T) { ds.mcm.On("GetFFIWithChildren", context.Background(), "ffi1", "1.0").Return(nil, fmt.Errorf("pop")) mockRunAsGroupPassthrough(ds.mdi) - _, err := ds.PublishFFI(context.Background(), "ffi1", "1.0", "ffi1-shared", false) + _, err := ds.PublishFFI(context.Background(), "ffi1", "1.0", "ffi1-shared", false, nil) assert.EqualError(t, err, "pop") } @@ -427,7 +489,7 @@ func TestPublishFFIResolveFail(t *testing.T) { ds.mcm.On("ResolveFFI", context.Background(), ffi).Return(fmt.Errorf("pop")) mockRunAsGroupPassthrough(ds.mdi) - _, err := ds.PublishFFI(context.Background(), "ffi1", "1.0", "ffi1-shared", false) + _, err := ds.PublishFFI(context.Background(), "ffi1", "1.0", "ffi1-shared", false, nil) assert.EqualError(t, err, "pop") } @@ -458,7 +520,7 @@ func TestPublishFFIPrepareFail(t *testing.T) { mms.On("Prepare", context.Background()).Return(fmt.Errorf("pop")) mockRunAsGroupPassthrough(ds.mdi) - _, err := ds.PublishFFI(context.Background(), "ffi1", "1.0", "ffi1-shared", false) + _, err := ds.PublishFFI(context.Background(), "ffi1", "1.0", "ffi1-shared", false, nil) assert.EqualError(t, err, "pop") mms.AssertExpectations(t) @@ -469,7 +531,7 @@ func TestPublishFFINonMultiparty(t *testing.T) { defer ds.cleanup(t) ds.multiparty = false - _, err := ds.PublishFFI(context.Background(), "ffi1", "1.0", "ffi1-shared", false) + _, err := ds.PublishFFI(context.Background(), "ffi1", "1.0", "ffi1-shared", false, nil) assert.Regexp(t, "FF10414", err) } @@ -501,7 +563,7 @@ func TestPublishContractAPI(t *testing.T) { mms.On("Send", context.Background()).Return(nil) mockRunAsGroupPassthrough(ds.mdi) - result, err := ds.PublishContractAPI(context.Background(), url, "api", "api-shared", false) + result, err := ds.PublishContractAPI(context.Background(), url, "api", "api-shared", false, nil) assert.NoError(t, err) assert.Equal(t, api, result) assert.True(t, api.Published) @@ -524,7 +586,7 @@ func TestPublishContractAPIAlreadyPublished(t *testing.T) { ds.mcm.On("GetContractAPI", context.Background(), url, "api").Return(api, nil) mockRunAsGroupPassthrough(ds.mdi) - _, err := ds.PublishContractAPI(context.Background(), url, "api", "api-shared", false) + _, err := ds.PublishContractAPI(context.Background(), url, "api", "api-shared", false, nil) assert.Regexp(t, "FF10450", err) } @@ -538,7 +600,7 @@ func TestPublishContractAPIQueryFail(t *testing.T) { ds.mcm.On("GetContractAPI", context.Background(), url, "api").Return(nil, fmt.Errorf("pop")) mockRunAsGroupPassthrough(ds.mdi) - _, err := ds.PublishContractAPI(context.Background(), url, "api", "api-shared", false) + _, err := ds.PublishContractAPI(context.Background(), url, "api", "api-shared", false, nil) assert.EqualError(t, err, "pop") } @@ -558,7 +620,7 @@ func TestPublishContractAPIResolveFail(t *testing.T) { ds.mcm.On("ResolveContractAPI", context.Background(), url, api).Return(fmt.Errorf("pop")) mockRunAsGroupPassthrough(ds.mdi) - _, err := ds.PublishContractAPI(context.Background(), url, "api", "api-shared", false) + _, err := ds.PublishContractAPI(context.Background(), url, "api", "api-shared", false, nil) assert.EqualError(t, err, "pop") } @@ -569,7 +631,7 @@ func TestPublishContractAPINonMultiparty(t *testing.T) { url := "http://firefly" - _, err := ds.PublishContractAPI(context.Background(), url, "api", "api-shared", false) + _, err := ds.PublishContractAPI(context.Background(), url, "api", "api-shared", false, nil) assert.Regexp(t, "FF10414", err) } @@ -590,7 +652,7 @@ func TestPublishContractAPINetworkNameFail(t *testing.T) { ds.mcm.On("ResolveContractAPI", context.Background(), url, api).Return(nil) mockRunAsGroupPassthrough(ds.mdi) - _, err := ds.PublishContractAPI(context.Background(), url, "api", "api-shared", false) + _, err := ds.PublishContractAPI(context.Background(), url, "api", "api-shared", false, nil) assert.EqualError(t, err, "pop") } @@ -611,7 +673,7 @@ func TestPublishContractAPINetworkNameConflict(t *testing.T) { ds.mcm.On("ResolveContractAPI", context.Background(), url, api).Return(nil) mockRunAsGroupPassthrough(ds.mdi) - _, err := ds.PublishContractAPI(context.Background(), url, "api", "api-shared", false) + _, err := ds.PublishContractAPI(context.Background(), url, "api", "api-shared", false, nil) assert.Regexp(t, "FF10448", err) } @@ -636,7 +698,7 @@ func TestPublishContractAPIInterfaceFail(t *testing.T) { mockRunAsGroupPassthrough(ds.mdi) ds.mdi.On("GetFFIByID", context.Background(), "ns1", api.Interface.ID).Return(nil, fmt.Errorf("pop")) - _, err := ds.PublishContractAPI(context.Background(), url, "api", "api-shared", false) + _, err := ds.PublishContractAPI(context.Background(), url, "api", "api-shared", false, nil) assert.EqualError(t, err, "pop") } @@ -661,7 +723,7 @@ func TestPublishContractAPIInterfaceNotFound(t *testing.T) { mockRunAsGroupPassthrough(ds.mdi) ds.mdi.On("GetFFIByID", context.Background(), "ns1", api.Interface.ID).Return(nil, nil) - _, err := ds.PublishContractAPI(context.Background(), url, "api", "api-shared", false) + _, err := ds.PublishContractAPI(context.Background(), url, "api", "api-shared", false, nil) assert.Regexp(t, "FF10303", err) } @@ -688,6 +750,6 @@ func TestPublishContractAPIInterfaceNotPublished(t *testing.T) { Published: false, }, nil) - _, err := ds.PublishContractAPI(context.Background(), url, "api", "api-shared", false) + _, err := ds.PublishContractAPI(context.Background(), url, "api", "api-shared", false, nil) assert.Regexp(t, "FF10451", err) } diff --git a/internal/definitions/sender_datatype.go b/internal/definitions/sender_datatype.go index d34ee9e9e9..6ee3fb025e 100644 --- a/internal/definitions/sender_datatype.go +++ b/internal/definitions/sender_datatype.go @@ -44,7 +44,7 @@ func (ds *definitionSender) DefineDatatype(ctx context.Context, datatype *core.D } datatype.Namespace = "" - msg, err := ds.getSenderDefault(ctx, datatype, core.SystemTagDefineDatatype).send(ctx, waitConfirm) + msg, err := ds.getSenderDefault(ctx, datatype, core.SystemTagDefineDatatype, nil).send(ctx, waitConfirm) if msg != nil { datatype.Message = msg.Header.ID } diff --git a/internal/definitions/sender_identity.go b/internal/definitions/sender_identity.go index 02986684db..3a3db16d7b 100644 --- a/internal/definitions/sender_identity.go +++ b/internal/definitions/sender_identity.go @@ -34,7 +34,7 @@ func (ds *definitionSender) ClaimIdentity(ctx context.Context, claim *core.Ident } claim.Identity.Namespace = "" - claimMsg, err := ds.getSenderResolved(ctx, claim, signingIdentity, core.SystemTagIdentityClaim).send(ctx, false) + claimMsg, err := ds.getSenderResolved(ctx, claim, signingIdentity, core.SystemTagIdentityClaim, nil).send(ctx, false) if err != nil { return err } @@ -48,7 +48,7 @@ func (ds *definitionSender) ClaimIdentity(ctx context.Context, claim *core.Ident Hash: claimMsg.Hash, }, Identity: claim.Identity.IdentityBase, - }, parentSigner, core.SystemTagIdentityVerification).send(ctx, false) + }, parentSigner, core.SystemTagIdentityVerification, nil).send(ctx, false) if err != nil { return err } @@ -66,7 +66,7 @@ func (ds *definitionSender) ClaimIdentity(ctx context.Context, claim *core.Ident func (ds *definitionSender) UpdateIdentity(ctx context.Context, identity *core.Identity, def *core.IdentityUpdate, signingIdentity *core.SignerRef, waitConfirm bool) error { if ds.multiparty { - updateMsg, err := ds.getSender(ctx, def, signingIdentity, core.SystemTagIdentityUpdate).send(ctx, waitConfirm) + updateMsg, err := ds.getSender(ctx, def, signingIdentity, core.SystemTagIdentityUpdate, nil).send(ctx, waitConfirm) identity.Messages.Update = updateMsg.Header.ID return err } diff --git a/internal/definitions/sender_test.go b/internal/definitions/sender_test.go index 261c5c3579..935a845296 100644 --- a/internal/definitions/sender_test.go +++ b/internal/definitions/sender_test.go @@ -149,7 +149,7 @@ func TestCreateDefinitionConfirm(t *testing.T) { mms.On("SendAndWait", mock.Anything).Return(nil) ds.multiparty = true - _, err := ds.getSenderDefault(ds.ctx, &core.Datatype{}, core.SystemTagDefineDatatype).send(ds.ctx, true) + _, err := ds.getSenderDefault(ds.ctx, &core.Datatype{}, core.SystemTagDefineDatatype, nil).send(ds.ctx, true) assert.NoError(t, err) mim.AssertExpectations(t) @@ -176,7 +176,7 @@ func TestCreateDatatypeDefinitionAsNodeConfirm(t *testing.T) { ds.multiparty = true - _, err := ds.getSenderDefault(ds.ctx, &core.Datatype{}, core.SystemTagDefineDatatype).send(ds.ctx, true) + _, err := ds.getSenderDefault(ds.ctx, &core.Datatype{}, core.SystemTagDefineDatatype, nil).send(ds.ctx, true) assert.NoError(t, err) mim.AssertExpectations(t) @@ -195,6 +195,6 @@ func TestCreateDefinitionBadIdentity(t *testing.T) { _, err := ds.getSender(ds.ctx, &core.Datatype{}, &core.SignerRef{ Author: "wrong", Key: "wrong", - }, core.SystemTagDefineDatatype).send(ds.ctx, false) + }, core.SystemTagDefineDatatype, nil).send(ds.ctx, false) assert.Regexp(t, "pop", err) } diff --git a/internal/definitions/sender_tokenpool.go b/internal/definitions/sender_tokenpool.go index 1b02783c51..c7960dc613 100644 --- a/internal/definitions/sender_tokenpool.go +++ b/internal/definitions/sender_tokenpool.go @@ -97,7 +97,7 @@ func (ds *definitionSender) getTokenPoolSender(ctx context.Context, pool *core.T pool.Active = false definition := &core.TokenPoolDefinition{Pool: pool} - sender := ds.getSenderDefault(ctx, definition, core.SystemTagDefinePool) + sender := ds.getSenderDefault(ctx, definition, core.SystemTagDefinePool, nil) if sender.message != nil { pool.Message = sender.message.Header.ID } diff --git a/mocks/definitionsmocks/sender.go b/mocks/definitionsmocks/sender.go index d4720e15c7..374761353e 100644 --- a/mocks/definitionsmocks/sender.go +++ b/mocks/definitionsmocks/sender.go @@ -35,17 +35,17 @@ func (_m *Sender) ClaimIdentity(ctx context.Context, def *core.IdentityClaim, si return r0 } -// DefineContractAPI provides a mock function with given fields: ctx, httpServerURL, api, waitConfirm -func (_m *Sender) DefineContractAPI(ctx context.Context, httpServerURL string, api *core.ContractAPI, waitConfirm bool) error { - ret := _m.Called(ctx, httpServerURL, api, waitConfirm) +// DefineContractAPI provides a mock function with given fields: ctx, httpServerURL, api, waitConfirm, topics +func (_m *Sender) DefineContractAPI(ctx context.Context, httpServerURL string, api *core.ContractAPI, waitConfirm bool, topics []string) error { + ret := _m.Called(ctx, httpServerURL, api, waitConfirm, topics) if len(ret) == 0 { panic("no return value specified for DefineContractAPI") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, *core.ContractAPI, bool) error); ok { - r0 = rf(ctx, httpServerURL, api, waitConfirm) + if rf, ok := ret.Get(0).(func(context.Context, string, *core.ContractAPI, bool, []string) error); ok { + r0 = rf(ctx, httpServerURL, api, waitConfirm, topics) } else { r0 = ret.Error(0) } @@ -71,17 +71,17 @@ func (_m *Sender) DefineDatatype(ctx context.Context, datatype *core.Datatype, w return r0 } -// DefineFFI provides a mock function with given fields: ctx, ffi, waitConfirm -func (_m *Sender) DefineFFI(ctx context.Context, ffi *fftypes.FFI, waitConfirm bool) error { - ret := _m.Called(ctx, ffi, waitConfirm) +// DefineFFI provides a mock function with given fields: ctx, ffi, waitConfirm, topics +func (_m *Sender) DefineFFI(ctx context.Context, ffi *fftypes.FFI, waitConfirm bool, topics []string) error { + ret := _m.Called(ctx, ffi, waitConfirm, topics) if len(ret) == 0 { panic("no return value specified for DefineFFI") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *fftypes.FFI, bool) error); ok { - r0 = rf(ctx, ffi, waitConfirm) + if rf, ok := ret.Get(0).(func(context.Context, *fftypes.FFI, bool, []string) error); ok { + r0 = rf(ctx, ffi, waitConfirm, topics) } else { r0 = ret.Error(0) } @@ -125,9 +125,9 @@ func (_m *Sender) Name() string { return r0 } -// PublishContractAPI provides a mock function with given fields: ctx, httpServerURL, name, networkName, waitConfirm -func (_m *Sender) PublishContractAPI(ctx context.Context, httpServerURL string, name string, networkName string, waitConfirm bool) (*core.ContractAPI, error) { - ret := _m.Called(ctx, httpServerURL, name, networkName, waitConfirm) +// PublishContractAPI provides a mock function with given fields: ctx, httpServerURL, name, networkName, waitConfirm, topics +func (_m *Sender) PublishContractAPI(ctx context.Context, httpServerURL string, name string, networkName string, waitConfirm bool, topics []string) (*core.ContractAPI, error) { + ret := _m.Called(ctx, httpServerURL, name, networkName, waitConfirm, topics) if len(ret) == 0 { panic("no return value specified for PublishContractAPI") @@ -135,19 +135,19 @@ func (_m *Sender) PublishContractAPI(ctx context.Context, httpServerURL string, var r0 *core.ContractAPI var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, string, bool) (*core.ContractAPI, error)); ok { - return rf(ctx, httpServerURL, name, networkName, waitConfirm) + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, bool, []string) (*core.ContractAPI, error)); ok { + return rf(ctx, httpServerURL, name, networkName, waitConfirm, topics) } - if rf, ok := ret.Get(0).(func(context.Context, string, string, string, bool) *core.ContractAPI); ok { - r0 = rf(ctx, httpServerURL, name, networkName, waitConfirm) + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, bool, []string) *core.ContractAPI); ok { + r0 = rf(ctx, httpServerURL, name, networkName, waitConfirm, topics) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*core.ContractAPI) } } - if rf, ok := ret.Get(1).(func(context.Context, string, string, string, bool) error); ok { - r1 = rf(ctx, httpServerURL, name, networkName, waitConfirm) + if rf, ok := ret.Get(1).(func(context.Context, string, string, string, bool, []string) error); ok { + r1 = rf(ctx, httpServerURL, name, networkName, waitConfirm, topics) } else { r1 = ret.Error(1) } @@ -155,9 +155,9 @@ func (_m *Sender) PublishContractAPI(ctx context.Context, httpServerURL string, return r0, r1 } -// PublishFFI provides a mock function with given fields: ctx, name, version, networkName, waitConfirm -func (_m *Sender) PublishFFI(ctx context.Context, name string, version string, networkName string, waitConfirm bool) (*fftypes.FFI, error) { - ret := _m.Called(ctx, name, version, networkName, waitConfirm) +// PublishFFI provides a mock function with given fields: ctx, name, version, networkName, waitConfirm, topics +func (_m *Sender) PublishFFI(ctx context.Context, name string, version string, networkName string, waitConfirm bool, topics []string) (*fftypes.FFI, error) { + ret := _m.Called(ctx, name, version, networkName, waitConfirm, topics) if len(ret) == 0 { panic("no return value specified for PublishFFI") @@ -165,19 +165,19 @@ func (_m *Sender) PublishFFI(ctx context.Context, name string, version string, n var r0 *fftypes.FFI var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, string, bool) (*fftypes.FFI, error)); ok { - return rf(ctx, name, version, networkName, waitConfirm) + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, bool, []string) (*fftypes.FFI, error)); ok { + return rf(ctx, name, version, networkName, waitConfirm, topics) } - if rf, ok := ret.Get(0).(func(context.Context, string, string, string, bool) *fftypes.FFI); ok { - r0 = rf(ctx, name, version, networkName, waitConfirm) + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, bool, []string) *fftypes.FFI); ok { + r0 = rf(ctx, name, version, networkName, waitConfirm, topics) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*fftypes.FFI) } } - if rf, ok := ret.Get(1).(func(context.Context, string, string, string, bool) error); ok { - r1 = rf(ctx, name, version, networkName, waitConfirm) + if rf, ok := ret.Get(1).(func(context.Context, string, string, string, bool, []string) error); ok { + r1 = rf(ctx, name, version, networkName, waitConfirm, topics) } else { r1 = ret.Error(1) } From 18ea870384d43e3654a6dd56c06ba21077541af9 Mon Sep 17 00:00:00 2001 From: Kaijun Zhang Date: Mon, 13 Jul 2026 13:42:56 -0400 Subject: [PATCH 2/3] -update: custom topics Signed-off-by: Kaijun Zhang --- doc-site/docs/swagger/swagger.yaml | 40 ++++++++++++++----- .../route_post_contract_api_publish.go | 2 +- .../route_post_contract_interface_publish.go | 2 +- .../apiserver/route_post_new_contract_api.go | 2 +- .../route_post_new_contract_interface.go | 2 +- internal/apiserver/route_put_contract_api.go | 2 +- internal/coremsgs/en_api_translations.go | 2 +- internal/coremsgs/en_error_messages.go | 1 + internal/definitions/sender.go | 14 +++---- internal/definitions/sender_contracts_test.go | 25 ++++++++++++ 10 files changed, 68 insertions(+), 24 deletions(-) diff --git a/doc-site/docs/swagger/swagger.yaml b/doc-site/docs/swagger/swagger.yaml index 1d7ca1811a..3f750440f5 100644 --- a/doc-site/docs/swagger/swagger.yaml +++ b/doc-site/docs/swagger/swagger.yaml @@ -171,7 +171,9 @@ paths: name: publish schema: type: string - - description: Custom topics for ordering definition broadcast messages + - description: Custom topics to use for ordering of the definition broadcast + message, instead of the default system topic. Each topic must be a non-empty + string. Only used when the definition is published to the multiparty network in: query name: topics schema: @@ -1807,7 +1809,9 @@ paths: name: confirm schema: type: string - - description: Custom topics for ordering definition broadcast messages + - description: Custom topics to use for ordering of the definition broadcast + message, instead of the default system topic. Each topic must be a non-empty + string. Only used when the definition is published to the multiparty network in: query name: topics schema: @@ -2425,7 +2429,9 @@ paths: schema: example: "true" type: string - - description: Custom topics for ordering definition broadcast messages + - description: Custom topics to use for ordering of the definition broadcast + message, instead of the default system topic. Each topic must be a non-empty + string. Only used when the definition is published to the multiparty network in: query name: topics schema: @@ -3849,7 +3855,9 @@ paths: name: publish schema: type: string - - description: Custom topics for ordering definition broadcast messages + - description: Custom topics to use for ordering of the definition broadcast + message, instead of the default system topic. Each topic must be a non-empty + string. Only used when the definition is published to the multiparty network in: query name: topics schema: @@ -4884,7 +4892,9 @@ paths: name: confirm schema: type: string - - description: Custom topics for ordering definition broadcast messages + - description: Custom topics to use for ordering of the definition broadcast + message, instead of the default system topic. Each topic must be a non-empty + string. Only used when the definition is published to the multiparty network in: query name: topics schema: @@ -13756,7 +13766,9 @@ paths: name: publish schema: type: string - - description: Custom topics for ordering definition broadcast messages + - description: Custom topics to use for ordering of the definition broadcast + message, instead of the default system topic. Each topic must be a non-empty + string. Only used when the definition is published to the multiparty network in: query name: topics schema: @@ -15652,7 +15664,9 @@ paths: name: confirm schema: type: string - - description: Custom topics for ordering definition broadcast messages + - description: Custom topics to use for ordering of the definition broadcast + message, instead of the default system topic. Each topic must be a non-empty + string. Only used when the definition is published to the multiparty network in: query name: topics schema: @@ -16550,7 +16564,9 @@ paths: schema: example: "true" type: string - - description: Custom topics for ordering definition broadcast messages + - description: Custom topics to use for ordering of the definition broadcast + message, instead of the default system topic. Each topic must be a non-empty + string. Only used when the definition is published to the multiparty network in: query name: topics schema: @@ -18037,7 +18053,9 @@ paths: name: publish schema: type: string - - description: Custom topics for ordering definition broadcast messages + - description: Custom topics to use for ordering of the definition broadcast + message, instead of the default system topic. Each topic must be a non-empty + string. Only used when the definition is published to the multiparty network in: query name: topics schema: @@ -19100,7 +19118,9 @@ paths: name: confirm schema: type: string - - description: Custom topics for ordering definition broadcast messages + - description: Custom topics to use for ordering of the definition broadcast + message, instead of the default system topic. Each topic must be a non-empty + string. Only used when the definition is published to the multiparty network in: query name: topics schema: diff --git a/internal/apiserver/route_post_contract_api_publish.go b/internal/apiserver/route_post_contract_api_publish.go index 3102529967..9714ff726c 100644 --- a/internal/apiserver/route_post_contract_api_publish.go +++ b/internal/apiserver/route_post_contract_api_publish.go @@ -35,7 +35,7 @@ var postContractAPIPublish = &ffapi.Route{ }, QueryParams: []*ffapi.QueryParam{ {Name: "confirm", Description: coremsgs.APIConfirmMsgQueryParam, IsBool: true}, - {Name: "topics", Description: coremsgs.APICustomTopicsQueryParam, IsArray: true}, + {Name: "topics", Description: coremsgs.APICustomTopicsParam, IsArray: true}, }, Description: coremsgs.APIEndpointsPostContractAPIPublish, JSONInputValue: func() interface{} { return &core.DefinitionPublish{} }, diff --git a/internal/apiserver/route_post_contract_interface_publish.go b/internal/apiserver/route_post_contract_interface_publish.go index c8607dd463..1d63f403f8 100644 --- a/internal/apiserver/route_post_contract_interface_publish.go +++ b/internal/apiserver/route_post_contract_interface_publish.go @@ -36,7 +36,7 @@ var postContractInterfacePublish = &ffapi.Route{ }, QueryParams: []*ffapi.QueryParam{ {Name: "confirm", Description: coremsgs.APIConfirmMsgQueryParam, IsBool: true}, - {Name: "topics", Description: coremsgs.APICustomTopicsQueryParam, IsArray: true}, + {Name: "topics", Description: coremsgs.APICustomTopicsParam, IsArray: true}, }, Description: coremsgs.APIEndpointsPostContractInterfacePublish, JSONInputValue: func() interface{} { return &core.DefinitionPublish{} }, diff --git a/internal/apiserver/route_post_new_contract_api.go b/internal/apiserver/route_post_new_contract_api.go index bfda155be4..fb40c6ce90 100644 --- a/internal/apiserver/route_post_new_contract_api.go +++ b/internal/apiserver/route_post_new_contract_api.go @@ -34,7 +34,7 @@ var postNewContractAPI = &ffapi.Route{ QueryParams: []*ffapi.QueryParam{ {Name: "confirm", Description: coremsgs.APIConfirmMsgQueryParam, IsBool: true, Example: "true"}, {Name: "publish", Description: coremsgs.APIPublishQueryParam, IsBool: true}, - {Name: "topics", Description: coremsgs.APICustomTopicsQueryParam, IsArray: true}, + {Name: "topics", Description: coremsgs.APICustomTopicsParam, IsArray: true}, }, Description: coremsgs.APIEndpointsPostNewContractAPI, JSONInputValue: func() interface{} { return &core.ContractAPI{} }, diff --git a/internal/apiserver/route_post_new_contract_interface.go b/internal/apiserver/route_post_new_contract_interface.go index e90adea813..c2172e00e8 100644 --- a/internal/apiserver/route_post_new_contract_interface.go +++ b/internal/apiserver/route_post_new_contract_interface.go @@ -34,7 +34,7 @@ var postNewContractInterface = &ffapi.Route{ QueryParams: []*ffapi.QueryParam{ {Name: "confirm", Description: coremsgs.APIConfirmMsgQueryParam, IsBool: true, Example: "true"}, {Name: "publish", Description: coremsgs.APIPublishQueryParam, IsBool: true}, - {Name: "topics", Description: coremsgs.APICustomTopicsQueryParam, IsArray: true}, + {Name: "topics", Description: coremsgs.APICustomTopicsParam, IsArray: true}, }, Description: coremsgs.APIEndpointsPostNewContractInterface, JSONInputValue: func() interface{} { return &fftypes.FFI{} }, diff --git a/internal/apiserver/route_put_contract_api.go b/internal/apiserver/route_put_contract_api.go index a3a37e7d33..a04521faff 100644 --- a/internal/apiserver/route_put_contract_api.go +++ b/internal/apiserver/route_put_contract_api.go @@ -36,7 +36,7 @@ var putContractAPI = &ffapi.Route{ }, QueryParams: []*ffapi.QueryParam{ {Name: "confirm", Description: coremsgs.APIConfirmMsgQueryParam, IsBool: true, Example: "true"}, - {Name: "topics", Description: coremsgs.APICustomTopicsQueryParam, IsArray: true}, + {Name: "topics", Description: coremsgs.APICustomTopicsParam, IsArray: true}, }, Description: coremsgs.APIParamsContractAPIID, JSONInputValue: func() interface{} { return &core.ContractAPI{} }, diff --git a/internal/coremsgs/en_api_translations.go b/internal/coremsgs/en_api_translations.go index 590e28c692..fb99ef52e5 100644 --- a/internal/coremsgs/en_api_translations.go +++ b/internal/coremsgs/en_api_translations.go @@ -217,5 +217,5 @@ var ( APISubscriptionStartSequenceID = ffm("api.startsequenceid", "The sequence ID in the raw event stream to start indexing through events from. Leave blank to start indexing from the most recent events") APISubscriptionEndSequenceID = ffm("api.endsequenceid", "The sequence ID in the raw event stream to stop indexing through events at. Leave blank to start indexing from the most recent events") - APICustomTopicsQueryParam = ffm("api.customTopicsQueryParam", "Custom topics for ordering definition broadcast messages") + APICustomTopicsParam = ffm("api.customTopicsParam", "Custom topics to use for ordering of the definition broadcast message, instead of the default system topic. Each topic must be a non-empty string. Only used when the definition is published to the multiparty network") ) diff --git a/internal/coremsgs/en_error_messages.go b/internal/coremsgs/en_error_messages.go index 7ca9bd83a0..2370255994 100644 --- a/internal/coremsgs/en_error_messages.go +++ b/internal/coremsgs/en_error_messages.go @@ -323,4 +323,5 @@ var ( MsgNodeMissingProfile = ffe("FF10482", "Node provided for check does not have a profile", 500) MsgInvalidCardanoAddress = ffe("FF10483", "Supplied cardano address is invalid", 400) MsgCardanoconnectRESTErr = ffe("FF10484", "Error from cardano connector: %s") + MsgEmptyCustomTopic = ffe("FF10485", "Custom topic at index %d is empty. Each custom topic for the definition broadcast message must be a non-empty string", 400) ) diff --git a/internal/definitions/sender.go b/internal/definitions/sender.go index 3dcf7ecfc2..094366cc38 100644 --- a/internal/definitions/sender.go +++ b/internal/definitions/sender.go @@ -159,15 +159,13 @@ func (ds *definitionSender) getSenderResolved(ctx context.Context, def core.Defi return wrapSendError(i18n.WrapError(ctx, err, coremsgs.MsgSerializationFailed)) } topics := fftypes.FFStringArray{def.Topic()} - - var filtered []string - for _, t := range customTopics { - if t != "" { - filtered = append(filtered, t) + if len(customTopics) > 0 { + for i, t := range customTopics { + if t == "" { + return wrapSendError(i18n.NewError(ctx, coremsgs.MsgEmptyCustomTopic, i)) + } } - } - if len(filtered) > 0 { - topics = fftypes.FFStringArray(filtered) + topics = fftypes.FFStringArray(customTopics) } dataValue := fftypes.JSONAnyPtrBytes(b) message := &core.MessageInOut{ diff --git a/internal/definitions/sender_contracts_test.go b/internal/definitions/sender_contracts_test.go index 7b7b856b40..8cc1f5ef46 100644 --- a/internal/definitions/sender_contracts_test.go +++ b/internal/definitions/sender_contracts_test.go @@ -258,6 +258,31 @@ func TestDefineFFICustomTopics(t *testing.T) { mms.AssertExpectations(t) } +// test that an empty custom topic is rejected with a descriptive validation error +func TestDefineFFIEmptyCustomTopic(t *testing.T) { + ds := newTestDefinitionSender(t) + defer ds.cleanup(t) + ds.multiparty = true + + ffi := &fftypes.FFI{ + Name: "ffi1", + Version: "1.0", + Published: true, + } + + ds.mdi.On("GetFFIByNetworkName", context.Background(), "ns1", "ffi1", "1.0").Return(nil, nil) + ds.mcm.On("ResolveFFI", context.Background(), ffi).Return(nil) + ds.mim.On("GetRootOrg", context.Background()).Return(&core.Identity{ + IdentityBase: core.IdentityBase{ + DID: "firefly:org1", + }, + }, nil) + ds.mim.On("ResolveInputSigningIdentity", context.Background(), mock.Anything).Return(nil) + + err := ds.DefineFFI(context.Background(), ffi, false, []string{"my-custom-topic", ""}) + assert.Regexp(t, "FF10485.*index 1", err) +} + func TestDefineContractAPIResolveFail(t *testing.T) { ds := newTestDefinitionSender(t) defer ds.cleanup(t) From 5bc44224058ac2f08c74a1e0ce179c77e97278b5 Mon Sep 17 00:00:00 2001 From: Kaijun Zhang Date: Wed, 15 Jul 2026 09:04:20 -0500 Subject: [PATCH 3/3] -feat: relocate topics into request body Signed-off-by: Kaijun Zhang --- doc-site/docs/swagger/swagger.yaml | 234 +++++++++++------- .../route_post_contract_api_publish.go | 3 +- .../route_post_contract_api_publish_test.go | 5 +- .../route_post_contract_interface_publish.go | 3 +- ...te_post_contract_interface_publish_test.go | 4 +- .../apiserver/route_post_new_contract_api.go | 8 +- .../route_post_new_contract_api_test.go | 5 +- .../route_post_new_contract_interface.go | 9 +- .../route_post_new_contract_interface_test.go | 5 +- internal/apiserver/route_put_contract_api.go | 8 +- .../apiserver/route_put_contract_api_test.go | 5 +- internal/coremsgs/en_api_translations.go | 2 - internal/coremsgs/en_struct_descriptions.go | 7 + pkg/core/contracts.go | 10 + pkg/core/definition.go | 3 +- 15 files changed, 192 insertions(+), 119 deletions(-) diff --git a/doc-site/docs/swagger/swagger.yaml b/doc-site/docs/swagger/swagger.yaml index 3f750440f5..5fd679f24a 100644 --- a/doc-site/docs/swagger/swagger.yaml +++ b/doc-site/docs/swagger/swagger.yaml @@ -171,15 +171,6 @@ paths: name: publish schema: type: string - - description: Custom topics to use for ordering of the definition broadcast - message, instead of the default system topic. Each topic must be a non-empty - string. Only used when the definition is published to the multiparty network - in: query - name: topics - schema: - items: - type: string - type: array - description: Server-side request timeout (milliseconds, or set a custom suffix like 10s) in: header @@ -224,6 +215,18 @@ paths: description: The published name of the API within the multiparty network type: string + topics: + description: An optional list of custom topics to use for ordering + of the definition broadcast message, instead of the default system + topic. Each topic must be a non-empty string. Only used when the + definition is published to the multiparty network + items: + description: An optional list of custom topics to use for ordering + of the definition broadcast message, instead of the default + system topic. Each topic must be a non-empty string. Only used + when the definition is published to the multiparty network + type: string + type: array type: object required: true responses: @@ -1809,15 +1812,6 @@ paths: name: confirm schema: type: string - - description: Custom topics to use for ordering of the definition broadcast - message, instead of the default system topic. Each topic must be a non-empty - string. Only used when the definition is published to the multiparty network - in: query - name: topics - schema: - items: - type: string - type: array - description: Server-side request timeout (milliseconds, or set a custom suffix like 10s) in: header @@ -1834,6 +1828,18 @@ paths: description: An optional name to be used for publishing this definition to the multiparty network, which may differ from the local name type: string + topics: + description: An optional list of custom topics to use for ordering + of the definition broadcast message, instead of the default system + topic. Each topic must be a non-empty string. Only used when the + definition is published to the multiparty network + items: + description: An optional list of custom topics to use for ordering + of the definition broadcast message, instead of the default + system topic. Each topic must be a non-empty string. Only used + when the definition is published to the multiparty network + type: string + type: array type: object required: true responses: @@ -2429,15 +2435,6 @@ paths: schema: example: "true" type: string - - description: Custom topics to use for ordering of the definition broadcast - message, instead of the default system topic. Each topic must be a non-empty - string. Only used when the definition is published to the multiparty network - in: query - name: topics - schema: - items: - type: string - type: array - description: Server-side request timeout (milliseconds, or set a custom suffix like 10s) in: header @@ -2482,6 +2479,18 @@ paths: description: The published name of the API within the multiparty network type: string + topics: + description: An optional list of custom topics to use for ordering + of the definition broadcast message, instead of the default system + topic. Each topic must be a non-empty string. Only used when the + definition is published to the multiparty network + items: + description: An optional list of custom topics to use for ordering + of the definition broadcast message, instead of the default + system topic. Each topic must be a non-empty string. Only used + when the definition is published to the multiparty network + type: string + type: array type: object required: true responses: @@ -3855,15 +3864,6 @@ paths: name: publish schema: type: string - - description: Custom topics to use for ordering of the definition broadcast - message, instead of the default system topic. Each topic must be a non-empty - string. Only used when the definition is published to the multiparty network - in: query - name: topics - schema: - items: - type: string - type: array - description: Server-side request timeout (milliseconds, or set a custom suffix like 10s) in: header @@ -4028,6 +4028,18 @@ paths: description: The published name of the FFI within the multiparty network type: string + topics: + description: An optional list of custom topics to use for ordering + of the definition broadcast message, instead of the default system + topic. Each topic must be a non-empty string. Only used when the + definition is published to the multiparty network + items: + description: An optional list of custom topics to use for ordering + of the definition broadcast message, instead of the default + system topic. Each topic must be a non-empty string. Only used + when the definition is published to the multiparty network + type: string + type: array version: description: A version for the FFI - use of semantic versioning such as 'v1.0.1' is encouraged @@ -4892,15 +4904,6 @@ paths: name: confirm schema: type: string - - description: Custom topics to use for ordering of the definition broadcast - message, instead of the default system topic. Each topic must be a non-empty - string. Only used when the definition is published to the multiparty network - in: query - name: topics - schema: - items: - type: string - type: array - description: Server-side request timeout (milliseconds, or set a custom suffix like 10s) in: header @@ -4917,6 +4920,18 @@ paths: description: An optional name to be used for publishing this definition to the multiparty network, which may differ from the local name type: string + topics: + description: An optional list of custom topics to use for ordering + of the definition broadcast message, instead of the default system + topic. Each topic must be a non-empty string. Only used when the + definition is published to the multiparty network + items: + description: An optional list of custom topics to use for ordering + of the definition broadcast message, instead of the default + system topic. Each topic must be a non-empty string. Only used + when the definition is published to the multiparty network + type: string + type: array type: object required: true responses: @@ -13766,15 +13781,6 @@ paths: name: publish schema: type: string - - description: Custom topics to use for ordering of the definition broadcast - message, instead of the default system topic. Each topic must be a non-empty - string. Only used when the definition is published to the multiparty network - in: query - name: topics - schema: - items: - type: string - type: array - description: Server-side request timeout (milliseconds, or set a custom suffix like 10s) in: header @@ -13819,6 +13825,18 @@ paths: description: The published name of the API within the multiparty network type: string + topics: + description: An optional list of custom topics to use for ordering + of the definition broadcast message, instead of the default system + topic. Each topic must be a non-empty string. Only used when the + definition is published to the multiparty network + items: + description: An optional list of custom topics to use for ordering + of the definition broadcast message, instead of the default + system topic. Each topic must be a non-empty string. Only used + when the definition is published to the multiparty network + type: string + type: array type: object required: true responses: @@ -15664,15 +15682,6 @@ paths: name: confirm schema: type: string - - description: Custom topics to use for ordering of the definition broadcast - message, instead of the default system topic. Each topic must be a non-empty - string. Only used when the definition is published to the multiparty network - in: query - name: topics - schema: - items: - type: string - type: array - description: Server-side request timeout (milliseconds, or set a custom suffix like 10s) in: header @@ -15689,6 +15698,18 @@ paths: description: An optional name to be used for publishing this definition to the multiparty network, which may differ from the local name type: string + topics: + description: An optional list of custom topics to use for ordering + of the definition broadcast message, instead of the default system + topic. Each topic must be a non-empty string. Only used when the + definition is published to the multiparty network + items: + description: An optional list of custom topics to use for ordering + of the definition broadcast message, instead of the default + system topic. Each topic must be a non-empty string. Only used + when the definition is published to the multiparty network + type: string + type: array type: object required: true responses: @@ -16564,15 +16585,6 @@ paths: schema: example: "true" type: string - - description: Custom topics to use for ordering of the definition broadcast - message, instead of the default system topic. Each topic must be a non-empty - string. Only used when the definition is published to the multiparty network - in: query - name: topics - schema: - items: - type: string - type: array - description: Server-side request timeout (milliseconds, or set a custom suffix like 10s) in: header @@ -16617,6 +16629,18 @@ paths: description: The published name of the API within the multiparty network type: string + topics: + description: An optional list of custom topics to use for ordering + of the definition broadcast message, instead of the default system + topic. Each topic must be a non-empty string. Only used when the + definition is published to the multiparty network + items: + description: An optional list of custom topics to use for ordering + of the definition broadcast message, instead of the default + system topic. Each topic must be a non-empty string. Only used + when the definition is published to the multiparty network + type: string + type: array type: object required: true responses: @@ -18053,15 +18077,6 @@ paths: name: publish schema: type: string - - description: Custom topics to use for ordering of the definition broadcast - message, instead of the default system topic. Each topic must be a non-empty - string. Only used when the definition is published to the multiparty network - in: query - name: topics - schema: - items: - type: string - type: array - description: Server-side request timeout (milliseconds, or set a custom suffix like 10s) in: header @@ -18226,6 +18241,18 @@ paths: description: The published name of the FFI within the multiparty network type: string + topics: + description: An optional list of custom topics to use for ordering + of the definition broadcast message, instead of the default system + topic. Each topic must be a non-empty string. Only used when the + definition is published to the multiparty network + items: + description: An optional list of custom topics to use for ordering + of the definition broadcast message, instead of the default + system topic. Each topic must be a non-empty string. Only used + when the definition is published to the multiparty network + type: string + type: array version: description: A version for the FFI - use of semantic versioning such as 'v1.0.1' is encouraged @@ -19118,15 +19145,6 @@ paths: name: confirm schema: type: string - - description: Custom topics to use for ordering of the definition broadcast - message, instead of the default system topic. Each topic must be a non-empty - string. Only used when the definition is published to the multiparty network - in: query - name: topics - schema: - items: - type: string - type: array - description: Server-side request timeout (milliseconds, or set a custom suffix like 10s) in: header @@ -19143,6 +19161,18 @@ paths: description: An optional name to be used for publishing this definition to the multiparty network, which may differ from the local name type: string + topics: + description: An optional list of custom topics to use for ordering + of the definition broadcast message, instead of the default system + topic. Each topic must be a non-empty string. Only used when the + definition is published to the multiparty network + items: + description: An optional list of custom topics to use for ordering + of the definition broadcast message, instead of the default + system topic. Each topic must be a non-empty string. Only used + when the definition is published to the multiparty network + type: string + type: array type: object required: true responses: @@ -36102,6 +36132,18 @@ paths: description: An optional name to be used for publishing this definition to the multiparty network, which may differ from the local name type: string + topics: + description: An optional list of custom topics to use for ordering + of the definition broadcast message, instead of the default system + topic. Each topic must be a non-empty string. Only used when the + definition is published to the multiparty network + items: + description: An optional list of custom topics to use for ordering + of the definition broadcast message, instead of the default + system topic. Each topic must be a non-empty string. Only used + when the definition is published to the multiparty network + type: string + type: array type: object required: true responses: @@ -45652,6 +45694,18 @@ paths: description: An optional name to be used for publishing this definition to the multiparty network, which may differ from the local name type: string + topics: + description: An optional list of custom topics to use for ordering + of the definition broadcast message, instead of the default system + topic. Each topic must be a non-empty string. Only used when the + definition is published to the multiparty network + items: + description: An optional list of custom topics to use for ordering + of the definition broadcast message, instead of the default + system topic. Each topic must be a non-empty string. Only used + when the definition is published to the multiparty network + type: string + type: array type: object required: true responses: diff --git a/internal/apiserver/route_post_contract_api_publish.go b/internal/apiserver/route_post_contract_api_publish.go index 9714ff726c..4f8474cfeb 100644 --- a/internal/apiserver/route_post_contract_api_publish.go +++ b/internal/apiserver/route_post_contract_api_publish.go @@ -35,7 +35,6 @@ var postContractAPIPublish = &ffapi.Route{ }, QueryParams: []*ffapi.QueryParam{ {Name: "confirm", Description: coremsgs.APIConfirmMsgQueryParam, IsBool: true}, - {Name: "topics", Description: coremsgs.APICustomTopicsParam, IsArray: true}, }, Description: coremsgs.APIEndpointsPostContractAPIPublish, JSONInputValue: func() interface{} { return &core.DefinitionPublish{} }, @@ -46,7 +45,7 @@ var postContractAPIPublish = &ffapi.Route{ waitConfirm := strings.EqualFold(r.QP["confirm"], "true") r.SuccessStatus = syncRetcode(waitConfirm) input := r.Input.(*core.DefinitionPublish) - return cr.or.DefinitionSender().PublishContractAPI(cr.ctx, cr.apiBaseURL, r.PP["apiName"], input.NetworkName, waitConfirm, r.QAP["topics"]) + return cr.or.DefinitionSender().PublishContractAPI(cr.ctx, cr.apiBaseURL, r.PP["apiName"], input.NetworkName, waitConfirm, input.Topics) }, }, } diff --git a/internal/apiserver/route_post_contract_api_publish_test.go b/internal/apiserver/route_post_contract_api_publish_test.go index 58847a747f..20980e53b4 100644 --- a/internal/apiserver/route_post_contract_api_publish_test.go +++ b/internal/apiserver/route_post_contract_api_publish_test.go @@ -22,6 +22,7 @@ import ( "net/http/httptest" "testing" + "github.com/hyperledger-firefly/common/pkg/fftypes" "github.com/hyperledger-firefly/firefly/mocks/definitionsmocks" "github.com/hyperledger-firefly/firefly/pkg/core" "github.com/stretchr/testify/assert" @@ -52,10 +53,10 @@ func TestPostContractAPIPublishWithTopics(t *testing.T) { o.On("Authorize", mock.Anything, mock.Anything).Return(nil) mds := &definitionsmocks.Sender{} o.On("DefinitionSender").Return(mds) - input := core.DefinitionPublish{NetworkName: "banana-net"} + input := core.DefinitionPublish{NetworkName: "banana-net", Topics: fftypes.FFStringArray{"my-topic"}} var buf bytes.Buffer json.NewEncoder(&buf).Encode(&input) - req := httptest.NewRequest("POST", "/api/v1/namespaces/ns1/apis/banana/publish?topics=my-topic", &buf) + req := httptest.NewRequest("POST", "/api/v1/namespaces/ns1/apis/banana/publish", &buf) req.Header.Set("Content-Type", "application/json; charset=utf-8") res := httptest.NewRecorder() api := &core.ContractAPI{} diff --git a/internal/apiserver/route_post_contract_interface_publish.go b/internal/apiserver/route_post_contract_interface_publish.go index 1d63f403f8..5b0c27c40e 100644 --- a/internal/apiserver/route_post_contract_interface_publish.go +++ b/internal/apiserver/route_post_contract_interface_publish.go @@ -36,7 +36,6 @@ var postContractInterfacePublish = &ffapi.Route{ }, QueryParams: []*ffapi.QueryParam{ {Name: "confirm", Description: coremsgs.APIConfirmMsgQueryParam, IsBool: true}, - {Name: "topics", Description: coremsgs.APICustomTopicsParam, IsArray: true}, }, Description: coremsgs.APIEndpointsPostContractInterfacePublish, JSONInputValue: func() interface{} { return &core.DefinitionPublish{} }, @@ -47,7 +46,7 @@ var postContractInterfacePublish = &ffapi.Route{ waitConfirm := strings.EqualFold(r.QP["confirm"], "true") r.SuccessStatus = syncRetcode(waitConfirm) input := r.Input.(*core.DefinitionPublish) - return cr.or.DefinitionSender().PublishFFI(cr.ctx, r.PP["name"], r.PP["version"], input.NetworkName, waitConfirm, r.QAP["topics"]) + return cr.or.DefinitionSender().PublishFFI(cr.ctx, r.PP["name"], r.PP["version"], input.NetworkName, waitConfirm, input.Topics) }, }, } diff --git a/internal/apiserver/route_post_contract_interface_publish_test.go b/internal/apiserver/route_post_contract_interface_publish_test.go index ffbbd31a8a..d8918ae717 100644 --- a/internal/apiserver/route_post_contract_interface_publish_test.go +++ b/internal/apiserver/route_post_contract_interface_publish_test.go @@ -53,10 +53,10 @@ func TestPostContractInterfacePublishWithTopics(t *testing.T) { o.On("Authorize", mock.Anything, mock.Anything).Return(nil) mds := &definitionsmocks.Sender{} o.On("DefinitionSender").Return(mds) - input := core.TokenPool{} + input := core.DefinitionPublish{Topics: fftypes.FFStringArray{"my-topic"}} var buf bytes.Buffer json.NewEncoder(&buf).Encode(&input) - req := httptest.NewRequest("POST", "/api/v1/namespaces/ns1/contracts/interfaces/ffi1/1.0/publish?topics=my-topic", &buf) + req := httptest.NewRequest("POST", "/api/v1/namespaces/ns1/contracts/interfaces/ffi1/1.0/publish", &buf) req.Header.Set("Content-Type", "application/json; charset=utf-8") res := httptest.NewRecorder() ffi := &fftypes.FFI{} diff --git a/internal/apiserver/route_post_new_contract_api.go b/internal/apiserver/route_post_new_contract_api.go index fb40c6ce90..e57eb54b2d 100644 --- a/internal/apiserver/route_post_new_contract_api.go +++ b/internal/apiserver/route_post_new_contract_api.go @@ -34,10 +34,9 @@ var postNewContractAPI = &ffapi.Route{ QueryParams: []*ffapi.QueryParam{ {Name: "confirm", Description: coremsgs.APIConfirmMsgQueryParam, IsBool: true, Example: "true"}, {Name: "publish", Description: coremsgs.APIPublishQueryParam, IsBool: true}, - {Name: "topics", Description: coremsgs.APICustomTopicsParam, IsArray: true}, }, Description: coremsgs.APIEndpointsPostNewContractAPI, - JSONInputValue: func() interface{} { return &core.ContractAPI{} }, + JSONInputValue: func() interface{} { return &core.ContractAPIInput{} }, JSONOutputValue: func() interface{} { return &core.ContractAPI{} }, JSONOutputCodes: []int{http.StatusOK, http.StatusAccepted}, Extensions: &coreExtensions{ @@ -47,10 +46,11 @@ var postNewContractAPI = &ffapi.Route{ CoreJSONHandler: func(r *ffapi.APIRequest, cr *coreRequest) (output interface{}, err error) { waitConfirm := strings.EqualFold(r.QP["confirm"], "true") r.SuccessStatus = syncRetcode(waitConfirm) - api := r.Input.(*core.ContractAPI) + input := r.Input.(*core.ContractAPIInput) + api := &input.ContractAPI api.ID = nil api.Published = strings.EqualFold(r.QP["publish"], "true") - err = cr.or.DefinitionSender().DefineContractAPI(cr.ctx, cr.apiBaseURL, api, waitConfirm, r.QAP["topics"]) + err = cr.or.DefinitionSender().DefineContractAPI(cr.ctx, cr.apiBaseURL, api, waitConfirm, input.Topics) return api, err }, }, diff --git a/internal/apiserver/route_post_new_contract_api_test.go b/internal/apiserver/route_post_new_contract_api_test.go index efa9220092..0482b535c3 100644 --- a/internal/apiserver/route_post_new_contract_api_test.go +++ b/internal/apiserver/route_post_new_contract_api_test.go @@ -22,6 +22,7 @@ import ( "net/http/httptest" "testing" + "github.com/hyperledger-firefly/common/pkg/fftypes" "github.com/hyperledger-firefly/firefly/mocks/contractmocks" "github.com/hyperledger-firefly/firefly/mocks/definitionsmocks" "github.com/hyperledger-firefly/firefly/pkg/core" @@ -73,10 +74,10 @@ func TestPostNewContractAPIWithTopics(t *testing.T) { mds := &definitionsmocks.Sender{} o.On("Contracts").Return(&contractmocks.Manager{}) o.On("DefinitionSender").Return(mds) - input := core.Datatype{} + input := core.ContractAPIInput{Topics: fftypes.FFStringArray{"my-topic"}} var buf bytes.Buffer json.NewEncoder(&buf).Encode(&input) - req := httptest.NewRequest("POST", "/api/v1/namespaces/ns1/apis?topics=my-topic", &buf) + req := httptest.NewRequest("POST", "/api/v1/namespaces/ns1/apis", &buf) req.Header.Set("Content-Type", "application/json; charset=utf-8") res := httptest.NewRecorder() diff --git a/internal/apiserver/route_post_new_contract_interface.go b/internal/apiserver/route_post_new_contract_interface.go index c2172e00e8..9c3bdba806 100644 --- a/internal/apiserver/route_post_new_contract_interface.go +++ b/internal/apiserver/route_post_new_contract_interface.go @@ -24,6 +24,7 @@ import ( "github.com/hyperledger-firefly/common/pkg/fftypes" "github.com/hyperledger-firefly/firefly/internal/coremsgs" "github.com/hyperledger-firefly/firefly/internal/orchestrator" + "github.com/hyperledger-firefly/firefly/pkg/core" ) var postNewContractInterface = &ffapi.Route{ @@ -34,10 +35,9 @@ var postNewContractInterface = &ffapi.Route{ QueryParams: []*ffapi.QueryParam{ {Name: "confirm", Description: coremsgs.APIConfirmMsgQueryParam, IsBool: true, Example: "true"}, {Name: "publish", Description: coremsgs.APIPublishQueryParam, IsBool: true}, - {Name: "topics", Description: coremsgs.APICustomTopicsParam, IsArray: true}, }, Description: coremsgs.APIEndpointsPostNewContractInterface, - JSONInputValue: func() interface{} { return &fftypes.FFI{} }, + JSONInputValue: func() interface{} { return &core.FFIInput{} }, JSONOutputValue: func() interface{} { return &fftypes.FFI{} }, JSONOutputCodes: []int{http.StatusOK}, Extensions: &coreExtensions{ @@ -47,9 +47,10 @@ var postNewContractInterface = &ffapi.Route{ CoreJSONHandler: func(r *ffapi.APIRequest, cr *coreRequest) (output interface{}, err error) { waitConfirm := strings.EqualFold(r.QP["confirm"], "true") r.SuccessStatus = syncRetcode(waitConfirm) - ffi := r.Input.(*fftypes.FFI) + input := r.Input.(*core.FFIInput) + ffi := &input.FFI ffi.Published = strings.EqualFold(r.QP["publish"], "true") - err = cr.or.DefinitionSender().DefineFFI(cr.ctx, ffi, waitConfirm, r.QAP["topics"]) + err = cr.or.DefinitionSender().DefineFFI(cr.ctx, ffi, waitConfirm, input.Topics) return ffi, err }, }, diff --git a/internal/apiserver/route_post_new_contract_interface_test.go b/internal/apiserver/route_post_new_contract_interface_test.go index 86463ab07d..48bcebeb60 100644 --- a/internal/apiserver/route_post_new_contract_interface_test.go +++ b/internal/apiserver/route_post_new_contract_interface_test.go @@ -22,6 +22,7 @@ import ( "net/http/httptest" "testing" + "github.com/hyperledger-firefly/common/pkg/fftypes" "github.com/hyperledger-firefly/firefly/mocks/contractmocks" "github.com/hyperledger-firefly/firefly/mocks/definitionsmocks" "github.com/hyperledger-firefly/firefly/pkg/core" @@ -73,10 +74,10 @@ func TestPostNewContractInterfaceWithTopics(t *testing.T) { mds := &definitionsmocks.Sender{} o.On("Contracts").Return(&contractmocks.Manager{}) o.On("DefinitionSender").Return(mds) - input := core.Datatype{} + input := core.FFIInput{Topics: fftypes.FFStringArray{"my-topic"}} var buf bytes.Buffer json.NewEncoder(&buf).Encode(&input) - req := httptest.NewRequest("POST", "/api/v1/namespaces/ns1/contracts/interfaces?topics=my-topic", &buf) + req := httptest.NewRequest("POST", "/api/v1/namespaces/ns1/contracts/interfaces", &buf) req.Header.Set("Content-Type", "application/json; charset=utf-8") res := httptest.NewRecorder() diff --git a/internal/apiserver/route_put_contract_api.go b/internal/apiserver/route_put_contract_api.go index a04521faff..9a516eaa1f 100644 --- a/internal/apiserver/route_put_contract_api.go +++ b/internal/apiserver/route_put_contract_api.go @@ -36,10 +36,9 @@ var putContractAPI = &ffapi.Route{ }, QueryParams: []*ffapi.QueryParam{ {Name: "confirm", Description: coremsgs.APIConfirmMsgQueryParam, IsBool: true, Example: "true"}, - {Name: "topics", Description: coremsgs.APICustomTopicsParam, IsArray: true}, }, Description: coremsgs.APIParamsContractAPIID, - JSONInputValue: func() interface{} { return &core.ContractAPI{} }, + JSONInputValue: func() interface{} { return &core.ContractAPIInput{} }, JSONOutputValue: func() interface{} { return &core.ContractAPI{} }, JSONOutputCodes: []int{http.StatusOK, http.StatusAccepted}, Extensions: &coreExtensions{ @@ -49,10 +48,11 @@ var putContractAPI = &ffapi.Route{ CoreJSONHandler: func(r *ffapi.APIRequest, cr *coreRequest) (output interface{}, err error) { waitConfirm := strings.EqualFold(r.QP["confirm"], "true") r.SuccessStatus = syncRetcode(waitConfirm) - api := r.Input.(*core.ContractAPI) + input := r.Input.(*core.ContractAPIInput) + api := &input.ContractAPI api.ID, err = fftypes.ParseUUID(cr.ctx, r.PP["id"]) if err == nil { - err = cr.or.DefinitionSender().DefineContractAPI(cr.ctx, cr.apiBaseURL, api, waitConfirm, r.QAP["topics"]) + err = cr.or.DefinitionSender().DefineContractAPI(cr.ctx, cr.apiBaseURL, api, waitConfirm, input.Topics) } return api, err }, diff --git a/internal/apiserver/route_put_contract_api_test.go b/internal/apiserver/route_put_contract_api_test.go index 1a48149c2c..2f86352d2c 100644 --- a/internal/apiserver/route_put_contract_api_test.go +++ b/internal/apiserver/route_put_contract_api_test.go @@ -22,6 +22,7 @@ import ( "net/http/httptest" "testing" + "github.com/hyperledger-firefly/common/pkg/fftypes" "github.com/hyperledger-firefly/firefly/mocks/contractmocks" "github.com/hyperledger-firefly/firefly/mocks/definitionsmocks" "github.com/hyperledger-firefly/firefly/pkg/core" @@ -73,10 +74,10 @@ func TestPutContractAPIWithTopics(t *testing.T) { mds := &definitionsmocks.Sender{} o.On("DefinitionSender").Return(mds) o.On("Contracts").Return(&contractmocks.Manager{}) - input := core.Datatype{} + input := core.ContractAPIInput{Topics: fftypes.FFStringArray{"my-topic"}} var buf bytes.Buffer json.NewEncoder(&buf).Encode(&input) - req := httptest.NewRequest("PUT", "/api/v1/namespaces/ns1/apis/99EEE458-037C-4C78-B66B-31E52F93D2E9?topics=my-topic", &buf) + req := httptest.NewRequest("PUT", "/api/v1/namespaces/ns1/apis/99EEE458-037C-4C78-B66B-31E52F93D2E9", &buf) req.Header.Set("Content-Type", "application/json; charset=utf-8") res := httptest.NewRecorder() diff --git a/internal/coremsgs/en_api_translations.go b/internal/coremsgs/en_api_translations.go index fb99ef52e5..e7c4aa76e1 100644 --- a/internal/coremsgs/en_api_translations.go +++ b/internal/coremsgs/en_api_translations.go @@ -216,6 +216,4 @@ var ( APISubscriptionStartSequenceID = ffm("api.startsequenceid", "The sequence ID in the raw event stream to start indexing through events from. Leave blank to start indexing from the most recent events") APISubscriptionEndSequenceID = ffm("api.endsequenceid", "The sequence ID in the raw event stream to stop indexing through events at. Leave blank to start indexing from the most recent events") - - APICustomTopicsParam = ffm("api.customTopicsParam", "Custom topics to use for ordering of the definition broadcast message, instead of the default system topic. Each topic must be a non-empty string. Only used when the definition is published to the multiparty network") ) diff --git a/internal/coremsgs/en_struct_descriptions.go b/internal/coremsgs/en_struct_descriptions.go index 8411e61145..d6486ff6b1 100644 --- a/internal/coremsgs/en_struct_descriptions.go +++ b/internal/coremsgs/en_struct_descriptions.go @@ -739,4 +739,11 @@ var ( // DefinitionPublish field descriptions DefinitionPublishNetworkName = ffm("DefinitionPublish.networkName", "An optional name to be used for publishing this definition to the multiparty network, which may differ from the local name") + DefinitionPublishTopics = ffm("DefinitionPublish.topics", "An optional list of custom topics to use for ordering of the definition broadcast message, instead of the default system topic. Each topic must be a non-empty string. Only used when the definition is published to the multiparty network") + + // ContractAPIInput field descriptions + ContractAPIInputTopics = ffm("ContractAPIInput.topics", "An optional list of custom topics to use for ordering of the definition broadcast message, instead of the default system topic. Each topic must be a non-empty string. Only used when the definition is published to the multiparty network") + + // FFIInput field descriptions + FFIInputTopics = ffm("FFIInput.topics", "An optional list of custom topics to use for ordering of the definition broadcast message, instead of the default system topic. Each topic must be a non-empty string. Only used when the definition is published to the multiparty network") ) diff --git a/pkg/core/contracts.go b/pkg/core/contracts.go index c164fdb50b..bc57f24c28 100644 --- a/pkg/core/contracts.go +++ b/pkg/core/contracts.go @@ -72,6 +72,16 @@ type ContractAPI struct { Published bool `ffstruct:"ContractAPI" json:"published" ffexcludeinput:"true"` } +type ContractAPIInput struct { + ContractAPI + Topics fftypes.FFStringArray `ffstruct:"ContractAPIInput" json:"topics,omitempty" ffexcludeoutput:"true"` +} + +type FFIInput struct { + fftypes.FFI + Topics fftypes.FFStringArray `ffstruct:"FFIInput" json:"topics,omitempty" ffexcludeoutput:"true"` +} + func (c *ContractAPI) Validate(ctx context.Context) (err error) { if err = fftypes.ValidateFFNameField(ctx, c.Namespace, "namespace"); err != nil { return err diff --git a/pkg/core/definition.go b/pkg/core/definition.go index 72d618a491..7f2d98ec60 100644 --- a/pkg/core/definition.go +++ b/pkg/core/definition.go @@ -27,5 +27,6 @@ type Definition interface { } type DefinitionPublish struct { - NetworkName string `ffstruct:"DefinitionPublish" json:"networkName,omitempty"` + NetworkName string `ffstruct:"DefinitionPublish" json:"networkName,omitempty"` + Topics fftypes.FFStringArray `ffstruct:"DefinitionPublish" json:"topics,omitempty"` }