From b8c31ec9ab4197e4be741fdeb0c2a079963786df Mon Sep 17 00:00:00 2001 From: Subhrajyoti Date: Tue, 20 Dec 2022 11:43:55 +0530 Subject: [PATCH] Add comments and unittests to go-sqlcommenter-core (#218) --- go/core/core.go | 35 +++++++++++---- go/core/core_test.go | 101 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+), 9 deletions(-) create mode 100644 go/core/core_test.go diff --git a/go/core/core.go b/go/core/core.go index c2d30696..5a35e61a 100644 --- a/go/core/core.go +++ b/go/core/core.go @@ -22,20 +22,23 @@ import ( "runtime" "sort" "strings" - - "go.opentelemetry.io/otel/propagation" ) +// Constants used as key string for tags. +// It is not necessary that all SQLCommenter frameworks/ORMs will contain all these keys i.e. +// it is on best-effort basis. const ( Route string = "route" - Controller string = "controller" - Action string = "action" - Framework string = "framework" - Driver string = "db_driver" - Traceparent string = "traceparent" - Application string = "application" + Controller = "controller" + Action = "action" + Framework = "framework" + Driver = "db_driver" + Traceparent = "traceparent" + Application = "application" ) +// CommenterConfig contains configurations for SQLCommenter library. +// We can enable and disable certain tags by enabling these configurations. type CommenterConfig struct { EnableDBDriver bool EnableRoute bool @@ -46,11 +49,15 @@ type CommenterConfig struct { EnableApplication bool } +// StaticTags are few tags that can be set by the application and will be constant +// for every API call. type StaticTags struct { Application string DriverName string } +// CommenterOptions contains all options regarding SQLCommenter library. +// This includes the configurations as well as any static tags. type CommenterOptions struct { Config CommenterConfig Tags StaticTags @@ -60,13 +67,19 @@ func encodeURL(k string) string { return url.QueryEscape(k) } -func GetFunctionName(i interface{}) string { +// GetFunctionName returns the name of the function passed. +func GetFunctionName(i any) string { if i == nil { return "" } return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name() } +// ConvertMapToComment returns a comment string given a map of key-value pairs of tags. +// There are few steps involved here: +// - Sorting the tags by key string +// - url encoding the key value pairs +// - Formatting the key value pairs as "key1=value1,key2=value2" format. func ConvertMapToComment(tags map[string]string) string { var sb strings.Builder i, sz := 0, len(tags) @@ -89,6 +102,7 @@ func ConvertMapToComment(tags map[string]string) string { return sb.String() } +// ExtractTraceparent extracts the traceparent field using OpenTelemetry library. func ExtractTraceparent(ctx context.Context) propagation.MapCarrier { // Serialize the context into carrier textMapPropogator := propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}) @@ -97,12 +111,15 @@ func ExtractTraceparent(ctx context.Context) propagation.MapCarrier { return carrier } +// RequestTagsProvider adds a basic interface for other libraries like gorilla/mux to implement. type RequestTagsProvider interface { Route() string Action() string Framework() string } +// ContextInject injects the tags key-value pairs into context, +// which can be later passed into drivers/ORMs to finally inject them into SQL queries. func ContextInject(ctx context.Context, h RequestTagsProvider) context.Context { ctx = context.WithValue(ctx, Route, h.Route()) ctx = context.WithValue(ctx, Action, h.Action()) diff --git a/go/core/core_test.go b/go/core/core_test.go new file mode 100644 index 00000000..fb33c515 --- /dev/null +++ b/go/core/core_test.go @@ -0,0 +1,101 @@ +package core + +import ( + "context" + "testing" +) + +func TestConvertMapToComment(t *testing.T) { + for _, tc := range []struct { + desc string + tagMap map[string]string + want string + }{ + { + desc: "nil tagMap", + want: "", + }, + { + desc: "no tags", + tagMap: map[string]string{}, + want: "", + }, + { + desc: "only one tag", + tagMap: map[string]string{ + Route: "test-route", + }, + want: "route='test-route'", + }, + { + desc: "only one tag with url encoding", + tagMap: map[string]string{ + Route: "test/route", + }, + want: "route='test%2Froute'", + }, + { + desc: "multiple tags", + tagMap: map[string]string{ + Route: "test/route", + Action: "test-action", + Driver: "sql-pg", + }, + want: "action='test-action',db_driver='sql-pg',route='test%2Froute'", + }, + } { + t.Run(tc.desc, func(t *testing.T) { + if got, want := ConvertMapToComment(tc.tagMap), tc.want; got != want { + t.Errorf("ConvertMapToComment(%+v) = %q, want = %q", tc.tagMap, got, want) + } + }) + } +} + +type testRequestProvider struct { + withRoute string + withAction string + withFramework string +} + +func (p *testRequestProvider) Route() string { return p.withRoute } +func (p *testRequestProvider) Action() string { return p.withAction } +func (p *testRequestProvider) Framework() string { return p.withFramework } + +func TestContextInject(t *testing.T) { + tagsProvider := &testRequestProvider{ + withRoute: "test-route", + withAction: "test-action", + withFramework: "test-framework", + } + ctx := context.Background() + gotCtx := ContextInject(ctx, tagsProvider) + + for _, tc := range []struct { + desc string + key string + want string + }{ + { + desc: "fetch action", + key: Action, + want: "test-action", + }, + { + desc: "fetch route", + key: Route, + want: "test-route", + }, + { + desc: "fetch framework", + key: Framework, + want: "test-framework", + }, + } { + t.Run(tc.desc, func(t *testing.T) { + if got, want := gotCtx.Value(tc.key), tc.want; got != want { + t.Errorf("ContextInject(ctx, tagsProvider) context.Value(%q) = %q, want = %q", tc.key, got, tc.want) + } + }) + } +}