Skip to content
This repository has been archived by the owner on Oct 3, 2022. It is now read-only.

Added property bag to endpoints for custom metadata #51

Merged
merged 3 commits into from
Mar 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func (apiResource ApiWrapper) RegisterEndpoint(ctx echo.Context, id string) erro
if err = ep.validate(); err != nil {
return ctx.String(http.StatusBadRequest, err.Error())
}
event, err := apiResource.R.RegisterEndpoint(unescapedID, ep.Identifier.String(), ep.URL, ep.EndpointType, ep.Status, ep.Version)
event, err := apiResource.R.RegisterEndpoint(unescapedID, ep.Identifier.String(), ep.URL, ep.EndpointType, ep.Status, ep.Version, fromEndpointProperties(ep.Properties))
if err != nil {
return ctx.String(http.StatusInternalServerError, err.Error())
}
Expand Down Expand Up @@ -145,7 +145,7 @@ func (apiResource ApiWrapper) EndpointsByOrganisationId(ctx echo.Context, params
if err != nil {
logrus.Warning(err.Error())
} else {
dupEndpoints = append(endpointsArrayFromDb(dbEndpoints), dupEndpoints...)
dupEndpoints = append(endpointsFromDb(dbEndpoints), dupEndpoints...)
}

if strict != nil && *strict && len(dbEndpoints) == 0 {
Expand Down
5 changes: 4 additions & 1 deletion api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -720,12 +720,15 @@ func TestApiResource_RegisterEndpoint(t *testing.T) {
t.Run("204", func(t *testing.T) {
var registryClient = mock.NewMockRegistryClient(mockCtrl)
e, wrapper := initMockEcho(registryClient)
registryClient.EXPECT().RegisterEndpoint("1", "abc", "foo:bar", "fhir", "", "")
registryClient.EXPECT().RegisterEndpoint("1", "abc", "foo:bar", "fhir", "", "", map[string]string{"key": "value"})

props := EndpointProperties{}
props["key"] = "value"
b, _ := json.Marshal(Endpoint{
Identifier: "abc",
URL: "foo:bar",
EndpointType: "fhir",
Properties: &props,
})

req := httptest.NewRequest(echo.POST, "/", bytes.NewReader(b))
Expand Down
7 changes: 4 additions & 3 deletions api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func (hb HttpClient) EndpointsByOrganizationAndType(legalEntity string, endpoint
return nil, err
}

return endpointsArrayToDb(endpoints), nil
return endpointsToDb(endpoints), nil
}

// SearchOrganizations is the client Api implementation for finding organizations by (partial) query
Expand Down Expand Up @@ -156,11 +156,11 @@ func (hb HttpClient) searchOrganization(params SearchOrganizationsParams) ([]db.
}
}

return organizationsToFromDb(organizations), nil
return organizationsToDb(organizations), nil
}

// RegisterEndpoint is the client Api implementation for registering an endpoint for an organisation.
func (hb HttpClient) RegisterEndpoint(organizationID string, id string, url string, endpointType string, status string, version string) (events.Event, error) {
func (hb HttpClient) RegisterEndpoint(organizationID string, id string, url string, endpointType string, status string, version string, properties map[string]string) (events.Event, error) {
ctx, cancel := context.WithTimeout(context.Background(), hb.Timeout)
defer cancel()
res, err := hb.client().RegisterEndpoint(ctx, organizationID, RegisterEndpointJSONRequestBody{
Expand All @@ -169,6 +169,7 @@ func (hb HttpClient) RegisterEndpoint(organizationID string, id string, url stri
Identifier: Identifier(id),
Status: status,
Version: version,
Properties: toEndpointProperties(properties),
})
if err != nil {
return nil, err
Expand Down
4 changes: 2 additions & 2 deletions api/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ func TestHttpClient_RegisterEndpoint(t *testing.T) {
s := httptest.NewServer(handler{statusCode: http.StatusOK, responseData: event.Marshal()})
c := HttpClient{ServerAddress: s.URL, Timeout: time.Second}

event, err := c.RegisterEndpoint("orgId", "id", "url", "type", "status", "version")
event, err := c.RegisterEndpoint("orgId", "id", "url", "type", "status", "version", map[string]string{"foo": "bar"})
if !assert.NoError(t, err) {
return
}
Expand All @@ -184,7 +184,7 @@ func TestHttpClient_RegisterEndpoint(t *testing.T) {
s := httptest.NewServer(handler{statusCode: http.StatusInternalServerError, responseData: []byte{}})
c := HttpClient{ServerAddress: s.URL, Timeout: time.Second}

event, err := c.RegisterEndpoint("orgId", "id", "url", "type", "status", "version")
event, err := c.RegisterEndpoint("orgId", "id", "url", "type", "status", "version", nil)
assert.EqualError(t, err, "registry returned HTTP 500 (expected: 200), response: ", "error")
assert.Nil(t, event)
})
Expand Down
47 changes: 35 additions & 12 deletions api/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,23 @@

package api

import "github.com/nuts-foundation/nuts-registry/pkg/db"
import (
"fmt"
"github.com/nuts-foundation/nuts-registry/pkg/db"
)

func (e Endpoint) fromDb(db db.Endpoint) Endpoint {
e.URL = db.URL
e.EndpointType = db.EndpointType
e.Identifier = Identifier(db.Identifier)
e.Status = db.Status
e.Version = db.Version
e.Properties = toEndpointProperties(db.Properties)
return e
}

func (o Organization) fromDb(db db.Organization) Organization {
e := endpointsArrayFromDb(db.Endpoints)
e := endpointsFromDb(db.Endpoints)
o.Identifier = Identifier(db.Identifier)
o.Name = db.Name
o.PublicKey = db.PublicKey
Expand Down Expand Up @@ -64,22 +68,41 @@ func (o Organization) toDb() db.Organization {
}

if o.Endpoints != nil {
org.Endpoints = endpointsArrayToDb(*o.Endpoints)
org.Endpoints = endpointsToDb(*o.Endpoints)
}

return org
}

func (a Endpoint) toDb() db.Endpoint {
func (e Endpoint) toDb() db.Endpoint {
return db.Endpoint{
URL: a.URL,
EndpointType: a.EndpointType,
Identifier: db.Identifier(a.Identifier),
Status: a.Status,
Version: a.Version,
URL: e.URL,
EndpointType: e.EndpointType,
Identifier: db.Identifier(e.Identifier),
Status: e.Status,
Version: e.Version,
Properties: fromEndpointProperties(e.Properties),
}
}

func fromEndpointProperties(endpointProperties *EndpointProperties) map[string]string {
props := make(map[string]string, 0)
if endpointProperties != nil {
for key, value := range *endpointProperties {
props[key] = fmt.Sprintf("%s", value)
}
}
return props
}

func toEndpointProperties(properties map[string]string) *EndpointProperties {
props := EndpointProperties{}
for key, value := range properties {
props[key] = value
}
return &props
}

func jwkToMap(jwk []JWK) []interface{} {
em := make([]interface{}, len(jwk))
for i, k := range jwk {
Expand All @@ -88,23 +111,23 @@ func jwkToMap(jwk []JWK) []interface{} {
return em
}

func endpointsArrayFromDb(endpointsIn []db.Endpoint) []Endpoint {
func endpointsFromDb(endpointsIn []db.Endpoint) []Endpoint {
es := make([]Endpoint, len(endpointsIn))
for i, a := range endpointsIn {
es[i] = Endpoint{}.fromDb(a)
}
return es
}

func organizationsToFromDb(organizationsIn []Organization) []db.Organization {
func organizationsToDb(organizationsIn []Organization) []db.Organization {
os := make([]db.Organization, len(organizationsIn))
for i, a := range organizationsIn {
os[i] = a.toDb()
}
return os
}

func endpointsArrayToDb(endpointsIn []Endpoint) []db.Endpoint {
func endpointsToDb(endpointsIn []Endpoint) []db.Endpoint {
es := make([]db.Endpoint, len(endpointsIn))
for i, a := range endpointsIn {
es[i] = a.toDb()
Expand Down
9 changes: 9 additions & 0 deletions api/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions docs/_static/nuts-registry.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,8 @@ components:
type: string
description: location of the actual en endpoint on the internet
example: tcp://127.0.0.1:1234, https://nuts.nl/endpoint
properties:
$ref: "#/components/schemas/EndpointProperties"
RegisterVendorEvent:
required:
- identifier
Expand Down Expand Up @@ -362,6 +364,8 @@ components:
type: string
description: location of the actual en endpoint on the internet
example: tcp://127.0.0.1:1234, https://nuts.nl/endpoint
properties:
$ref: "#/components/schemas/EndpointProperties"
Identifier:
type: string
description: >
Expand All @@ -373,6 +377,11 @@ components:
enum: [healthcare, personal, insurance]
description: Domain the entity operates in.
example: health
EndpointProperties:
type: object
additionalProperties:
type: string
description: A property bag, containing extra properties for endpoints
JWK:
description: as described by https://tools.ietf.org/html/rfc7517. Modelled as object so libraries can parse the tokens themselves.
additionalProperties: {}
Expand Down
8 changes: 7 additions & 1 deletion docs/pages/administration/registry.rst
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,10 @@ In the following example we register a Corda consent endpoint for the previously
"urn:ietf:rfc:1779:O=BecauseWeCare B.V.,C=NL,L=Almere,CN=Kunstgebit Thuiszorg" \
urn:nuts:endpoint:consent \
"tcp://1.2.3.4:4321" \
1.0.0
1.0.0

Endpoints can have extra metadata in the form of string properties. Property are specified as **key=value** using the **-p** flag:

.. code-block:: shell

NUTS_MODE=cli ./nuts ...(etc) -p key1=Hello -p key2=World
54 changes: 37 additions & 17 deletions engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/spf13/pflag"
"os"
"os/signal"
"strings"
)

// registryClientCreator is a variable to aid testability
Expand Down Expand Up @@ -162,27 +163,46 @@ func cmd() *cobra.Command {
},
})

cmd.AddCommand(&cobra.Command{
Use: "register-endpoint [org-identifier] [identifier] [type] [url] [version]",
Short: "Registers an endpoint",
Long: "Registers an endpoint for an organization.",
Args: cobra.ExactArgs(5),
RunE: func(cmd *cobra.Command, args []string) error {
cl := registryClientCreator()
event, err := cl.RegisterEndpoint(args[0], args[1], args[3], args[2], db.StatusActive, args[4])
if err != nil {
logrus.Errorf("Unable to register endpoint: %v", err)
return err
}
logrus.Info("Endpoint registered.")
logEventToConsole(event)
return nil
},
})
{
var properties *[]string
command := &cobra.Command{
Use: "register-endpoint [org-identifier] [identifier] [type] [url] [version]",
Short: "Registers an endpoint",
Long: "Registers an endpoint for an organization.",
Args: cobra.ExactArgs(5),
RunE: func(cmd *cobra.Command, args []string) error {
cl := registryClientCreator()
event, err := cl.RegisterEndpoint(args[0], args[1], args[3], args[2], db.StatusActive, args[4], parseCLIProperties(*properties))
if err != nil {
logrus.Errorf("Unable to register endpoint: %v", err)
return err
}
logrus.Info("Endpoint registered.")
logEventToConsole(event)
return nil
},
}
flagSet := pflag.NewFlagSet("register-endpoint", pflag.ContinueOnError)
properties = flagSet.StringArrayP("properties", "p", nil, "extra properties for the endpoint, in the format: key=value")
command.Flags().AddFlagSet(flagSet)
cmd.AddCommand(command)
}

return cmd
}

// parseCLIProperties parses a slice of key-value entries (key=value) to a map.
func parseCLIProperties(keysAndValues []string) map[string]string {
result := make(map[string]string, 0)
for _, keyAndValue := range keysAndValues {
parts := strings.SplitN(keyAndValue, "=", 2)
if len(parts) == 2 {
result[parts[0]] = parts[1]
}
}
return result
}

func logEventToConsole(event events.Event) {
println("Event:", events.SuggestEventFileName(event))
println(string(event.Marshal()))
Expand Down
6 changes: 3 additions & 3 deletions engine/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@ func TestRegisterEndpoint(t *testing.T) {
command := cmd()
t.Run("ok", withMock(func(t *testing.T, client *mock.MockRegistryClient) {
event := events.CreateEvent(events.RegisterEndpoint, events.RegisterEndpointEvent{})
client.EXPECT().RegisterEndpoint("orgId", "id", "url", "type", db.StatusActive, "version").Return(event, nil)
command.SetArgs([]string{"register-endpoint", "orgId", "id", "type", "url", "version"})
client.EXPECT().RegisterEndpoint("orgId", "id", "url", "type", db.StatusActive, "version", map[string]string{"k1": "v1", "k2": "v2"}).Return(event, nil)
command.SetArgs([]string{"register-endpoint", "orgId", "id", "type", "url", "version", "-p", "k1=v1", "-p", "k2=v2"})
err := command.Execute()
assert.NoError(t, err)
}))
t.Run("error", withMock(func(t *testing.T, client *mock.MockRegistryClient) {
client.EXPECT().RegisterEndpoint(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New("failed"))
client.EXPECT().RegisterEndpoint(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New("failed"))
command.SetArgs([]string{"register-endpoint", "orgId", "id", "type", "url", "version"})
command.Execute()
}))
Expand Down
8 changes: 4 additions & 4 deletions mock/mock_client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pkg/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func (r *Registry) VendorClaim(vendorID string, orgID string, orgName string, or
}

// RegisterEndpoint registers an endpoint for an organization
func (r *Registry) RegisterEndpoint(organizationID string, id string, url string, endpointType string, status string, version string) (events.Event, error) {
func (r *Registry) RegisterEndpoint(organizationID string, id string, url string, endpointType string, status string, version string, properties map[string]string) (events.Event, error) {
logrus.Infof("Registering endpoint, organization=%s, id=%s, type=%s, url=%s, status=%s, version=%s",
organizationID, id, endpointType, url, status, version)
return r.publishEvent(events.RegisterEndpoint, events.RegisterEndpointEvent{
Expand All @@ -109,6 +109,7 @@ func (r *Registry) RegisterEndpoint(organizationID string, id string, url string
Identifier: events.Identifier(id),
Status: status,
Version: version,
Properties: properties,
})
}

Expand Down