diff --git a/app/gql/resolver.go b/app/gql/resolver.go index 690d32f..aaa7a84 100644 --- a/app/gql/resolver.go +++ b/app/gql/resolver.go @@ -45,13 +45,13 @@ func (*noop) IsResolver() {} func (*noop) CreateSaltyUser(ctx context.Context, nick string, pubkey string) (*salty.SaltyUser, error) { panic("not implemented") } -func (*noop) Posts(ctx context.Context, streamID string, paging *gql.PageInput) (*gql.Connection, error) { +func (*noop) Posts(ctx context.Context, name, tag string, paging *gql.PageInput) (*gql.Connection, error) { panic("not implemented") } func (*noop) SaltyUser(ctx context.Context, nick string) (*salty.SaltyUser, error) { panic("not implemented") } -func (*noop) PostAdded(ctx context.Context, streamID string, after int64) (<-chan *msgbus.PostEvent, error) { +func (*noop) PostAdded(ctx context.Context, name, tag string, after int64) (<-chan *msgbus.PostEvent, error) { panic("not implemented") } func (*noop) Events(ctx context.Context, streamID string, paging *gql.PageInput) (*gql.Connection, error) { diff --git a/app/msgbus/msgbus.graphqls b/app/msgbus/msgbus.graphqls index 97dae1e..7dae993 100644 --- a/app/msgbus/msgbus.graphqls +++ b/app/msgbus/msgbus.graphqls @@ -1,9 +1,9 @@ extend type Query { - posts(streamID: String! paging: PageInput): Connection! + posts(name: String!, tag: String! = "", paging: PageInput): Connection! } extend type Subscription { """after == 0 start from begining, after == -1 start from end""" - postAdded(streamID: String! after: Int! = -1): PostEvent + postAdded(name: String!, tag: String! = "", after: Int! = -1): PostEvent } type PostEvent implements Edge @goModel(model: "go.sour.is/ev/app/msgbus.PostEvent") { id: ID! diff --git a/app/msgbus/service.go b/app/msgbus/service.go index 8d48573..8303222 100644 --- a/app/msgbus/service.go +++ b/app/msgbus/service.go @@ -3,8 +3,10 @@ package msgbus import ( "bytes" "context" + "encoding/base64" "encoding/json" "fmt" + "hash/fnv" "io" "net/http" "strconv" @@ -33,8 +35,8 @@ type service struct { } type MsgbusResolver interface { - Posts(ctx context.Context, streamID string, paging *gql.PageInput) (*gql.Connection, error) - PostAdded(ctx context.Context, streamID string, after int64) (<-chan *PostEvent, error) + Posts(ctx context.Context, name, tag string, paging *gql.PageInput) (*gql.Connection, error) + PostAdded(ctx context.Context, name, tag string, after int64) (<-chan *PostEvent, error) IsResolver() } @@ -115,7 +117,7 @@ func (s *service) ServeHTTP(w http.ResponseWriter, r *http.Request) { } // Posts is the resolver for the events field. -func (s *service) Posts(ctx context.Context, streamID string, paging *gql.PageInput) (*gql.Connection, error) { +func (s *service) Posts(ctx context.Context, name, tag string, paging *gql.PageInput) (*gql.Connection, error) { ctx, span := lg.Span(ctx) defer span.End() @@ -124,6 +126,7 @@ func (s *service) Posts(ctx context.Context, streamID string, paging *gql.PageIn start := time.Now() defer s.m_req_time.Record(ctx, time.Since(start).Milliseconds()) + streamID := withTag("post-"+name, tag) lis, err := s.es.Read(ctx, streamID, paging.GetIdx(0), paging.GetCount(30)) if err != nil { span.RecordError(err) @@ -164,7 +167,7 @@ func (s *service) Posts(ctx context.Context, streamID string, paging *gql.PageIn }, nil } -func (r *service) PostAdded(ctx context.Context, streamID string, after int64) (<-chan *PostEvent, error) { +func (r *service) PostAdded(ctx context.Context, name, tag string, after int64) (<-chan *PostEvent, error) { ctx, span := lg.Span(ctx) defer span.End() @@ -175,6 +178,8 @@ func (r *service) PostAdded(ctx context.Context, streamID string, after int64) ( return nil, fmt.Errorf("EventStore does not implement streaming") } + streamID := withTag("post-"+name, tag) + sub, err := es.Subscribe(ctx, streamID, after) if err != nil { span.RecordError(err) @@ -232,14 +237,15 @@ func (s *service) get(w http.ResponseWriter, r *http.Request) { start := time.Now() defer s.m_req_time.Record(ctx, time.Since(start).Milliseconds()) - name, _, _ := strings.Cut(r.URL.Path, "/") + name, tag, _ := strings.Cut(r.URL.Path, "/") if name == "" { w.WriteHeader(http.StatusNotFound) return } + streamID := withTag("post-"+name, tag) var first event.Event = event.NilEvent - if lis, err := s.es.Read(ctx, "post-"+name, 0, 1); err == nil && len(lis) > 0 { + if lis, err := s.es.Read(ctx, streamID, 0, 1); err == nil && len(lis) > 0 { first = lis[0] } @@ -256,8 +262,8 @@ func (s *service) get(w http.ResponseWriter, r *http.Request) { count = i } - span.AddEvent(fmt.Sprint("GET topic=", name, " idx=", pos, " n=", count)) - events, err := s.es.Read(ctx, "post-"+name, pos, count) + span.AddEvent(fmt.Sprint("GET topic=", streamID, " idx=", pos, " n=", count)) + events, err := s.es.Read(ctx, streamID, pos, count) if err != nil { span.RecordError(err) w.WriteHeader(http.StatusInternalServerError) @@ -356,14 +362,16 @@ func (s *service) websocket(w http.ResponseWriter, r *http.Request) { ctx, span := lg.Span(ctx) defer span.End() - name, _, _ := strings.Cut(r.URL.Path, "/") + name, tag, _ := strings.Cut(r.URL.Path, "/") if name == "" { w.WriteHeader(http.StatusNotFound) return } + streamID := withTag("post-"+name, tag) + var first event.Event = event.NilEvent - if lis, err := s.es.Read(ctx, "post-"+name, 0, 1); err == nil && len(lis) > 0 { + if lis, err := s.es.Read(ctx, streamID, 0, 1); err == nil && len(lis) > 0 { first = lis[0] } @@ -374,7 +382,7 @@ func (s *service) websocket(w http.ResponseWriter, r *http.Request) { pos = i - 1 } - span.AddEvent(fmt.Sprint("WS topic=", name, " idx=", pos)) + span.AddEvent(fmt.Sprint("WS topic=", streamID, " idx=", pos)) c, err := upgrader.Upgrade(w, r, nil) if err != nil { @@ -409,7 +417,7 @@ func (s *service) websocket(w http.ResponseWriter, r *http.Request) { return } - sub, err := es.Subscribe(ctx, "post-"+name, pos) + sub, err := es.Subscribe(ctx, streamID, pos) if err != nil { span.RecordError(err) return @@ -457,21 +465,9 @@ type PostEvent struct { payload []byte tags []string - eventMeta event.Meta + event.IsEvent } -func (e *PostEvent) EventMeta() event.Meta { - if e == nil { - return event.Meta{} - } - return e.eventMeta -} -func (e *PostEvent) SetEventMeta(eventMeta event.Meta) { - if e == nil { - return - } - e.eventMeta = eventMeta -} func (e *PostEvent) Values() any { if e == nil { return nil @@ -503,25 +499,23 @@ func (e *PostEvent) UnmarshalBinary(b []byte) error { func (e *PostEvent) MarshalJSON() ([]byte, error) { return e.MarshalBinary() } func (e *PostEvent) UnmarshalJSON(b []byte) error { return e.UnmarshalBinary(b) } -func (e *PostEvent) ID() string { return e.eventMeta.GetEventID() } +func (e *PostEvent) ID() string { return e.EventMeta().GetEventID() } func (e *PostEvent) Tags() []string { return e.tags } func (e *PostEvent) Payload() string { return string(e.payload) } func (e *PostEvent) PayloadJSON(ctx context.Context) (m map[string]interface{}, err error) { err = json.Unmarshal([]byte(e.payload), &m) return } -func (e *PostEvent) Meta() *event.Meta { return &e.eventMeta } -func (e *PostEvent) IsEdge() {} +func (e *PostEvent) Meta() event.Meta { return e.EventMeta() } +func (e *PostEvent) IsEdge() {} func (e *PostEvent) String() string { var b bytes.Buffer - // b.WriteString(e.eventMeta.StreamID) - // b.WriteRune('@') - b.WriteString(strconv.FormatUint(e.eventMeta.Position, 10)) + b.WriteString(strconv.FormatUint(e.EventMeta().Position, 10)) b.WriteRune('\t') - b.WriteString(e.eventMeta.EventID.String()) + b.WriteString(e.EventMeta().EventID.String()) b.WriteRune('\t') b.WriteString(string(e.payload)) if len(e.tags) > 0 { @@ -536,7 +530,7 @@ func fields(s string) []string { if s == "" { return nil } - return strings.Split(s, "/") + return strings.Split(s, ";") } func encodeJSON(w io.Writer, first event.Event, events ...event.Event) error { @@ -573,3 +567,32 @@ func encodeJSON(w io.Writer, first event.Event, events ...event.Event) error { return json.NewEncoder(w).Encode(out) } + +func Projector(e event.Event) []event.Event { + m := e.EventMeta() + streamID := m.StreamID + streamPos := m.Position + + switch e := e.(type) { + case *PostEvent: + lis := make([]event.Event, len(e.tags)) + for i := range lis { + tag := e.tags[i] + ne := event.NewPtr(streamID, streamPos) + event.SetStreamID(withTag(streamID, tag), ne) + lis[i] = ne + } + + return lis + } + return nil +} +func withTag(id, tag string) string { + if tag == "" { + return id + } + + h := fnv.New128a() + fmt.Fprint(h, tag) + return id + "-" + base64.RawURLEncoding.EncodeToString(h.Sum(nil)) +} diff --git a/app/peerfinder/ev-info.go b/app/peerfinder/ev-info.go index 7a8f13f..d5f6b28 100644 --- a/app/peerfinder/ev-info.go +++ b/app/peerfinder/ev-info.go @@ -51,22 +51,11 @@ func (a *Info) OnUpsert() error { type VersionChanged struct { ScriptVersion string `json:"script_version"` - eventMeta event.Meta + event.IsEvent } var _ event.Event = (*VersionChanged)(nil) -func (e *VersionChanged) EventMeta() event.Meta { - if e == nil { - return event.Meta{} - } - return e.eventMeta -} -func (e *VersionChanged) SetEventMeta(m event.Meta) { - if e != nil { - e.eventMeta = m - } -} func (e *VersionChanged) MarshalBinary() (text []byte, err error) { return json.Marshal(e) } diff --git a/app/peerfinder/ev-request.go b/app/peerfinder/ev-request.go index c353cde..900a401 100644 --- a/app/peerfinder/ev-request.go +++ b/app/peerfinder/ev-request.go @@ -33,7 +33,7 @@ func (a *Request) ApplyEvent(lis ...event.Event) { for _, e := range lis { switch e := e.(type) { case *RequestSubmitted: - a.RequestID = e.eventMeta.EventID.String() + a.RequestID = e.EventMeta().EventID.String() a.RequestIP = e.RequestIP a.Hidden = e.Hidden a.Created = ulid.Time(e.EventMeta().EventID.Time()) @@ -120,7 +120,7 @@ func (lis ListResponse) Swap(i, j int) { } type RequestSubmitted struct { - eventMeta event.Meta + event.IsEvent RequestIP string `json:"req_ip"` Hidden bool `json:"hide,omitempty"` @@ -154,22 +154,11 @@ func (r *RequestSubmitted) Family() int { } } func (r *RequestSubmitted) String() string { - return fmt.Sprint(r.eventMeta.EventID, r.RequestIP, r.Hidden, r.CreatedString()) + return fmt.Sprint(r.EventMeta().EventID, r.RequestIP, r.Hidden, r.CreatedString()) } var _ event.Event = (*RequestSubmitted)(nil) -func (e *RequestSubmitted) EventMeta() event.Meta { - if e == nil { - return event.Meta{} - } - return e.eventMeta -} -func (e *RequestSubmitted) SetEventMeta(m event.Meta) { - if e != nil { - e.eventMeta = m - } -} func (e *RequestSubmitted) MarshalBinary() (text []byte, err error) { return json.Marshal(e) } @@ -204,7 +193,7 @@ func (e *RequestSubmitted) MarshalEnviron() ([]byte, error) { } type ResultSubmitted struct { - eventMeta event.Meta + event.IsEvent RequestID string `json:"req_id"` PeerID string `json:"peer_id"` @@ -219,22 +208,11 @@ type ResultSubmitted struct { } func (r *ResultSubmitted) Created() time.Time { - return r.eventMeta.Created() + return r.EventMeta().Created() } var _ event.Event = (*ResultSubmitted)(nil) -func (e *ResultSubmitted) EventMeta() event.Meta { - if e == nil { - return event.Meta{} - } - return e.eventMeta -} -func (e *ResultSubmitted) SetEventMeta(m event.Meta) { - if e != nil { - e.eventMeta = m - } -} func (e *ResultSubmitted) MarshalBinary() (text []byte, err error) { return json.Marshal(e) } @@ -248,22 +226,11 @@ func (e *ResultSubmitted) String() string { type RequestTruncated struct { RequestID string - eventMeta event.Meta + event.IsEvent } var _ event.Event = (*RequestTruncated)(nil) -func (e *RequestTruncated) EventMeta() event.Meta { - if e == nil { - return event.Meta{} - } - return e.eventMeta -} -func (e *RequestTruncated) SetEventMeta(m event.Meta) { - if e != nil { - e.eventMeta = m - } -} func (e *RequestTruncated) MarshalBinary() (text []byte, err error) { return json.Marshal(e) } diff --git a/app/peerfinder/jobs.go b/app/peerfinder/jobs.go index ccb45bf..fcd7b90 100644 --- a/app/peerfinder/jobs.go +++ b/app/peerfinder/jobs.go @@ -164,7 +164,7 @@ func (s *service) cleanRequests(ctx context.Context, now time.Time) error { for _, event := range events { switch e := event.(type) { case *RequestSubmitted: - if e.eventMeta.ActualPosition < last-maxResults { + if e.EventMeta().ActualPosition < last-maxResults { streamIDs = append(streamIDs, e.RequestID()) } } diff --git a/app/salty/salty-user.go b/app/salty/salty-user.go index 5972cd0..2d79cd0 100644 --- a/app/salty/salty-user.go +++ b/app/salty/salty-user.go @@ -60,22 +60,11 @@ type UserRegistered struct { Name string Pubkey *keys.EdX25519PublicKey - eventMeta event.Meta + event.IsEvent } var _ event.Event = (*UserRegistered)(nil) -func (e *UserRegistered) EventMeta() event.Meta { - if e == nil { - return event.Meta{} - } - return e.eventMeta -} -func (e *UserRegistered) SetEventMeta(m event.Meta) { - if e != nil { - e.eventMeta = m - } -} func (e *UserRegistered) MarshalBinary() (text []byte, err error) { var b bytes.Buffer b.WriteString(e.Name) diff --git a/app/webfinger/events.go b/app/webfinger/events.go index 9bf62a0..643bed0 100644 --- a/app/webfinger/events.go +++ b/app/webfinger/events.go @@ -11,20 +11,9 @@ type SubjectSet struct { Aliases []string `json:"aliases,omitempty"` Properties map[string]*string `json:"properties,omitempty"` - eventMeta event.Meta + event.IsEvent } -func (e *SubjectSet) EventMeta() event.Meta { - if e == nil { - return event.Meta{} - } - return e.eventMeta -} -func (e *SubjectSet) SetEventMeta(m event.Meta) { - if e != nil { - e.eventMeta = m - } -} func (e *SubjectSet) MarshalBinary() (text []byte, err error) { return json.Marshal(e) } @@ -37,20 +26,9 @@ var _ event.Event = (*SubjectSet)(nil) type SubjectDeleted struct { Subject string `json:"subject"` - eventMeta event.Meta + event.IsEvent } -func (e *SubjectDeleted) EventMeta() event.Meta { - if e == nil { - return event.Meta{} - } - return e.eventMeta -} -func (e *SubjectDeleted) SetEventMeta(m event.Meta) { - if e != nil { - e.eventMeta = m - } -} func (e *SubjectDeleted) MarshalBinary() (text []byte, err error) { return json.Marshal(e) } @@ -67,20 +45,9 @@ type LinkSet struct { Titles map[string]string `json:"titles,omitempty"` Properties map[string]*string `json:"properties,omitempty"` - eventMeta event.Meta + event.IsEvent } -func (e *LinkSet) EventMeta() event.Meta { - if e == nil { - return event.Meta{} - } - return e.eventMeta -} -func (e *LinkSet) SetEventMeta(m event.Meta) { - if e != nil { - e.eventMeta = m - } -} func (e *LinkSet) MarshalBinary() (text []byte, err error) { return json.Marshal(e) } @@ -93,20 +60,9 @@ var _ event.Event = (*LinkSet)(nil) type LinkDeleted struct { Rel string `json:"rel"` - eventMeta event.Meta + event.IsEvent } -func (e *LinkDeleted) EventMeta() event.Meta { - if e == nil { - return event.Meta{} - } - return e.eventMeta -} -func (e *LinkDeleted) SetEventMeta(m event.Meta) { - if e != nil { - e.eventMeta = m - } -} func (e *LinkDeleted) MarshalBinary() (text []byte, err error) { return json.Marshal(e) } diff --git a/cmd/ev/app.msgbus.go b/cmd/ev/app.msgbus.go index 4d033da..76cb205 100644 --- a/cmd/ev/app.msgbus.go +++ b/cmd/ev/app.msgbus.go @@ -8,6 +8,7 @@ import ( "go.sour.is/ev/app/msgbus" "go.sour.is/ev/internal/lg" "go.sour.is/ev/pkg/service" + "go.sour.is/ev/pkg/es/driver/projecter" "go.sour.is/ev/pkg/slice" ) @@ -20,6 +21,7 @@ var _ = apps.Register(50, func(ctx context.Context, svc *service.Harness) error if !ok { return fmt.Errorf("*es.EventStore not found in services") } + eventstore.Option(projecter.New(ctx, msgbus.Projector)) msgbus, err := msgbus.New(ctx, eventstore) if err != nil { diff --git a/ev_test.go b/ev_test.go index 118aa88..bc79a6d 100644 --- a/ev_test.go +++ b/ev_test.go @@ -48,21 +48,9 @@ func (a *Thing) OnSetValue(value string) error { type ValueSet struct { Value string - eventMeta event.Meta + event.IsEvent } -func (e *ValueSet) EventMeta() event.Meta { - if e == nil { - return event.Meta{} - } - return e.eventMeta -} -func (e *ValueSet) SetEventMeta(eventMeta event.Meta) { - if e == nil { - return - } - e.eventMeta = eventMeta -} func (e *ValueSet) MarshalBinary() ([]byte, error) { return json.Marshal(e) } diff --git a/internal/graph/generated/generated.go b/internal/graph/generated/generated.go index f91fb3a..fc1b40c 100644 --- a/internal/graph/generated/generated.go +++ b/internal/graph/generated/generated.go @@ -16,13 +16,13 @@ import ( "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/introspection" "github.com/99designs/gqlgen/plugin/federation/fedruntime" + gqlparser "github.com/vektah/gqlparser/v2" + "github.com/vektah/gqlparser/v2/ast" "go.sour.is/ev/app/msgbus" "go.sour.is/ev/app/salty" "go.sour.is/ev/pkg/es" "go.sour.is/ev/pkg/es/event" "go.sour.is/ev/pkg/gql" - gqlparser "github.com/vektah/gqlparser/v2" - "github.com/vektah/gqlparser/v2/ast" ) // region ************************** generated!.gotpl ************************** @@ -99,7 +99,7 @@ type ComplexityRoot struct { Query struct { Events func(childComplexity int, streamID string, paging *gql.PageInput) int - Posts func(childComplexity int, streamID string, paging *gql.PageInput) int + Posts func(childComplexity int, name string, tag string, paging *gql.PageInput) int SaltyUser func(childComplexity int, nick string) int __resolve__service func(childComplexity int) int } @@ -112,7 +112,7 @@ type ComplexityRoot struct { Subscription struct { EventAdded func(childComplexity int, streamID string, after int64) int - PostAdded func(childComplexity int, streamID string, after int64) int + PostAdded func(childComplexity int, name string, tag string, after int64) int } _Service struct { @@ -126,12 +126,12 @@ type MutationResolver interface { } type QueryResolver interface { Events(ctx context.Context, streamID string, paging *gql.PageInput) (*gql.Connection, error) - Posts(ctx context.Context, streamID string, paging *gql.PageInput) (*gql.Connection, error) + Posts(ctx context.Context, name string, tag string, paging *gql.PageInput) (*gql.Connection, error) SaltyUser(ctx context.Context, nick string) (*salty.SaltyUser, error) } type SubscriptionResolver interface { EventAdded(ctx context.Context, streamID string, after int64) (<-chan *es.GQLEvent, error) - PostAdded(ctx context.Context, streamID string, after int64) (<-chan *msgbus.PostEvent, error) + PostAdded(ctx context.Context, name string, tag string, after int64) (<-chan *msgbus.PostEvent, error) } type executableSchema struct { @@ -370,7 +370,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Query.Posts(childComplexity, args["streamID"].(string), args["paging"].(*gql.PageInput)), true + return e.complexity.Query.Posts(childComplexity, args["name"].(string), args["tag"].(string), args["paging"].(*gql.PageInput)), true case "Query.saltyUser": if e.complexity.Query.SaltyUser == nil { @@ -434,7 +434,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Subscription.PostAdded(childComplexity, args["streamID"].(string), args["after"].(int64)), true + return e.complexity.Subscription.PostAdded(childComplexity, args["name"].(string), args["tag"].(string), args["after"].(int64)), true case "_Service.sdl": if e.complexity._Service.SDL == nil { @@ -609,11 +609,11 @@ directive @goTag( # set(namespace: String! key: String! value: String): Bool! # }`, BuiltIn: false}, {Name: "../../../app/msgbus/msgbus.graphqls", Input: `extend type Query { - posts(streamID: String! paging: PageInput): Connection! + posts(name: String!, tag: String! = "", paging: PageInput): Connection! } extend type Subscription { """after == 0 start from begining, after == -1 start from end""" - postAdded(streamID: String! after: Int! = -1): PostEvent + postAdded(name: String!, tag: String! = "", after: Int! = -1): PostEvent } type PostEvent implements Edge @goModel(model: "go.sour.is/ev/app/msgbus.PostEvent") { id: ID! @@ -742,7 +742,7 @@ func (ec *executionContext) field_Query_events_args(ctx context.Context, rawArgs var arg1 *gql.PageInput if tmp, ok := rawArgs["paging"]; ok { ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("paging")) - arg1, err = ec.unmarshalOPageInput2ᚖgithubᚗcomᚋsourᚑisᚋevᚋpkgᚋgqlᚐPageInput(ctx, tmp) + arg1, err = ec.unmarshalOPageInput2ᚖgoᚗsourᚗisᚋevᚋpkgᚋgqlᚐPageInput(ctx, tmp) if err != nil { return nil, err } @@ -755,23 +755,32 @@ func (ec *executionContext) field_Query_posts_args(ctx context.Context, rawArgs var err error args := map[string]interface{}{} var arg0 string - if tmp, ok := rawArgs["streamID"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("streamID")) + if tmp, ok := rawArgs["name"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("name")) arg0, err = ec.unmarshalNString2string(ctx, tmp) if err != nil { return nil, err } } - args["streamID"] = arg0 - var arg1 *gql.PageInput - if tmp, ok := rawArgs["paging"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("paging")) - arg1, err = ec.unmarshalOPageInput2ᚖgithubᚗcomᚋsourᚑisᚋevᚋpkgᚋgqlᚐPageInput(ctx, tmp) + args["name"] = arg0 + var arg1 string + if tmp, ok := rawArgs["tag"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("tag")) + arg1, err = ec.unmarshalNString2string(ctx, tmp) if err != nil { return nil, err } } - args["paging"] = arg1 + args["tag"] = arg1 + var arg2 *gql.PageInput + if tmp, ok := rawArgs["paging"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("paging")) + arg2, err = ec.unmarshalOPageInput2ᚖgoᚗsourᚗisᚋevᚋpkgᚋgqlᚐPageInput(ctx, tmp) + if err != nil { + return nil, err + } + } + args["paging"] = arg2 return args, nil } @@ -818,23 +827,32 @@ func (ec *executionContext) field_Subscription_postAdded_args(ctx context.Contex var err error args := map[string]interface{}{} var arg0 string - if tmp, ok := rawArgs["streamID"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("streamID")) + if tmp, ok := rawArgs["name"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("name")) arg0, err = ec.unmarshalNString2string(ctx, tmp) if err != nil { return nil, err } } - args["streamID"] = arg0 - var arg1 int64 - if tmp, ok := rawArgs["after"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("after")) - arg1, err = ec.unmarshalNInt2int64(ctx, tmp) + args["name"] = arg0 + var arg1 string + if tmp, ok := rawArgs["tag"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("tag")) + arg1, err = ec.unmarshalNString2string(ctx, tmp) if err != nil { return nil, err } } - args["after"] = arg1 + args["tag"] = arg1 + var arg2 int64 + if tmp, ok := rawArgs["after"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("after")) + arg2, err = ec.unmarshalNInt2int64(ctx, tmp) + if err != nil { + return nil, err + } + } + args["after"] = arg2 return args, nil } @@ -904,7 +922,7 @@ func (ec *executionContext) _Connection_paging(ctx context.Context, field graphq } res := resTmp.(*gql.PageInfo) fc.Result = res - return ec.marshalNPageInfo2ᚖgithubᚗcomᚋsourᚑisᚋevᚋpkgᚋgqlᚐPageInfo(ctx, field.Selections, res) + return ec.marshalNPageInfo2ᚖgoᚗsourᚗisᚋevᚋpkgᚋgqlᚐPageInfo(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Connection_paging(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -958,7 +976,7 @@ func (ec *executionContext) _Connection_edges(ctx context.Context, field graphql } res := resTmp.([]gql.Edge) fc.Result = res - return ec.marshalNEdge2ᚕgithubᚗcomᚋsourᚑisᚋevᚋpkgᚋgqlᚐEdgeᚄ(ctx, field.Selections, res) + return ec.marshalNEdge2ᚕgoᚗsourᚗisᚋevᚋpkgᚋgqlᚐEdgeᚄ(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Connection_edges(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -1354,7 +1372,7 @@ func (ec *executionContext) _Event_meta(ctx context.Context, field graphql.Colle } res := resTmp.(*event.Meta) fc.Result = res - return ec.marshalNMeta2ᚖgithubᚗcomᚋsourᚑisᚋevᚋpkgᚋesᚋeventᚐMeta(ctx, field.Selections, res) + return ec.marshalNMeta2ᚖgoᚗsourᚗisᚋevᚋpkgᚋesᚋeventᚐMeta(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Event_meta(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -1405,7 +1423,7 @@ func (ec *executionContext) _Event_linked(ctx context.Context, field graphql.Col } res := resTmp.(*es.GQLEvent) fc.Result = res - return ec.marshalOEvent2ᚖgithubᚗcomᚋsourᚑisᚋevᚋpkgᚋesᚐGQLEvent(ctx, field.Selections, res) + return ec.marshalOEvent2ᚖgoᚗsourᚗisᚋevᚋpkgᚋesᚐGQLEvent(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Event_linked(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -1699,7 +1717,7 @@ func (ec *executionContext) _Mutation_createSaltyUser(ctx context.Context, field } res := resTmp.(*salty.SaltyUser) fc.Result = res - return ec.marshalOSaltyUser2ᚖgithubᚗcomᚋsourᚑisᚋevᚋappᚋsaltyᚐSaltyUser(ctx, field.Selections, res) + return ec.marshalOSaltyUser2ᚖgoᚗsourᚗisᚋevᚋappᚋsaltyᚐSaltyUser(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Mutation_createSaltyUser(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -2112,9 +2130,9 @@ func (ec *executionContext) _PostEvent_meta(ctx context.Context, field graphql.C } return graphql.Null } - res := resTmp.(*event.Meta) + res := resTmp.(event.Meta) fc.Result = res - return ec.marshalNMeta2ᚖgithubᚗcomᚋsourᚑisᚋevᚋpkgᚋesᚋeventᚐMeta(ctx, field.Selections, res) + return ec.marshalNMeta2goᚗsourᚗisᚋevᚋpkgᚋesᚋeventᚐMeta(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_PostEvent_meta(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -2168,7 +2186,7 @@ func (ec *executionContext) _Query_events(ctx context.Context, field graphql.Col } res := resTmp.(*gql.Connection) fc.Result = res - return ec.marshalNConnection2ᚖgithubᚗcomᚋsourᚑisᚋevᚋpkgᚋgqlᚐConnection(ctx, field.Selections, res) + return ec.marshalNConnection2ᚖgoᚗsourᚗisᚋevᚋpkgᚋgqlᚐConnection(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Query_events(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -2215,7 +2233,7 @@ func (ec *executionContext) _Query_posts(ctx context.Context, field graphql.Coll }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().Posts(rctx, fc.Args["streamID"].(string), fc.Args["paging"].(*gql.PageInput)) + return ec.resolvers.Query().Posts(rctx, fc.Args["name"].(string), fc.Args["tag"].(string), fc.Args["paging"].(*gql.PageInput)) }) if err != nil { ec.Error(ctx, err) @@ -2229,7 +2247,7 @@ func (ec *executionContext) _Query_posts(ctx context.Context, field graphql.Coll } res := resTmp.(*gql.Connection) fc.Result = res - return ec.marshalNConnection2ᚖgithubᚗcomᚋsourᚑisᚋevᚋpkgᚋgqlᚐConnection(ctx, field.Selections, res) + return ec.marshalNConnection2ᚖgoᚗsourᚗisᚋevᚋpkgᚋgqlᚐConnection(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Query_posts(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -2287,7 +2305,7 @@ func (ec *executionContext) _Query_saltyUser(ctx context.Context, field graphql. } res := resTmp.(*salty.SaltyUser) fc.Result = res - return ec.marshalOSaltyUser2ᚖgithubᚗcomᚋsourᚑisᚋevᚋappᚋsaltyᚐSaltyUser(ctx, field.Selections, res) + return ec.marshalOSaltyUser2ᚖgoᚗsourᚗisᚋevᚋappᚋsaltyᚐSaltyUser(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Query_saltyUser(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -2664,7 +2682,7 @@ func (ec *executionContext) _Subscription_eventAdded(ctx context.Context, field w.Write([]byte{'{'}) graphql.MarshalString(field.Alias).MarshalGQL(w) w.Write([]byte{':'}) - ec.marshalOEvent2ᚖgithubᚗcomᚋsourᚑisᚋevᚋpkgᚋesᚐGQLEvent(ctx, field.Selections, res).MarshalGQL(w) + ec.marshalOEvent2ᚖgoᚗsourᚗisᚋevᚋpkgᚋesᚐGQLEvent(ctx, field.Selections, res).MarshalGQL(w) w.Write([]byte{'}'}) }) case <-ctx.Done(): @@ -2733,7 +2751,7 @@ func (ec *executionContext) _Subscription_postAdded(ctx context.Context, field g }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Subscription().PostAdded(rctx, fc.Args["streamID"].(string), fc.Args["after"].(int64)) + return ec.resolvers.Subscription().PostAdded(rctx, fc.Args["name"].(string), fc.Args["tag"].(string), fc.Args["after"].(int64)) }) if err != nil { ec.Error(ctx, err) @@ -2752,7 +2770,7 @@ func (ec *executionContext) _Subscription_postAdded(ctx context.Context, field g w.Write([]byte{'{'}) graphql.MarshalString(field.Alias).MarshalGQL(w) w.Write([]byte{':'}) - ec.marshalOPostEvent2ᚖgithubᚗcomᚋsourᚑisᚋevᚋappᚋmsgbusᚐPostEvent(ctx, field.Selections, res).MarshalGQL(w) + ec.marshalOPostEvent2ᚖgoᚗsourᚗisᚋevᚋappᚋmsgbusᚐPostEvent(ctx, field.Selections, res).MarshalGQL(w) w.Write([]byte{'}'}) }) case <-ctx.Done(): @@ -5603,11 +5621,11 @@ func (ec *executionContext) marshalNBoolean2bool(ctx context.Context, sel ast.Se return res } -func (ec *executionContext) marshalNConnection2githubᚗcomᚋsourᚑisᚋevᚋpkgᚋgqlᚐConnection(ctx context.Context, sel ast.SelectionSet, v gql.Connection) graphql.Marshaler { +func (ec *executionContext) marshalNConnection2goᚗsourᚗisᚋevᚋpkgᚋgqlᚐConnection(ctx context.Context, sel ast.SelectionSet, v gql.Connection) graphql.Marshaler { return ec._Connection(ctx, sel, &v) } -func (ec *executionContext) marshalNConnection2ᚖgithubᚗcomᚋsourᚑisᚋevᚋpkgᚋgqlᚐConnection(ctx context.Context, sel ast.SelectionSet, v *gql.Connection) graphql.Marshaler { +func (ec *executionContext) marshalNConnection2ᚖgoᚗsourᚗisᚋevᚋpkgᚋgqlᚐConnection(ctx context.Context, sel ast.SelectionSet, v *gql.Connection) graphql.Marshaler { if v == nil { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { ec.Errorf(ctx, "the requested element is null which the schema does not allow") @@ -5617,7 +5635,7 @@ func (ec *executionContext) marshalNConnection2ᚖgithubᚗcomᚋsourᚑisᚋev return ec._Connection(ctx, sel, v) } -func (ec *executionContext) marshalNEdge2githubᚗcomᚋsourᚑisᚋevᚋpkgᚋgqlᚐEdge(ctx context.Context, sel ast.SelectionSet, v gql.Edge) graphql.Marshaler { +func (ec *executionContext) marshalNEdge2goᚗsourᚗisᚋevᚋpkgᚋgqlᚐEdge(ctx context.Context, sel ast.SelectionSet, v gql.Edge) graphql.Marshaler { if v == nil { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { ec.Errorf(ctx, "the requested element is null which the schema does not allow") @@ -5627,7 +5645,7 @@ func (ec *executionContext) marshalNEdge2githubᚗcomᚋsourᚑisᚋevᚋpkgᚋg return ec._Edge(ctx, sel, v) } -func (ec *executionContext) marshalNEdge2ᚕgithubᚗcomᚋsourᚑisᚋevᚋpkgᚋgqlᚐEdgeᚄ(ctx context.Context, sel ast.SelectionSet, v []gql.Edge) graphql.Marshaler { +func (ec *executionContext) marshalNEdge2ᚕgoᚗsourᚗisᚋevᚋpkgᚋgqlᚐEdgeᚄ(ctx context.Context, sel ast.SelectionSet, v []gql.Edge) graphql.Marshaler { ret := make(graphql.Array, len(v)) var wg sync.WaitGroup isLen1 := len(v) == 1 @@ -5651,7 +5669,7 @@ func (ec *executionContext) marshalNEdge2ᚕgithubᚗcomᚋsourᚑisᚋevᚋpkg if !isLen1 { defer wg.Done() } - ret[i] = ec.marshalNEdge2githubᚗcomᚋsourᚑisᚋevᚋpkgᚋgqlᚐEdge(ctx, sel, v[i]) + ret[i] = ec.marshalNEdge2goᚗsourᚗisᚋevᚋpkgᚋgqlᚐEdge(ctx, sel, v[i]) } if isLen1 { f(i) @@ -5737,7 +5755,11 @@ func (ec *executionContext) marshalNMap2map(ctx context.Context, sel ast.Selecti return res } -func (ec *executionContext) marshalNMeta2ᚖgithubᚗcomᚋsourᚑisᚋevᚋpkgᚋesᚋeventᚐMeta(ctx context.Context, sel ast.SelectionSet, v *event.Meta) graphql.Marshaler { +func (ec *executionContext) marshalNMeta2goᚗsourᚗisᚋevᚋpkgᚋesᚋeventᚐMeta(ctx context.Context, sel ast.SelectionSet, v event.Meta) graphql.Marshaler { + return ec._Meta(ctx, sel, &v) +} + +func (ec *executionContext) marshalNMeta2ᚖgoᚗsourᚗisᚋevᚋpkgᚋesᚋeventᚐMeta(ctx context.Context, sel ast.SelectionSet, v *event.Meta) graphql.Marshaler { if v == nil { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { ec.Errorf(ctx, "the requested element is null which the schema does not allow") @@ -5747,7 +5769,7 @@ func (ec *executionContext) marshalNMeta2ᚖgithubᚗcomᚋsourᚑisᚋevᚋpkg return ec._Meta(ctx, sel, v) } -func (ec *executionContext) marshalNPageInfo2ᚖgithubᚗcomᚋsourᚑisᚋevᚋpkgᚋgqlᚐPageInfo(ctx context.Context, sel ast.SelectionSet, v *gql.PageInfo) graphql.Marshaler { +func (ec *executionContext) marshalNPageInfo2ᚖgoᚗsourᚗisᚋevᚋpkgᚋgqlᚐPageInfo(ctx context.Context, sel ast.SelectionSet, v *gql.PageInfo) graphql.Marshaler { if v == nil { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { ec.Errorf(ctx, "the requested element is null which the schema does not allow") @@ -6117,7 +6139,7 @@ func (ec *executionContext) marshalOBoolean2ᚖbool(ctx context.Context, sel ast return res } -func (ec *executionContext) marshalOEvent2ᚖgithubᚗcomᚋsourᚑisᚋevᚋpkgᚋesᚐGQLEvent(ctx context.Context, sel ast.SelectionSet, v *es.GQLEvent) graphql.Marshaler { +func (ec *executionContext) marshalOEvent2ᚖgoᚗsourᚗisᚋevᚋpkgᚋesᚐGQLEvent(ctx context.Context, sel ast.SelectionSet, v *es.GQLEvent) graphql.Marshaler { if v == nil { return graphql.Null } @@ -6140,7 +6162,7 @@ func (ec *executionContext) marshalOInt2ᚖint64(ctx context.Context, sel ast.Se return res } -func (ec *executionContext) unmarshalOPageInput2ᚖgithubᚗcomᚋsourᚑisᚋevᚋpkgᚋgqlᚐPageInput(ctx context.Context, v interface{}) (*gql.PageInput, error) { +func (ec *executionContext) unmarshalOPageInput2ᚖgoᚗsourᚗisᚋevᚋpkgᚋgqlᚐPageInput(ctx context.Context, v interface{}) (*gql.PageInput, error) { if v == nil { return nil, nil } @@ -6148,14 +6170,14 @@ func (ec *executionContext) unmarshalOPageInput2ᚖgithubᚗcomᚋsourᚑisᚋev return &res, graphql.ErrorOnPath(ctx, err) } -func (ec *executionContext) marshalOPostEvent2ᚖgithubᚗcomᚋsourᚑisᚋevᚋappᚋmsgbusᚐPostEvent(ctx context.Context, sel ast.SelectionSet, v *msgbus.PostEvent) graphql.Marshaler { +func (ec *executionContext) marshalOPostEvent2ᚖgoᚗsourᚗisᚋevᚋappᚋmsgbusᚐPostEvent(ctx context.Context, sel ast.SelectionSet, v *msgbus.PostEvent) graphql.Marshaler { if v == nil { return graphql.Null } return ec._PostEvent(ctx, sel, v) } -func (ec *executionContext) marshalOSaltyUser2ᚖgithubᚗcomᚋsourᚑisᚋevᚋappᚋsaltyᚐSaltyUser(ctx context.Context, sel ast.SelectionSet, v *salty.SaltyUser) graphql.Marshaler { +func (ec *executionContext) marshalOSaltyUser2ᚖgoᚗsourᚗisᚋevᚋappᚋsaltyᚐSaltyUser(ctx context.Context, sel ast.SelectionSet, v *salty.SaltyUser) graphql.Marshaler { if v == nil { return graphql.Null } diff --git a/internal/graph/resolver/resolver.go b/internal/graph/resolver/resolver.go index d0786de..02d6a16 100644 --- a/internal/graph/resolver/resolver.go +++ b/internal/graph/resolver/resolver.go @@ -30,7 +30,7 @@ func (r *queryResolver) Events(ctx context.Context, streamID string, paging *gql } // // foo -func (r *queryResolver) Posts(ctx context.Context, streamID string, paging *gql.PageInput) (*gql.Connection, error) { +func (r *queryResolver) Posts(ctx context.Context, name, tag string, paging *gql.PageInput) (*gql.Connection, error) { panic("not implemented") } @@ -45,7 +45,7 @@ func (r *subscriptionResolver) EventAdded(ctx context.Context, streamID string, } // // foo -func (r *subscriptionResolver) PostAdded(ctx context.Context, streamID string, after int64) (<-chan *msgbus.PostEvent, error) { +func (r *subscriptionResolver) PostAdded(ctx context.Context, name, tag string, after int64) (<-chan *msgbus.PostEvent, error) { panic("not implemented") } diff --git a/pkg/es/event/aggregate_test.go b/pkg/es/event/aggregate_test.go index 338a13f..5abe50c 100644 --- a/pkg/es/event/aggregate_test.go +++ b/pkg/es/event/aggregate_test.go @@ -33,24 +33,11 @@ func (a *Agg) ApplyEvent(lis ...event.Event) { type ValueApplied struct { Value string - eventMeta event.Meta + event.IsEvent } var _ event.Event = (*ValueApplied)(nil) -func (e *ValueApplied) EventMeta() event.Meta { - if e == nil { - return event.Meta{} - } - return e.eventMeta -} - -func (e *ValueApplied) SetEventMeta(m event.Meta) { - if e != nil { - e.eventMeta = m - } -} - func (e *ValueApplied) MarshalBinary() ([]byte, error) { return json.Marshal(e) } diff --git a/pkg/es/event/events.go b/pkg/es/event/events.go index 15a99b6..51c6abe 100644 --- a/pkg/es/event/events.go +++ b/pkg/es/event/events.go @@ -5,7 +5,6 @@ import ( "context" "crypto/rand" "encoding" - "encoding/json" "fmt" "io" "strconv" @@ -156,25 +155,23 @@ func Init(ctx context.Context) error { } type nilEvent struct { + IsEvent } -func (*nilEvent) EventMeta() Meta { return Meta{} } -func (*nilEvent) SetEventMeta(eventMeta Meta) {} - var NilEvent = &nilEvent{} func (e *nilEvent) MarshalBinary() ([]byte, error) { - return json.Marshal(e) + return nil, nil } func (e *nilEvent) UnmarshalBinary(b []byte) error { - return json.Unmarshal(b, e) + return nil } type EventPtr struct { StreamID string `json:"stream_id"` Pos uint64 `json:"pos"` - eventMeta Meta + IsEvent } var _ Event = (*EventPtr)(nil) @@ -202,22 +199,6 @@ func (e *EventPtr) UnmarshalBinary(data []byte) error { return err } -// EventMeta implements Event -func (e *EventPtr) EventMeta() Meta { - if e == nil { - return Meta{} - } - return e.eventMeta -} - -// SetEventMeta implements Event -func (e *EventPtr) SetEventMeta(m Meta) { - if e == nil { - return - } - e.eventMeta = m -} - func (e *EventPtr) Values() any { return struct { StreamID string `json:"stream_id"` @@ -229,26 +210,30 @@ func (e *EventPtr) Values() any { } type FeedTruncated struct { - eventMeta Meta -} - -// EventMeta implements Event -func (e *FeedTruncated) EventMeta() Meta { - if e == nil { - return Meta{} - } - return e.eventMeta -} - -// SetEventMeta implements Event -func (e *FeedTruncated) SetEventMeta(m Meta) { - if e == nil { - return - } - e.eventMeta = m + IsEvent } func (e *FeedTruncated) Values() any { return struct { }{} } + +type property[T any] struct { + v T +} + +type IsEvent = property[Meta] + +func (p *property[T]) EventMeta() T { + if p == nil { + var t T + return t + } + return p.v +} + +func (p *property[T]) SetEventMeta(x T) { + if p != nil { + p.v = x + } +} diff --git a/pkg/es/event/events_test.go b/pkg/es/event/events_test.go index 348314a..621e685 100644 --- a/pkg/es/event/events_test.go +++ b/pkg/es/event/events_test.go @@ -14,21 +14,9 @@ import ( type DummyEvent struct { Value string - eventMeta event.Meta + event.IsEvent } -func (e *DummyEvent) EventMeta() event.Meta { - if e == nil { - return event.Meta{} - } - return e.eventMeta -} -func (e *DummyEvent) SetEventMeta(eventMeta event.Meta) { - if e == nil { - return - } - e.eventMeta = eventMeta -} func (e *DummyEvent) MarshalBinary() ([]byte, error) { return json.Marshal(e) } diff --git a/pkg/es/event/reflect.go b/pkg/es/event/reflect.go index e2407f2..d06f724 100644 --- a/pkg/es/event/reflect.go +++ b/pkg/es/event/reflect.go @@ -25,7 +25,7 @@ type UnknownEvent struct { eventType string values map[string]json.RawMessage - eventMeta Meta + IsEvent } var _ Event = (*UnknownEvent)(nil) @@ -48,16 +48,16 @@ func NewUnknownEventFromValues(eventType string, meta Meta, values url.Values) * } } - return &UnknownEvent{eventType: eventType, eventMeta: meta, values: jsonValues} + e := &UnknownEvent{eventType: eventType, values: jsonValues} + e.SetEventMeta(meta) + return e } func NewUnknownEventFromRaw(eventType string, meta Meta, values map[string]json.RawMessage) *UnknownEvent { - return &UnknownEvent{eventType: eventType, eventMeta: meta, values: values} + e := &UnknownEvent{eventType: eventType, values: values} + e.SetEventMeta(meta) + return e } -func (u UnknownEvent) EventMeta() Meta { return u.eventMeta } func (u UnknownEvent) EventType() string { return u.eventType } -func (u *UnknownEvent) SetEventMeta(em Meta) { - u.eventMeta = em -} func (u *UnknownEvent) UnmarshalBinary(b []byte) error { return json.Unmarshal(b, &u.values) }