From f6e1e802020302539a40c2629d30b7e1a0cc0810 Mon Sep 17 00:00:00 2001 From: gearnode Date: Thu, 23 Jan 2025 17:29:16 +0100 Subject: [PATCH] Move to string to GID Signed-off-by: gearnode --- pkg/api/console/v1/gqlgen.yaml | 3 ++ pkg/api/console/v1/schema/schema.go | 39 ++++++++++++------------- pkg/api/console/v1/types/gid.go | 44 +++++++++++++++++++++++++++++ pkg/api/console/v1/types/types.go | 27 +++++++++--------- pkg/api/console/v1/v1_resolver.go | 3 +- pkg/probo/coredata/control.go | 2 +- pkg/probo/coredata/framework.go | 4 +-- pkg/probo/coredata/gid/gid.go | 19 +++++++++++-- pkg/probo/coredata/organization.go | 2 +- pkg/probo/coredata/task.go | 2 +- pkg/probo/probo.go | 9 +++--- 11 files changed, 110 insertions(+), 44 deletions(-) create mode 100644 pkg/api/console/v1/types/gid.go diff --git a/pkg/api/console/v1/gqlgen.yaml b/pkg/api/console/v1/gqlgen.yaml index 904edd9e0..2f77ab2bd 100644 --- a/pkg/api/console/v1/gqlgen.yaml +++ b/pkg/api/console/v1/gqlgen.yaml @@ -19,6 +19,9 @@ omit_panic_handler: true call_argument_directives_with_null: true models: + ID: + model: + - "github.com/getprobo/probo/pkg/api/console/v1/types.GIDScalar" Datetime: model: - "github.com/99designs/gqlgen/graphql.Time" diff --git a/pkg/api/console/v1/schema/schema.go b/pkg/api/console/v1/schema/schema.go index cbbbb473c..a5dbde648 100644 --- a/pkg/api/console/v1/schema/schema.go +++ b/pkg/api/console/v1/schema/schema.go @@ -15,6 +15,7 @@ import ( "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/introspection" "github.com/getprobo/probo/pkg/api/console/v1/types" + "github.com/getprobo/probo/pkg/probo/coredata/gid" "github.com/getprobo/probo/pkg/probo/coredata/page" gqlparser "github.com/vektah/gqlparser/v2" "github.com/vektah/gqlparser/v2/ast" @@ -104,7 +105,7 @@ type ComplexityRoot struct { } Query struct { - Node func(childComplexity int, id string) int + Node func(childComplexity int, id gid.GID) int } Task struct { @@ -135,7 +136,7 @@ type OrganizationResolver interface { Frameworks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.FrameworkConnection, error) } type QueryResolver interface { - Node(ctx context.Context, id string) (types.Node, error) + Node(ctx context.Context, id gid.GID) (types.Node, error) } type executableSchema struct { @@ -385,7 +386,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Query.Node(childComplexity, args["id"].(string)), true + return e.complexity.Query.Node(childComplexity, args["id"].(gid.GID)), true case "Task.createdAt": if e.complexity.Task.CreatedAt == nil { @@ -915,13 +916,13 @@ func (ec *executionContext) field_Query_node_args(ctx context.Context, rawArgs m func (ec *executionContext) field_Query_node_argsID( ctx context.Context, rawArgs map[string]any, -) (string, error) { +) (gid.GID, error) { ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("id")) if tmp, ok := rawArgs["id"]; ok { - return ec.unmarshalNID2string(ctx, tmp) + return ec.unmarshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋgidᚐGID(ctx, tmp) } - var zeroVal string + var zeroVal gid.GID return zeroVal, nil } @@ -999,9 +1000,9 @@ func (ec *executionContext) _Control_id(ctx context.Context, field graphql.Colle } return graphql.Null } - res := resTmp.(string) + res := resTmp.(gid.GID) fc.Result = res - return ec.marshalNID2string(ctx, field.Selections, res) + return ec.marshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋgidᚐGID(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Control_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -1420,9 +1421,9 @@ func (ec *executionContext) _Framework_id(ctx context.Context, field graphql.Col } return graphql.Null } - res := resTmp.(string) + res := resTmp.(gid.GID) fc.Result = res - return ec.marshalNID2string(ctx, field.Selections, res) + return ec.marshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋgidᚐGID(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Framework_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -1841,9 +1842,9 @@ func (ec *executionContext) _Organization_id(ctx context.Context, field graphql. } return graphql.Null } - res := resTmp.(string) + res := resTmp.(gid.GID) fc.Result = res - return ec.marshalNID2string(ctx, field.Selections, res) + return ec.marshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋgidᚐGID(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Organization_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -2176,7 +2177,7 @@ func (ec *executionContext) _Query_node(ctx context.Context, field graphql.Colle ctx = graphql.WithFieldContext(ctx, fc) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().Node(rctx, fc.Args["id"].(string)) + return ec.resolvers.Query().Node(rctx, fc.Args["id"].(gid.GID)) }) if err != nil { ec.Error(ctx, err) @@ -2342,9 +2343,9 @@ func (ec *executionContext) _Task_id(ctx context.Context, field graphql.Collecte } return graphql.Null } - res := resTmp.(string) + res := resTmp.(gid.GID) fc.Result = res - return ec.marshalNID2string(ctx, field.Selections, res) + return ec.marshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋgidᚐGID(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_Task_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -5461,13 +5462,13 @@ func (ec *executionContext) marshalNFrameworkEdge2ᚖgithubᚗcomᚋgetproboᚋp return ec._FrameworkEdge(ctx, sel, v) } -func (ec *executionContext) unmarshalNID2string(ctx context.Context, v any) (string, error) { - res, err := graphql.UnmarshalID(v) +func (ec *executionContext) unmarshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋgidᚐGID(ctx context.Context, v any) (gid.GID, error) { + res, err := types.UnmarshalGIDScalar(v) return res, graphql.ErrorOnPath(ctx, err) } -func (ec *executionContext) marshalNID2string(ctx context.Context, sel ast.SelectionSet, v string) graphql.Marshaler { - res := graphql.MarshalID(v) +func (ec *executionContext) marshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋgidᚐGID(ctx context.Context, sel ast.SelectionSet, v gid.GID) graphql.Marshaler { + res := types.MarshalGIDScalar(v) if res == graphql.Null { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { ec.Errorf(ctx, "the requested element is null which the schema does not allow") diff --git a/pkg/api/console/v1/types/gid.go b/pkg/api/console/v1/types/gid.go new file mode 100644 index 000000000..6a1756f68 --- /dev/null +++ b/pkg/api/console/v1/types/gid.go @@ -0,0 +1,44 @@ +// Copyright (c) 2025 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +package types + +import ( + "errors" + "io" + "strconv" + + "github.com/99designs/gqlgen/graphql" + "github.com/getprobo/probo/pkg/probo/coredata/gid" +) + +func MarshalGIDScalar(id gid.GID) graphql.Marshaler { + return graphql.WriterFunc(func(w io.Writer) { + w.Write([]byte(strconv.Quote(id.String()))) + }) +} + +func UnmarshalGIDScalar(v interface{}) (gid.GID, error) { + s, ok := v.(string) + if !ok { + return gid.Nil, errors.New("must be a string") + } + + id, err := gid.ParseGID(s) + if err != nil { + return gid.Nil, err + } + + return id, nil +} diff --git a/pkg/api/console/v1/types/types.go b/pkg/api/console/v1/types/types.go index 05c223787..317a918b0 100644 --- a/pkg/api/console/v1/types/types.go +++ b/pkg/api/console/v1/types/types.go @@ -5,16 +5,17 @@ package types import ( "time" + "github.com/getprobo/probo/pkg/probo/coredata/gid" "github.com/getprobo/probo/pkg/probo/coredata/page" ) type Node interface { IsNode() - GetID() string + GetID() gid.GID } type Control struct { - ID string `json:"id"` + ID gid.GID `json:"id"` Name string `json:"name"` Description string `json:"description"` Tasks *TaskConnection `json:"tasks"` @@ -22,8 +23,8 @@ type Control struct { UpdatedAt time.Time `json:"updatedAt"` } -func (Control) IsNode() {} -func (this Control) GetID() string { return this.ID } +func (Control) IsNode() {} +func (this Control) GetID() gid.GID { return this.ID } type ControlConnection struct { Edges []*ControlEdge `json:"edges"` @@ -36,7 +37,7 @@ type ControlEdge struct { } type Framework struct { - ID string `json:"id"` + ID gid.GID `json:"id"` Name string `json:"name"` Description string `json:"description"` Controls *ControlConnection `json:"controls"` @@ -44,8 +45,8 @@ type Framework struct { UpdatedAt time.Time `json:"updatedAt"` } -func (Framework) IsNode() {} -func (this Framework) GetID() string { return this.ID } +func (Framework) IsNode() {} +func (this Framework) GetID() gid.GID { return this.ID } type FrameworkConnection struct { Edges []*FrameworkEdge `json:"edges"` @@ -58,15 +59,15 @@ type FrameworkEdge struct { } type Organization struct { - ID string `json:"id"` + ID gid.GID `json:"id"` Name string `json:"name"` Frameworks *FrameworkConnection `json:"frameworks"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` } -func (Organization) IsNode() {} -func (this Organization) GetID() string { return this.ID } +func (Organization) IsNode() {} +func (this Organization) GetID() gid.GID { return this.ID } type PageInfo struct { HasNextPage bool `json:"hasNextPage"` @@ -79,14 +80,14 @@ type Query struct { } type Task struct { - ID string `json:"id"` + ID gid.GID `json:"id"` Name string `json:"name"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` } -func (Task) IsNode() {} -func (this Task) GetID() string { return this.ID } +func (Task) IsNode() {} +func (this Task) GetID() gid.GID { return this.ID } type TaskConnection struct { Edges []*TaskEdge `json:"edges"` diff --git a/pkg/api/console/v1/v1_resolver.go b/pkg/api/console/v1/v1_resolver.go index ddc0b4956..564fa8004 100644 --- a/pkg/api/console/v1/v1_resolver.go +++ b/pkg/api/console/v1/v1_resolver.go @@ -10,6 +10,7 @@ import ( "github.com/getprobo/probo/pkg/api/console/v1/schema" "github.com/getprobo/probo/pkg/api/console/v1/types" + "github.com/getprobo/probo/pkg/probo/coredata/gid" "github.com/getprobo/probo/pkg/probo/coredata/page" ) @@ -50,7 +51,7 @@ func (r *organizationResolver) Frameworks(ctx context.Context, obj *types.Organi } // Node is the resolver for the node field. -func (r *queryResolver) Node(ctx context.Context, id string) (types.Node, error) { +func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error) { panic(fmt.Errorf("not implemented: Node - node")) } diff --git a/pkg/probo/coredata/control.go b/pkg/probo/coredata/control.go index aff119927..e327773d5 100644 --- a/pkg/probo/coredata/control.go +++ b/pkg/probo/coredata/control.go @@ -62,7 +62,7 @@ func (c *Controls) LoadByFrameworkID( ctx context.Context, conn pg.Conn, scope *Scope, - frameworkID string, + frameworkID gid.GID, cursor *page.Cursor, ) error { q := ` diff --git a/pkg/probo/coredata/framework.go b/pkg/probo/coredata/framework.go index 67c0a1070..a329c5dbe 100644 --- a/pkg/probo/coredata/framework.go +++ b/pkg/probo/coredata/framework.go @@ -60,7 +60,7 @@ func (f *Frameworks) LoadByOrganizationID( ctx context.Context, conn pg.Conn, scope *Scope, - organizationID string, + organizationID gid.GID, cursor *page.Cursor, ) error { q := ` @@ -115,7 +115,7 @@ func (f *Framework) LoadByID( ctx context.Context, conn pg.Conn, scope *Scope, - frameworkID string, + frameworkID gid.GID, ) error { q := ` SELECT diff --git a/pkg/probo/coredata/gid/gid.go b/pkg/probo/coredata/gid/gid.go index b6b49283c..4d0a29813 100644 --- a/pkg/probo/coredata/gid/gid.go +++ b/pkg/probo/coredata/gid/gid.go @@ -27,15 +27,30 @@ type ( GID uuid.UUID ) +var ( + Nil = GID(uuid.Nil) +) + +func ParseGID(encoded string) (GID, error) { + gid := GID{} + + err := gid.UnmarshalText([]byte(encoded)) + if err != nil { + return Nil, err + } + + return gid, nil +} + func NewGID(et uint32) (GID, error) { id, err := uuid.NewV7() if err != nil { - return GID(uuid.Nil), err + return Nil, err } binary.BigEndian.PutUint32(id[10:14], et) - return GID(id), nil + return Nil, nil } func (gid GID) Value() (driver.Value, error) { diff --git a/pkg/probo/coredata/organization.go b/pkg/probo/coredata/organization.go index 76422735e..9901c37a3 100644 --- a/pkg/probo/coredata/organization.go +++ b/pkg/probo/coredata/organization.go @@ -47,7 +47,7 @@ func (o *Organization) LoadByID( ctx context.Context, conn pg.Conn, scope *Scope, - organizationID string, + organizationID gid.GID, ) error { q := ` SELECT diff --git a/pkg/probo/coredata/task.go b/pkg/probo/coredata/task.go index cd79bd36d..7a59b46f5 100644 --- a/pkg/probo/coredata/task.go +++ b/pkg/probo/coredata/task.go @@ -58,7 +58,7 @@ func (t *Tasks) LoadByControlID( ctx context.Context, conn pg.Conn, scope *Scope, - controlID string, + controlID gid.GID, cursor *page.Cursor, ) error { q := ` diff --git a/pkg/probo/probo.go b/pkg/probo/probo.go index 61cd22132..61c4cd7ed 100644 --- a/pkg/probo/probo.go +++ b/pkg/probo/probo.go @@ -18,6 +18,7 @@ import ( "context" "github.com/getprobo/probo/pkg/probo/coredata" + "github.com/getprobo/probo/pkg/probo/coredata/gid" "github.com/getprobo/probo/pkg/probo/coredata/page" "go.gearno.de/kit/pg" ) @@ -38,7 +39,7 @@ func NewService(ctx context.Context, pgClient *pg.Client) *Service { func (s *Service) GetOrganization( ctx context.Context, - organizationID string, + organizationID gid.GID, ) (*coredata.Organization, error) { organization := &coredata.Organization{} @@ -63,7 +64,7 @@ func (s *Service) GetOrganization( func (s *Service) ListOrganizationFrameworks( ctx context.Context, - organizationID string, + organizationID gid.GID, cursor *page.Cursor, ) (*page.Page[*coredata.Framework], error) { var frameworks coredata.Frameworks @@ -90,7 +91,7 @@ func (s *Service) ListOrganizationFrameworks( func (s *Service) ListFrameworkControls( ctx context.Context, - frameworkID string, + frameworkID gid.GID, cursor *page.Cursor, ) (*page.Page[*coredata.Control], error) { var controls coredata.Controls @@ -117,7 +118,7 @@ func (s *Service) ListFrameworkControls( func (s *Service) ListControlTasks( ctx context.Context, - controlID string, + controlID gid.GID, cursor *page.Cursor, ) (*page.Page[*coredata.Task], error) { var tasks coredata.Tasks