156 lines
3.4 KiB
Go
156 lines
3.4 KiB
Go
|
|
package did
|
||
|
|
|
||
|
|
import (
|
||
|
|
"encoding/json"
|
||
|
|
"fmt"
|
||
|
|
)
|
||
|
|
|
||
|
|
// Specification: https://www.w3.org/TR/cid-1.0/#services
|
||
|
|
// List of service types and their fields: https://www.w3.org/TR/did-extensions-properties/#service-types
|
||
|
|
|
||
|
|
// Services is a collection of Service.
|
||
|
|
type Services []Service
|
||
|
|
|
||
|
|
// ServiceById retrieves a Service from the Services slice by its id.
|
||
|
|
// Returns the Service and true if found, otherwise returns an empty Service and false.
|
||
|
|
func (ss Services) ServiceById(id string) (Service, bool) {
|
||
|
|
for _, s := range ss {
|
||
|
|
if s.Id == id {
|
||
|
|
return s, true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return Service{}, false
|
||
|
|
}
|
||
|
|
|
||
|
|
// ServiceByType returns zero or one Service matching the given type.
|
||
|
|
// If there is more than one service for that type, the first match is returned.
|
||
|
|
func (ss Services) ServiceByType(_type string) (Service, bool) {
|
||
|
|
for _, s := range ss {
|
||
|
|
if s.HasType(_type) {
|
||
|
|
return s, true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return Service{}, false
|
||
|
|
}
|
||
|
|
|
||
|
|
// Service is a means of communicating or interacting with the DID subject or associated entities
|
||
|
|
// via one or more service endpoints.
|
||
|
|
// It can have one or more types.
|
||
|
|
type Service struct {
|
||
|
|
Id string
|
||
|
|
Types []string
|
||
|
|
Endpoints []any // either strEndpoint or mapEndpoint
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s Service) HasType(_type string) bool {
|
||
|
|
for _, t := range s.Types {
|
||
|
|
if t == _type {
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s Service) MarshalJSON() ([]byte, error) {
|
||
|
|
var aux struct {
|
||
|
|
Id string `json:"id"`
|
||
|
|
Type any `json:"type"`
|
||
|
|
Endpoint any `json:"serviceEndpoint"`
|
||
|
|
}
|
||
|
|
|
||
|
|
aux.Id = s.Id
|
||
|
|
|
||
|
|
switch len(s.Types) {
|
||
|
|
case 0:
|
||
|
|
return nil, fmt.Errorf("service type is required")
|
||
|
|
case 1:
|
||
|
|
aux.Type = s.Types[0]
|
||
|
|
default:
|
||
|
|
aux.Type = s.Types
|
||
|
|
}
|
||
|
|
|
||
|
|
switch len(s.Endpoints) {
|
||
|
|
case 0:
|
||
|
|
return nil, fmt.Errorf("service endpoint is required")
|
||
|
|
case 1:
|
||
|
|
aux.Endpoint = s.Endpoints[0]
|
||
|
|
default:
|
||
|
|
aux.Endpoint = s.Endpoints
|
||
|
|
}
|
||
|
|
|
||
|
|
return json.Marshal(aux)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *Service) UnmarshalJSON(bytes []byte) error {
|
||
|
|
var aux struct {
|
||
|
|
Id string `json:"id"`
|
||
|
|
Type json.RawMessage `json:"type"`
|
||
|
|
Endpoint json.RawMessage `json:"serviceEndpoint"`
|
||
|
|
}
|
||
|
|
|
||
|
|
err := json.Unmarshal(bytes, &aux)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
if len(aux.Id) == 0 {
|
||
|
|
return fmt.Errorf("service id is required")
|
||
|
|
}
|
||
|
|
s.Id = aux.Id
|
||
|
|
|
||
|
|
s.Types, err = unmarshalSingleOrArray[string](aux.Type)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if len(s.Types) == 0 {
|
||
|
|
return fmt.Errorf("service type is required")
|
||
|
|
}
|
||
|
|
for _, _type := range s.Types {
|
||
|
|
if len(_type) == 0 {
|
||
|
|
return fmt.Errorf("invalid service type: must not be empty string")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
s.Endpoints, err = unmarshalSingleOrArray[any](aux.Endpoint)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if len(s.Endpoints) == 0 {
|
||
|
|
return fmt.Errorf("service endpoint is required")
|
||
|
|
}
|
||
|
|
for i, endpoint := range s.Endpoints {
|
||
|
|
switch endpoint := endpoint.(type) {
|
||
|
|
case string:
|
||
|
|
s.Endpoints[i] = StrEndpoint(endpoint)
|
||
|
|
case map[string]any:
|
||
|
|
s.Endpoints[i] = MapEndpoint(endpoint)
|
||
|
|
default:
|
||
|
|
return fmt.Errorf("endpoint must be %T or %T", StrEndpoint(""), MapEndpoint{})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
type StrEndpoint string
|
||
|
|
|
||
|
|
type MapEndpoint map[string]any
|
||
|
|
|
||
|
|
func unmarshalSingleOrArray[T any](data json.RawMessage) ([]T, error) {
|
||
|
|
if data == nil {
|
||
|
|
return nil, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
var single T
|
||
|
|
if err := json.Unmarshal(data, &single); err == nil {
|
||
|
|
return []T{single}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
var array []T
|
||
|
|
if err := json.Unmarshal(data, &array); err == nil {
|
||
|
|
return array, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil, fmt.Errorf("must be %T or array of %T", single, single)
|
||
|
|
}
|