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

Commit

Permalink
Merge pull request #51 from nuts-foundation/endpoint-properties
Browse files Browse the repository at this point in the history
Added property bag to endpoints for custom metadata
  • Loading branch information
reinkrul committed Mar 2, 2020
2 parents b0a75ed + fcb8664 commit c8f10e1
Show file tree
Hide file tree
Showing 17 changed files with 136 additions and 62 deletions.
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
Loading

0 comments on commit c8f10e1

Please sign in to comment.