From 119d9affbd6c64901d7b01f43d4a0ef21ef47409 Mon Sep 17 00:00:00 2001 From: Ravi Suhag Date: Tue, 21 Apr 2026 11:26:47 -0500 Subject: [PATCH 1/3] fix: migrate logging from zap to slog Replace zap logger with Go's standard log/slog throughout the codebase, establishing a single consistent logging approach. - pkg/logger: replace zap init with slog JSON handler + configurable level - pkg/logger/pgx.go: new slog adapter for pgx database logger with allowlisted safe keys to avoid logging sensitive bind arguments - config: add LogLevel field (debug/info/warn/error) - postgres: use slog adapter with warn-level DB logging - formats/json: replace zap warns with structured slog warns - middleware/recovery: use slog instead of stdlib log - server: use logger.Init() and slog throughout Closes #178 --- config/config.go | 1 + formats/json/compatibility.go | 5 +-- formats/json/schema.go | 7 ++-- go.mod | 23 +++++------ go.sum | 64 ++++++++++++++--------------- internal/middleware/recovery.go | 4 +- internal/server/server.go | 19 +++++---- internal/store/postgres/postgres.go | 11 ++--- pkg/logger/logger.go | 31 +++++++++----- pkg/logger/pgx.go | 48 ++++++++++++++++++++++ 10 files changed, 135 insertions(+), 78 deletions(-) create mode 100644 pkg/logger/pgx.go diff --git a/config/config.go b/config/config.go index d3f50b91..0afaafa6 100644 --- a/config/config.go +++ b/config/config.go @@ -17,6 +17,7 @@ type Config struct { Port string `default:"8080"` // Timeout represents graceful shutdown period. Defaults to 60 seconds. Timeout time.Duration `default:"60s"` + LogLevel string `default:"info"` CacheSizeInMB int64 `default:"100"` MaxRecvMsgSize int `default:"10485760"` // 10 MB MaxSendMsgSize int `default:"10485760"` // 10 MB diff --git a/formats/json/compatibility.go b/formats/json/compatibility.go index de0615e5..aa7f0048 100644 --- a/formats/json/compatibility.go +++ b/formats/json/compatibility.go @@ -1,9 +1,8 @@ package json import ( - "fmt" + "log/slog" - "github.com/raystack/stencil/pkg/logger" "github.com/santhosh-tekuri/jsonschema/v5" ) @@ -156,7 +155,7 @@ func TypeCheckExecutor(spec TypeCheckSpec) SchemaCompareCheck { case "null": default: - logger.Logger.Warn(fmt.Sprintf("Unexpected type %s", schemaTypes)) + slog.Warn("unexpected schema type", "type", schemaTypes) } } } diff --git a/formats/json/schema.go b/formats/json/schema.go index 49f89d3b..e28346f3 100644 --- a/formats/json/schema.go +++ b/formats/json/schema.go @@ -1,9 +1,10 @@ package json import ( + "log/slog" + "github.com/google/uuid" "github.com/raystack/stencil/core/schema" - "github.com/raystack/stencil/pkg/logger" "github.com/santhosh-tekuri/jsonschema/v5" _ "github.com/santhosh-tekuri/jsonschema/v5/httploader" // imported to compile http references in json schema "go.uber.org/multierr" @@ -32,12 +33,12 @@ func (s *Schema) GetCanonicalValue() *schema.SchemaFile { func (s *Schema) IsBackwardCompatible(against schema.ParsedSchema) error { sc, err := jsonschema.CompileString(schemaURI, string(s.data)) if err != nil { - logger.Logger.Warn("unable to compile schema to check for backward compatibility") + slog.Warn("unable to compile schema to check for backward compatibility", "error", err) return err } againstSchema, err := jsonschema.CompileString(schemaURI, string(against.GetCanonicalValue().Data)) if err != nil { - logger.Logger.Warn("unable to compile against schema to check for backward compatibility") + slog.Warn("unable to compile against schema to check for backward compatibility", "error", err) return err } jsonSchemaMap := exploreSchema(sc) diff --git a/go.mod b/go.mod index 0d733b67..1c1faf06 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/raystack/stencil -go 1.24.0 +go 1.25 require ( buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260415201107-50325440f8f2.1 @@ -28,14 +28,13 @@ require ( github.com/stretchr/testify v1.11.1 github.com/yudai/gojsondiff v1.0.0 go.uber.org/multierr v1.11.0 - go.uber.org/zap v1.27.0 - golang.org/x/net v0.37.0 + golang.org/x/net v0.49.0 google.golang.org/protobuf v1.36.11 ) require ( buf.build/go/protovalidate v1.0.0 // indirect - cel.dev/expr v0.24.0 // indirect + cel.dev/expr v0.25.1 // indirect github.com/AlecAivazis/survey/v2 v2.3.7 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/alecthomas/chroma/v2 v2.15.0 // indirect @@ -116,16 +115,16 @@ require ( github.com/yudai/pp v2.0.1+incompatible // indirect github.com/yuin/goldmark v1.7.8 // indirect github.com/yuin/goldmark-emoji v1.0.5 // indirect - go.opentelemetry.io/otel/metric v1.34.0 // indirect - go.opentelemetry.io/otel/trace v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect go.uber.org/atomic v1.11.0 // indirect - golang.org/x/crypto v0.36.0 // indirect + golang.org/x/crypto v0.47.0 // indirect golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/term v0.30.0 // indirect - golang.org/x/text v0.29.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250922171735-9219d122eba9 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250922171735-9219d122eba9 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/term v0.39.0 // indirect + golang.org/x/text v0.33.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect + google.golang.org/grpc v1.80.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index c77afc1f..4f0fcd7a 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-202604152011 buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260415201107-50325440f8f2.1/go.mod h1:tvtbpgaVXZX4g6Pn+AnzFycuRK3MOz5HJfEGeEllXYM= buf.build/go/protovalidate v1.0.0 h1:IAG1etULddAy93fiBsFVhpj7es5zL53AfB/79CVGtyY= buf.build/go/protovalidate v1.0.0/go.mod h1:KQmEUrcQuC99hAw+juzOEAmILScQiKBP1Oc36vvCLW8= -cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= -cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= +cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= connectrpc.com/connect v1.19.2 h1:McQ83FGdzL+t60peksi0gXC7MQ/iLKgLduAnThbM0mo= connectrpc.com/connect v1.19.2/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w= connectrpc.com/cors v0.1.0 h1:f3gTXJyDZPrDIZCQ567jxfD9PAIpopHiRDnJRt3QuOQ= @@ -115,8 +115,8 @@ github.com/georgysavva/scany v1.2.3 h1:yaEtl1B2i3qjCIsmLchSrcw2MxktvK+N0oi7uzYyq github.com/georgysavva/scany v1.2.3/go.mod h1:vGBpL5XRLOocMFFa55pj0P04DrL3I7qKVRL49K6Eu5o= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -413,24 +413,22 @@ github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRla github.com/yuin/goldmark-emoji v1.0.5 h1:EMVWyCGPlXJfUXBXpuMu+ii3TIaxbVBnEX9uaDC4cIk= github.com/yuin/goldmark-emoji v1.0.5/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= -go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= @@ -440,8 +438,6 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -457,8 +453,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU= golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -475,13 +471,13 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -503,16 +499,16 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -524,8 +520,8 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -543,12 +539,12 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20250922171735-9219d122eba9 h1:jm6v6kMRpTYKxBRrDkYAitNJegUeO1Mf3Kt80obv0gg= -google.golang.org/genproto/googleapis/api v0.0.0-20250922171735-9219d122eba9/go.mod h1:LmwNphe5Afor5V3R5BppOULHOnt2mCIf+NxMd4XiygE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250922171735-9219d122eba9 h1:V1jCN2HBa8sySkR5vLcCSqJSTMv093Rw9EJefhQGP7M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250922171735-9219d122eba9/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ= -google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= -google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516 h1:vmC/ws+pLzWjj/gzApyoZuSVrDtF1aod4u/+bbj8hgM= +google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:p3MLuOwURrGBRoEyFHBT3GjUwaCQVKeNqqWxlcISGdw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= +google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/middleware/recovery.go b/internal/middleware/recovery.go index a292028c..020bc9f4 100644 --- a/internal/middleware/recovery.go +++ b/internal/middleware/recovery.go @@ -3,7 +3,7 @@ package middleware import ( "context" "fmt" - "log" + "log/slog" "runtime/debug" "connectrpc.com/connect" @@ -16,7 +16,7 @@ func Recovery() connect.UnaryInterceptorFunc { defer func() { if r := recover(); r != nil { stack := debug.Stack() - log.Printf("panic recovered: %v\n%s", r, string(stack)) + slog.ErrorContext(ctx, "panic recovered", "error", r, "stack", string(stack)) err = connect.NewError( connect.CodeInternal, fmt.Errorf("internal server error"), diff --git a/internal/server/server.go b/internal/server/server.go index 14bd5a89..77ddc295 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "log" "log/slog" "net/http" "os" @@ -27,6 +26,7 @@ import ( "github.com/raystack/stencil/internal/api" "github.com/raystack/stencil/internal/middleware" "github.com/raystack/stencil/internal/store/postgres" + "github.com/raystack/stencil/pkg/logger" "github.com/raystack/stencil/ui" "github.com/rs/cors" "golang.org/x/net/http2" @@ -35,7 +35,7 @@ import ( // Start Entry point to start the server func Start(cfg config.Config) { - logger := slog.Default().With("component", "server") + logger.Init(cfg.LogLevel) db := postgres.NewStore(cfg.DB.ConnectionString) defer db.Close() @@ -99,7 +99,8 @@ func Start(cfg config.Config) { // UI SPA handler spaHandler, err := spa.Handler(ui.Assets, "build", "index.html", false) if err != nil { - log.Fatalln("Failed to load spa:", err) + slog.Error("failed to load spa", "error", err) + os.Exit(1) } mux.Handle("/ui/", http.StripPrefix("/ui", spaHandler)) @@ -127,9 +128,10 @@ func Start(cfg config.Config) { // Start server in goroutine go func() { - logger.Info("starting server", "addr", addr) + slog.Info("starting server", "addr", addr) if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { - log.Fatalf("server error: %v", err) + slog.Error("server error", "error", err) + os.Exit(1) } }() @@ -138,12 +140,13 @@ func Start(cfg config.Config) { signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit - logger.Info("shutting down server") + slog.Info("shutting down server") ctx, cancel := context.WithTimeout(context.Background(), cfg.Timeout) defer cancel() if err := server.Shutdown(ctx); err != nil { - log.Fatalf("server forced to shutdown: %v", err) + slog.Error("server forced to shutdown", "error", err) + os.Exit(1) } - logger.Info("server stopped") + slog.Info("server stopped") } diff --git a/internal/store/postgres/postgres.go b/internal/store/postgres/postgres.go index 3649ff12..6a59f35b 100644 --- a/internal/store/postgres/postgres.go +++ b/internal/store/postgres/postgres.go @@ -4,7 +4,7 @@ import ( "context" "embed" "fmt" - "log" + "log/slog" "net/http" "github.com/golang-migrate/migrate/v4" @@ -12,7 +12,6 @@ import ( "github.com/golang-migrate/migrate/v4/source/httpfs" "github.com/jackc/pgconn" "github.com/jackc/pgx/v4" - "github.com/jackc/pgx/v4/log/zapadapter" "github.com/jackc/pgx/v4/pgxpool" "github.com/pkg/errors" "github.com/raystack/stencil/internal/store" @@ -34,11 +33,13 @@ type DB struct { // NewStore create a postgres store func NewStore(conn string) *DB { cc, _ := pgxpool.ParseConfig(conn) - cc.ConnConfig.Logger = zapadapter.NewLogger(logger.Logger) + cc.ConnConfig.Logger = &logger.PgxLogger{} + cc.ConnConfig.LogLevel = pgx.LogLevelWarn pgxPool, err := pgxpool.ConnectConfig(context.Background(), cc) if err != nil { - log.Fatal(err) + slog.Error("failed to connect to database", "error", err) + panic(err) } return &DB{Pool: pgxPool} @@ -59,7 +60,7 @@ func Migrate(connURL string) error { if err != nil { return errors.Wrap(err, "db migrator") } - defer m.Close() + defer func() { _, _ = m.Close() }() if err := m.Up(); err != nil && err != migrate.ErrNoChange { return errors.Wrap(err, "db migrator") diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index 8b013f72..2c70cf58 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -1,18 +1,27 @@ package logger import ( - "log" - - "go.uber.org/zap" + "log/slog" + "os" + "strings" ) -// Logger zap logger instance -var Logger *zap.Logger - -func init() { - l, err := zap.NewProduction() - if err != nil { - log.Fatalln(err) +// Init sets up the global slog logger with a JSON handler. +func Init(logLevel string) { + var level slog.LevelVar + switch strings.ToLower(logLevel) { + case "debug": + level.Set(slog.LevelDebug) + case "warn", "warning": + level.Set(slog.LevelWarn) + case "error": + level.Set(slog.LevelError) + default: + level.Set(slog.LevelInfo) } - Logger = l + logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{ + Level: &level, + AddSource: true, + })) + slog.SetDefault(logger) } diff --git a/pkg/logger/pgx.go b/pkg/logger/pgx.go new file mode 100644 index 00000000..4957a47a --- /dev/null +++ b/pkg/logger/pgx.go @@ -0,0 +1,48 @@ +package logger + +import ( + "context" + "log/slog" + + "github.com/jackc/pgx/v4" +) + +// safeKeys are pgx log data keys that are safe to log without redaction. +var safeKeys = map[string]bool{ + "sql": true, + "commandTag": true, + "time": true, + "rowCount": true, + "pid": true, + "err": true, + "host": true, + "port": true, + "database": true, + "command_tag": true, +} + +// PgxLogger adapts slog to the pgx.Logger interface. +type PgxLogger struct{} + +// Log implements the pgx.Logger interface using slog. +func (l *PgxLogger) Log(ctx context.Context, level pgx.LogLevel, msg string, data map[string]interface{}) { + attrs := make([]slog.Attr, 0, len(data)) + for k, v := range data { + if safeKeys[k] { + attrs = append(attrs, slog.Any(k, v)) + } + } + + switch level { + case pgx.LogLevelTrace, pgx.LogLevelDebug: + slog.LogAttrs(ctx, slog.LevelDebug, msg, attrs...) + case pgx.LogLevelInfo: + slog.LogAttrs(ctx, slog.LevelInfo, msg, attrs...) + case pgx.LogLevelWarn: + slog.LogAttrs(ctx, slog.LevelWarn, msg, attrs...) + case pgx.LogLevelError: + slog.LogAttrs(ctx, slog.LevelError, msg, attrs...) + default: + slog.LogAttrs(ctx, slog.LevelInfo, msg, attrs...) + } +} From 0c25425bba2b17bff646c1389104ae37c1593773 Mon Sep 17 00:00:00 2001 From: Ravi Suhag Date: Tue, 21 Apr 2026 11:27:03 -0500 Subject: [PATCH 2/3] fix: cache invalidation on delete and not-found errors - Invalidate ristretto cache entries when schemas/versions are deleted, preventing stale data from being served after recreation - Return not-found error when deleting non-existent namespaces, schemas, or versions by checking RowsAffected - Check ListVersions error before proceeding with schema delete - Log encode errors instead of attempting http.Error after WriteHeader - Add Del method to Cache interface Fixes #180 Fixes #145 --- core/schema/mocks/schema_cache.go | 5 ++++ core/schema/schema.go | 9 ++++++ core/schema/service.go | 29 +++++++++++++++++-- internal/api/api.go | 13 ++++++--- .../store/postgres/namespace_repository.go | 15 ++++++++-- internal/store/postgres/schema_repository.go | 29 +++++++++++++++---- 6 files changed, 85 insertions(+), 15 deletions(-) diff --git a/core/schema/mocks/schema_cache.go b/core/schema/mocks/schema_cache.go index 333a1135..57821937 100644 --- a/core/schema/mocks/schema_cache.go +++ b/core/schema/mocks/schema_cache.go @@ -50,6 +50,11 @@ func (_m *SchemaCache) Set(_a0 interface{}, _a1 interface{}, _a2 int64) bool { return r0 } +// Del provides a mock function with given fields: _a0 +func (_m *SchemaCache) Del(_a0 interface{}) { + _m.Called(_a0) +} + // NewSchemaCache creates a new instance of SchemaCache. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. func NewSchemaCache(t testing.TB) *SchemaCache { mock := &SchemaCache{} diff --git a/core/schema/schema.go b/core/schema/schema.go index 69eb2671..de5a982c 100644 --- a/core/schema/schema.go +++ b/core/schema/schema.go @@ -2,18 +2,21 @@ package schema import "context" +// Metadata represents schema metadata such as format and compatibility. type Metadata struct { Authority string Format string Compatibility string } +// SchemaInfo represents version and location information for a schema. type SchemaInfo struct { ID string `json:"id"` Version int32 `json:"version"` Location string `json:"location"` } +// SchemaFile represents a schema's data along with its extracted types and fields. type SchemaFile struct { ID string Types []string @@ -21,6 +24,7 @@ type SchemaFile struct { Data []byte } +// Repository defines the persistence interface for schemas. type Repository interface { Create(ctx context.Context, namespace string, schema string, metadata *Metadata, versionID string, schemaFile *SchemaFile) (version int32, err error) List(context.Context, string) ([]Schema, error) @@ -33,6 +37,7 @@ type Repository interface { DeleteVersion(context.Context, string, string, int32) error } +// ParsedSchema defines the interface for a parsed schema with compatibility checks. type ParsedSchema interface { IsBackwardCompatible(ParsedSchema) error IsForwardCompatible(ParsedSchema) error @@ -41,15 +46,19 @@ type ParsedSchema interface { GetCanonicalValue() *SchemaFile } +// Provider defines the interface for parsing raw schema data into a ParsedSchema. type Provider interface { ParseSchema(format string, data []byte) (ParsedSchema, error) } +// Cache defines a key-value cache interface for storing schema data. type Cache interface { Get(interface{}) (interface{}, bool) Set(interface{}, interface{}, int64) bool + Del(interface{}) } +// Schema represents a schema entity with its configuration. type Schema struct { Name string Format string diff --git a/core/schema/service.go b/core/schema/service.go index 65af2761..d22fe066 100644 --- a/core/schema/service.go +++ b/core/schema/service.go @@ -10,6 +10,7 @@ import ( "github.com/raystack/stencil/internal/store" ) +// NewService creates a new schema service with the given dependencies. func NewService(repo Repository, provider Provider, nsSvc NamespaceService, cache Cache) *Service { return &Service{ repo: repo, @@ -23,6 +24,7 @@ type NamespaceService interface { Get(ctx context.Context, name string) (namespace.Namespace, error) } +// Service provides schema management operations. type Service struct { provider Provider repo Repository @@ -75,6 +77,7 @@ func (s *Service) checkCompatibility(ctx context.Context, nsName, schemaName, fo return checkerFn(current, []ParsedSchema{prevSchema}) } +// Create validates, parses, and stores a new schema version. func (s *Service) Create(ctx context.Context, nsName string, schemaName string, metadata *Metadata, data []byte) (SchemaInfo, error) { var scInfo SchemaInfo ns, err := s.namespaceService.Get(ctx, nsName) @@ -114,18 +117,36 @@ func (s *Service) withMetadata(ctx context.Context, namespace, schemaName string return meta, data, err } +// Get retrieves a specific schema version by namespace, name, and version number. func (s *Service) Get(ctx context.Context, namespace string, schemaName string, version int32) (*Metadata, []byte, error) { return s.withMetadata(ctx, namespace, schemaName, func() ([]byte, error) { return s.cachedGetSchema(ctx, namespace, schemaName, version) }) } +// Delete removes a schema and all its versions, and invalidates cached entries. func (s *Service) Delete(ctx context.Context, namespace string, schemaName string) error { - return s.repo.Delete(ctx, namespace, schemaName) + versions, err := s.repo.ListVersions(ctx, namespace, schemaName) + if err != nil { + return err + } + if err := s.repo.Delete(ctx, namespace, schemaName); err != nil { + return err + } + for _, v := range versions { + s.cache.Del(schemaKeyFunc(namespace, schemaName, v)) + } + return nil } +// DeleteVersion removes a specific version of a schema and invalidates the cached entry. func (s *Service) DeleteVersion(ctx context.Context, namespace string, schemaName string, version int32) error { - return s.repo.DeleteVersion(ctx, namespace, schemaName, version) + err := s.repo.DeleteVersion(ctx, namespace, schemaName, version) + if err == nil { + s.cache.Del(schemaKeyFunc(namespace, schemaName, version)) + } + return err } +// GetLatest retrieves the latest version of a schema. func (s *Service) GetLatest(ctx context.Context, namespace string, schemaName string) (*Metadata, []byte, error) { version, err := s.repo.GetLatestVersion(ctx, namespace, schemaName) if err != nil { @@ -134,18 +155,22 @@ func (s *Service) GetLatest(ctx context.Context, namespace string, schemaName st return s.Get(ctx, namespace, schemaName, version) } +// GetMetadata retrieves the metadata for a schema. func (s *Service) GetMetadata(ctx context.Context, namespace, schemaName string) (*Metadata, error) { return s.repo.GetMetadata(ctx, namespace, schemaName) } +// UpdateMetadata updates the metadata for a schema. func (s *Service) UpdateMetadata(ctx context.Context, namespace, schemaName string, meta *Metadata) (*Metadata, error) { return s.repo.UpdateMetadata(ctx, namespace, schemaName, meta) } +// List returns all schemas in a namespace. func (s *Service) List(ctx context.Context, namespaceID string) ([]Schema, error) { return s.repo.List(ctx, namespaceID) } +// ListVersions returns all version numbers for a schema. func (s *Service) ListVersions(ctx context.Context, namespaceID string, schemaName string) ([]int32, error) { return s.repo.ListVersions(ctx, namespaceID, schemaName) } diff --git a/internal/api/api.go b/internal/api/api.go index 3c8b5ef3..6547695d 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "io" + "log/slog" "net/http" "strconv" @@ -114,7 +115,9 @@ func (a *API) handleUploadSchema(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - json.NewEncoder(w).Encode(sc) + if err := json.NewEncoder(w).Encode(sc); err != nil { + slog.Error("failed to encode response", "error", err) + } } func (a *API) handleCheckCompatibility(w http.ResponseWriter, r *http.Request) { @@ -144,13 +147,15 @@ func writeSchemaResponse(w http.ResponseWriter, meta *schema.Metadata, data []by w.Header().Set("Content-Type", contentType) w.Header().Set("Content-Length", strconv.Itoa(len(data))) w.WriteHeader(http.StatusOK) - w.Write(data) + _, _ = w.Write(data) } func writeError(w http.ResponseWriter, statusCode int, msg string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(statusCode) - json.NewEncoder(w).Encode(map[string]string{"error": msg}) + if err := json.NewEncoder(w).Encode(map[string]string{"error": msg}); err != nil { + slog.Error("failed to encode error response", "error", err) + } } func writeServiceError(w http.ResponseWriter, err error) { @@ -158,6 +163,6 @@ func writeServiceError(w http.ResponseWriter, err error) { } func readBody(r *http.Request) ([]byte, error) { - defer r.Body.Close() + defer func() { _ = r.Body.Close() }() return io.ReadAll(r.Body) } diff --git a/internal/store/postgres/namespace_repository.go b/internal/store/postgres/namespace_repository.go index 78f4e989..9012ebcd 100644 --- a/internal/store/postgres/namespace_repository.go +++ b/internal/store/postgres/namespace_repository.go @@ -5,6 +5,7 @@ import ( "github.com/georgysavva/scany/pgxscan" "github.com/raystack/stencil/core/namespace" + "github.com/raystack/stencil/internal/store" ) const namespaceListQuery = ` @@ -60,9 +61,17 @@ func (r *NamespaceRepository) Get(ctx context.Context, id string) (namespace.Nam } func (r *NamespaceRepository) Delete(ctx context.Context, id string) error { - _, err := r.db.Exec(ctx, namespaceDeleteQuery, id) - r.db.Exec(ctx, deleteOrphanedData) - return wrapError(err, "%s", id) + ct, err := r.db.Exec(ctx, namespaceDeleteQuery, id) + if err != nil { + return wrapError(err, "%s", id) + } + if ct.RowsAffected() == 0 { + return store.NoRowsErr.WithErr(nil, id) + } + if _, execErr := r.db.Exec(ctx, deleteOrphanedData); execErr != nil { + return wrapError(execErr, "%s", id) + } + return nil } func (r *NamespaceRepository) List(ctx context.Context) ([]namespace.Namespace, error) { diff --git a/internal/store/postgres/schema_repository.go b/internal/store/postgres/schema_repository.go index 6f8b9642..1a370a09 100644 --- a/internal/store/postgres/schema_repository.go +++ b/internal/store/postgres/schema_repository.go @@ -7,6 +7,7 @@ import ( "github.com/jackc/pgx/v4" "github.com/pkg/errors" "github.com/raystack/stencil/core/schema" + "github.com/raystack/stencil/internal/store" ) type SchemaRepository struct { @@ -84,10 +85,18 @@ func (r *SchemaRepository) List(ctx context.Context, namespaceID string) ([]sche } func (r *SchemaRepository) Delete(ctx context.Context, ns string, sc string) error { - _, err := r.db.Exec(ctx, deleteSchemaQuery, ns, sc) + ct, err := r.db.Exec(ctx, deleteSchemaQuery, ns, sc) + if err != nil { + return wrapError(err, "delete schema") + } + if ct.RowsAffected() == 0 { + return store.NoRowsErr.WithErr(nil, "schema") + } // Idempotent operation to clean orphaned data. - r.db.Exec(ctx, deleteOrphanedData) - return wrapError(err, "delete schema") + if _, execErr := r.db.Exec(ctx, deleteOrphanedData); execErr != nil { + return wrapError(execErr, "delete schema") + } + return nil } func (r *SchemaRepository) ListVersions(ctx context.Context, ns string, sc string) ([]int32, error) { @@ -97,10 +106,18 @@ func (r *SchemaRepository) ListVersions(ctx context.Context, ns string, sc strin } func (r *SchemaRepository) DeleteVersion(ctx context.Context, ns string, sc string, version int32) error { - _, err := r.db.Exec(ctx, deleteVersionQuery, ns, sc, version) + ct, err := r.db.Exec(ctx, deleteVersionQuery, ns, sc, version) + if err != nil { + return wrapError(err, "delete version") + } + if ct.RowsAffected() == 0 { + return store.NoRowsErr.WithErr(nil, "version") + } // Idempotent operation to clean orphaned data. - r.db.Exec(ctx, deleteOrphanedData) - return wrapError(err, "delete version") + if _, execErr := r.db.Exec(ctx, deleteOrphanedData); execErr != nil { + return wrapError(execErr, "delete version") + } + return nil } const schemaInsertQuery = ` From 86b8a591b651adb3fee8e39c5177bc2e36a85239 Mon Sep 17 00:00:00 2001 From: Ravi Suhag Date: Tue, 21 Apr 2026 11:27:24 -0500 Subject: [PATCH 3/3] chore: upgrade CI workflows and fix all lint issues - Upgrade CI: actions/checkout@v6, actions/setup-go@v6, golangci-lint-action@v9, goreleaser-action@v7, setup-protoc@v3 - Use go-version-file instead of hardcoded Go versions - Add concurrency groups and path filters - Update golangci-lint config to v2 schema - Fix all errcheck issues (unchecked MarkFlagRequired, json.Encode, etc.) - Fix staticcheck issues (deprecated ioutil/rand, redundant nil checks) - Add comments on exported types --- .github/workflows/docs.yml | 8 ++-- .github/workflows/lint.yml | 56 ++++++++++++------------ .github/workflows/release-server.yml | 18 ++++---- .github/workflows/test-server.yaml | 37 +++++++++++----- .golangci.yaml | 38 +++++++--------- cmd/check.go | 6 +-- cmd/create.go | 4 +- cmd/delete.go | 2 +- cmd/diff.go | 10 +++-- cmd/download.go | 4 +- cmd/edit.go | 4 +- cmd/graph.go | 4 +- cmd/info.go | 4 +- cmd/list.go | 2 +- cmd/namespace.go | 6 +-- cmd/print.go | 20 ++++++--- config/build.go | 2 + config/load.go | 1 + core/namespace/namespace.go | 3 ++ core/namespace/service.go | 7 +++ core/schema/compatibility.go | 5 ++- core/search/search.go | 5 +++ core/search/service.go | 8 +++- formats/json/utils.go | 8 ++-- formats/protobuf/compatibility.go | 4 +- formats/protobuf/provider_test.go | 6 +-- internal/store/errors.go | 1 + internal/store/postgres/postgres_test.go | 6 ++- pkg/graph/graph.go | 2 + ui/embed.go | 1 + 30 files changed, 167 insertions(+), 115 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 05848892..450cab6c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,17 +1,19 @@ -name: docs +name: Docs on: push: branches: - main + paths-ignore: + - "**.md" workflow_dispatch: jobs: documentation: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v6 + - uses: actions/setup-node@v4 - name: Installation uses: bahmutov/npm-install@v1 with: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index bc784195..eaf9bf78 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,36 +1,34 @@ -name: "Lint" +name: Lint -on: [push, pull_request] +on: + push: + branches: + - main + paths-ignore: + - "**.md" + - "docs/**" + pull_request: + paths-ignore: + - "**.md" + - "docs/**" + +concurrency: + group: lint-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} jobs: golangci: - name: "Lint" - runs-on: "ubuntu-latest" + runs-on: ubuntu-latest steps: - - uses: actions/setup-go@v5 - - uses: actions/checkout@v4 - - name: Crete empty build directory - run: mkdir ui/build && touch ui/build/.gitkeep - - name: golangci-lint - uses: golangci/golangci-lint-action@v6 + - uses: actions/checkout@v6 + - uses: actions/setup-go@v6 with: - version: v1.64 - - codeql: - name: "Analyze with CodeQL" - runs-on: "ubuntu-latest" - permissions: - actions: "read" - contents: "read" - security-events: "write" - strategy: - fail-fast: false - matrix: - language: ["go"] - steps: - - uses: "actions/checkout@v2" - - uses: "github/codeql-action/init@v1" + go-version-file: "go.mod" + cache: true + - name: Create empty UI build directory + run: mkdir -p ui/build && touch ui/build/.gitkeep + - name: golangci-lint + uses: golangci/golangci-lint-action@v9 with: - languages: "${{ matrix.language }}" - - uses: "github/codeql-action/autobuild@v1" - - uses: "github/codeql-action/analyze@v1" + version: latest + args: --timeout=5m diff --git a/.github/workflows/release-server.yml b/.github/workflows/release-server.yml index 3b49498f..6a361f96 100644 --- a/.github/workflows/release-server.yml +++ b/.github/workflows/release-server.yml @@ -1,8 +1,9 @@ name: Release + on: push: tags: - - "v*.*.*" + - "v*" workflow_dispatch: inputs: goreleaserArgs: @@ -14,24 +15,25 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v6 with: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v6 with: - go-version: "1.20" + go-version-file: "go.mod" + cache: true - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v4 with: registry: docker.io username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v5 + uses: goreleaser/goreleaser-action@v7 with: distribution: goreleaser - version: v1.21.2 - args: --rm-dist ${{ inputs.goreleaserArgs }} + version: latest + args: release --clean ${{ inputs.goreleaserArgs }} env: GITHUB_TOKEN: ${{ secrets.GO_RELEASER_TOKEN }} diff --git a/.github/workflows/test-server.yaml b/.github/workflows/test-server.yaml index 731bb14c..9edc869a 100644 --- a/.github/workflows/test-server.yaml +++ b/.github/workflows/test-server.yaml @@ -4,15 +4,24 @@ on: push: branches: - main + paths-ignore: + - "**.md" + - "docs/**" pull_request: - workflow_dispatch: + paths-ignore: + - "**.md" + - "docs/**" + +concurrency: + group: test-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} jobs: test: runs-on: ubuntu-latest services: postgres: - image: postgres:13 + image: postgres:15 env: POSTGRES_HOST: localhost POSTGRES_USER: postgres @@ -26,18 +35,24 @@ jobs: ports: - 5432:5432 steps: - - name: Set up Go 1.x - uses: actions/setup-go@v4 + - uses: actions/checkout@v6 + - uses: actions/setup-go@v6 with: - go-version: ^1.20 - id: go + go-version-file: "go.mod" + cache: true + - name: Create empty UI build directory + run: mkdir -p ui/build && touch ui/build/.gitkeep - name: Install Protoc - uses: arduino/setup-protoc@v1 + uses: arduino/setup-protoc@v3 with: - version: "3.x" - - name: Check out code into the Go module directory - uses: actions/checkout@v2 - - name: Test + version: "25.x" + - name: Run Test run: make test env: TEST_DB_CONNECTIONSTRING: "postgres://postgres:postgres@localhost:5432/test_stencil_db?sslmode=disable" + - name: Install goveralls and send coverage + env: + COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + go install github.com/mattn/goveralls@latest + goveralls -coverprofile=coverage.out -service=github diff --git a/.golangci.yaml b/.golangci.yaml index 09cea2b1..fe51002a 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -1,35 +1,27 @@ +version: "2" run: timeout: 5m -output: - formats: - - format: colored-line-number +formatters: + enable: + - goimports + - gofmt linters: - enable-all: false - disable-all: true enable: - govet - - goimports - thelper - tparallel - unconvert - wastedassign - - revive - unused - - gofmt - whitespace - misspell -linters-settings: - revive: - ignore-generated-header: true - severity: warning -issues: - exclude-dirs: - - api/proto - - clients/java - - clients/js - - docs - - scripts - - ui - fix: true -severity: - default-severity: error + - errcheck + - staticcheck + exclusions: + paths: + - clients/java + - clients/js + - docs + - scripts + - ui + - gen diff --git a/cmd/check.go b/cmd/check.go index 5a9f1467..d9cb31c2 100644 --- a/cmd/check.go +++ b/cmd/check.go @@ -60,13 +60,13 @@ func checkSchemaCmd(cdk *CDK) *cobra.Command { } cmd.Flags().StringVarP(&namespaceID, "namespace", "n", "", "Parent namespace ID") - cmd.MarkFlagRequired("namespace") + _ = cmd.MarkFlagRequired("namespace") cmd.Flags().StringVarP(&comp, "comp", "c", "", "Schema compatibility") - cmd.MarkFlagRequired("comp") + _ = cmd.MarkFlagRequired("comp") cmd.Flags().StringVarP(&file, "file", "F", "", "Path to the schema file") - cmd.MarkFlagRequired("file") + _ = cmd.MarkFlagRequired("file") return cmd } diff --git a/cmd/create.go b/cmd/create.go index 0c19d0bb..ddd78c38 100644 --- a/cmd/create.go +++ b/cmd/create.go @@ -64,14 +64,14 @@ func createSchemaCmd(cdk *CDK) *cobra.Command { } cmd.Flags().StringVarP(&namespaceID, "namespace", "n", "", "Namespace ID") - cmd.MarkFlagRequired("namespace") + _ = cmd.MarkFlagRequired("namespace") cmd.Flags().StringVarP(&format, "format", "f", "", "Schema format") cmd.Flags().StringVarP(&comp, "comp", "c", "", "Schema compatibility") cmd.Flags().StringVarP(&file, "file", "F", "", "Path to the schema file") - cmd.MarkFlagRequired("file") + _ = cmd.MarkFlagRequired("file") return cmd } diff --git a/cmd/delete.go b/cmd/delete.go index 84dc007c..60231155 100644 --- a/cmd/delete.go +++ b/cmd/delete.go @@ -59,7 +59,7 @@ func deleteSchemaCmd(cdk *CDK) *cobra.Command { } cmd.Flags().StringVarP(&namespaceID, "namespace", "n", "", "Parent namespace ID") - cmd.MarkFlagRequired("namespace") + _ = cmd.MarkFlagRequired("namespace") cmd.Flags().Int32VarP(&version, "version", "v", 0, "Particular version to be deleted") diff --git a/cmd/diff.go b/cmd/diff.go index b82e4417..5148a0a8 100644 --- a/cmd/diff.go +++ b/cmd/diff.go @@ -123,7 +123,9 @@ func diffSchemaCmd(cdk *CDK) *cobra.Command { } var placeholder map[string]interface{} - json.Unmarshal(eJson, &placeholder) + if err := json.Unmarshal(eJson, &placeholder); err != nil { + return err + } config := formatter.AsciiFormatterConfig{ ShowArrayIndex: true, Coloring: true, @@ -147,11 +149,11 @@ func diffSchemaCmd(cdk *CDK) *cobra.Command { } cmd.Flags().StringVarP(&namespace, "namespace", "n", "", "Parent namespace ID") - cmd.MarkFlagRequired("namespace") + _ = cmd.MarkFlagRequired("namespace") cmd.Flags().Int32Var(&earlierVersion, "earlier-version", 0, "Earlier version of the schema") - cmd.MarkFlagRequired("earlier-version") + _ = cmd.MarkFlagRequired("earlier-version") cmd.Flags().Int32Var(&laterVersion, "later-version", 0, "Later version of the schema") - cmd.MarkFlagRequired("later-version") + _ = cmd.MarkFlagRequired("later-version") cmd.Flags().StringVar(&fullname, "fullname", "", "Only applicable for FORMAT_PROTO. fullname of proto schema eg: raystack.common.v1.Version") return cmd } diff --git a/cmd/download.go b/cmd/download.go index a45b55ce..78675ede 100644 --- a/cmd/download.go +++ b/cmd/download.go @@ -46,12 +46,12 @@ func downloadSchemaCmd(cdk *CDK) *cobra.Command { } cmd.Flags().StringVarP(&namespaceID, "namespace", "n", "", "Parent namespace ID") - cmd.MarkFlagRequired("namespace") + _ = cmd.MarkFlagRequired("namespace") cmd.Flags().Int32VarP(&version, "version", "v", 0, "Version of the schema") cmd.Flags().StringVarP(&output, "output", "o", "", "Path to the output file") - cmd.MarkFlagRequired("output") + _ = cmd.MarkFlagRequired("output") return cmd } diff --git a/cmd/edit.go b/cmd/edit.go index 69ac7fde..cf40b884 100644 --- a/cmd/edit.go +++ b/cmd/edit.go @@ -50,10 +50,10 @@ func editSchemaCmd(cdk *CDK) *cobra.Command { } cmd.Flags().StringVarP(&namespaceID, "namespace", "n", "", "Parent namespace ID") - cmd.MarkFlagRequired("namespace") + _ = cmd.MarkFlagRequired("namespace") cmd.Flags().StringVarP(&comp, "comp", "c", "", "Schema compatibility") - cmd.MarkFlagRequired("comp") + _ = cmd.MarkFlagRequired("comp") return cmd } diff --git a/cmd/graph.go b/cmd/graph.go index f8c73bbe..fdc274e7 100644 --- a/cmd/graph.go +++ b/cmd/graph.go @@ -5,8 +5,8 @@ import ( "os" "github.com/MakeNowJust/heredoc" - "github.com/raystack/stencil/pkg/graph" stencilv1beta1 "github.com/raystack/stencil/gen/raystack/stencil/v1beta1" + "github.com/raystack/stencil/pkg/graph" "github.com/spf13/cobra" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/descriptorpb" @@ -63,7 +63,7 @@ func graphSchemaCmd(cdk *CDK) *cobra.Command { } cmd.Flags().StringVarP(&namespaceID, "namespace", "n", "", "provide namespace/group or entity name") - cmd.MarkFlagRequired("namespace") + _ = cmd.MarkFlagRequired("namespace") cmd.Flags().Int32VarP(&version, "version", "v", 0, "provide version number") diff --git a/cmd/info.go b/cmd/info.go index 8ef9bfc2..66972d5e 100644 --- a/cmd/info.go +++ b/cmd/info.go @@ -59,7 +59,7 @@ func infoSchemaCmd(cdk *CDK) *cobra.Command { } cmd.Flags().StringVarP(&namespace, "namespace", "n", "", "Provide schema namespace") - cmd.MarkFlagRequired("namespace") + _ = cmd.MarkFlagRequired("namespace") return cmd } @@ -117,7 +117,7 @@ func versionSchemaCmd(cdk *CDK) *cobra.Command { } cmd.Flags().StringVarP(&namespaceID, "namespace", "n", "", "parent namespace ID") - cmd.MarkFlagRequired("namespace") + _ = cmd.MarkFlagRequired("namespace") return cmd } diff --git a/cmd/list.go b/cmd/list.go index 845c493f..7ecf7da1 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -76,7 +76,7 @@ func listSchemaCmd(cdk *CDK) *cobra.Command { } cmd.Flags().StringVarP(&namespace, "namespace", "n", "", "Namespace ID") - cmd.MarkFlagRequired("namespace") + _ = cmd.MarkFlagRequired("namespace") return cmd } diff --git a/cmd/namespace.go b/cmd/namespace.go index edda0eda..56eaadb0 100644 --- a/cmd/namespace.go +++ b/cmd/namespace.go @@ -219,13 +219,13 @@ func editNamespaceCmd(cdk *CDK) *cobra.Command { // TODO(Ravi) : Edit should not require all flags cmd.Flags().StringVarP(&format, "format", "f", "", "schema format") - cmd.MarkFlagRequired("format") + _ = cmd.MarkFlagRequired("format") cmd.Flags().StringVarP(&comp, "comp", "c", "", "schema compatibility") - cmd.MarkFlagRequired("comp") + _ = cmd.MarkFlagRequired("comp") cmd.Flags().StringVarP(&desc, "desc", "d", "", "description") - cmd.MarkFlagRequired("desc") + _ = cmd.MarkFlagRequired("desc") return cmd } diff --git a/cmd/print.go b/cmd/print.go index 3fe84f97..7da68d67 100644 --- a/cmd/print.go +++ b/cmd/print.go @@ -6,7 +6,7 @@ import ( "github.com/MakeNowJust/heredoc" "github.com/alecthomas/chroma/quick" - "github.com/jhump/protoreflect/desc" + "github.com/jhump/protoreflect/desc" //nolint:staticcheck "github.com/jhump/protoreflect/desc/protoprint" "github.com/raystack/salt/cli/printer" "github.com/raystack/salt/cli/terminator" @@ -54,7 +54,9 @@ func printSchemaCmd(cdk *CDK) *cobra.Command { return err } case "FORMAT_PROTOBUF": - printProtoSchema(data, filter) + if err := printProtoSchema(data, filter); err != nil { + return err + } default: fmt.Printf("%s Unknown schema format: %s\n", printer.Red(printer.Icon("failure")), format) } @@ -63,7 +65,7 @@ func printSchemaCmd(cdk *CDK) *cobra.Command { } cmd.Flags().StringVarP(&namespaceID, "namespace", "n", "", "Provide namespace/group or entity name") - cmd.MarkFlagRequired("namespace") + _ = cmd.MarkFlagRequired("namespace") cmd.Flags().Int32VarP(&version, "version", "v", 0, "Provide version number") cmd.Flags().StringVar(&filter, "filter", "", "Filter schema files by path prefix, e.g., --filter=google/protobuf") @@ -73,12 +75,14 @@ func printSchemaCmd(cdk *CDK) *cobra.Command { func printSchema(data []byte) error { page := terminator.NewPager() - page.Start() + if err := page.Start(); err != nil { + return err + } defer page.Stop() err := quick.Highlight(page.Out, string(data), "JSON", "terminal16m", "solarized-light") if err != nil { - page.Out.Write(data) + _, _ = page.Out.Write(data) } return nil } @@ -113,12 +117,14 @@ func printProtoSchema(data []byte, filter string) error { } page := terminator.NewPager() - page.Start() + if err := page.Start(); err != nil { + return err + } defer page.Stop() err = quick.Highlight(page.Out, schema, "Protocol Buffer", "terminal16m", "solarized-light") if err != nil { - fmt.Fprint(page.Out, schema) + _, _ = fmt.Fprint(page.Out, schema) } return nil } diff --git a/config/build.go b/config/build.go index a582488d..19cd5a12 100644 --- a/config/build.go +++ b/config/build.go @@ -1,5 +1,7 @@ +// Package config provides application configuration and build metadata. package config +// Version is the application version, set at build time. var ( Version = "dev" BuildCommit = "" diff --git a/config/load.go b/config/load.go index 0ba7614c..e8c24c9c 100644 --- a/config/load.go +++ b/config/load.go @@ -4,6 +4,7 @@ import ( "github.com/raystack/salt/config" ) +// Load reads the configuration from the given file path and returns a Config. func Load(configFile string) (Config, error) { var cfg Config loader := config.NewLoader(config.WithFile(configFile)) diff --git a/core/namespace/namespace.go b/core/namespace/namespace.go index d12113a8..932e7e8a 100644 --- a/core/namespace/namespace.go +++ b/core/namespace/namespace.go @@ -1,3 +1,4 @@ +// Package namespace provides types and operations for managing schema namespaces. package namespace import ( @@ -5,6 +6,7 @@ import ( "time" ) +// Namespace represents a grouping of schemas with shared configuration. type Namespace struct { ID string Format string @@ -14,6 +16,7 @@ type Namespace struct { UpdatedAt time.Time } +// Repository defines the persistence interface for namespaces. type Repository interface { Create(context.Context, Namespace) (Namespace, error) Update(context.Context, Namespace) (Namespace, error) diff --git a/core/namespace/service.go b/core/namespace/service.go index a47e74b2..ba32a222 100644 --- a/core/namespace/service.go +++ b/core/namespace/service.go @@ -4,32 +4,39 @@ import ( "context" ) +// Service provides namespace management operations. type Service struct { repo Repository } +// NewService creates a new namespace service with the given repository. func NewService(repository Repository) *Service { return &Service{ repo: repository, } } +// Create stores a new namespace. func (s Service) Create(ctx context.Context, ns Namespace) (Namespace, error) { return s.repo.Create(ctx, ns) } +// Update modifies an existing namespace. func (s Service) Update(ctx context.Context, ns Namespace) (Namespace, error) { return s.repo.Update(ctx, ns) } +// List returns all namespaces. func (s Service) List(ctx context.Context) ([]Namespace, error) { return s.repo.List(ctx) } +// Get retrieves a namespace by name. func (s Service) Get(ctx context.Context, name string) (Namespace, error) { return s.repo.Get(ctx, name) } +// Delete removes a namespace by name. func (s Service) Delete(ctx context.Context, name string) error { return s.repo.Delete(ctx, name) } diff --git a/core/schema/compatibility.go b/core/schema/compatibility.go index fd2e1a21..293fdf3d 100644 --- a/core/schema/compatibility.go +++ b/core/schema/compatibility.go @@ -2,7 +2,10 @@ package schema import "go.uber.org/multierr" +// ValidationStrategy defines a function that validates compatibility between two schemas. type ValidationStrategy func(ParsedSchema, ParsedSchema) error + +// CompatibilityFn defines a function that checks compatibility against a list of previous schemas. type CompatibilityFn func(ParsedSchema, []ParsedSchema) error func validateLatest(strategy ValidationStrategy) CompatibilityFn { @@ -37,7 +40,7 @@ func fullStrategy(current, prev ParsedSchema) error { return current.IsFullCompatible(prev) } -func defaultCompatibilityFn(current ParsedSchema, prevs []ParsedSchema) error { +func defaultCompatibilityFn(_ ParsedSchema, _ []ParsedSchema) error { return nil } diff --git a/core/search/search.go b/core/search/search.go index d0c8b56b..9af09cc9 100644 --- a/core/search/search.go +++ b/core/search/search.go @@ -1,12 +1,15 @@ +// Package search provides types and operations for searching across schemas. package search import "context" +// Repository defines the persistence interface for search operations. type Repository interface { Search(context.Context, *SearchRequest) ([]*SearchHits, error) SearchLatest(context.Context, *SearchRequest) ([]*SearchHits, error) } +// SearchRequest represents the parameters for a search query. type SearchRequest struct { NamespaceID string SchemaID string @@ -15,10 +18,12 @@ type SearchRequest struct { VersionID int32 } +// SearchResponse represents the result of a search query. type SearchResponse struct { Hits []*SearchHits } +// SearchHits represents a single search result with matched fields and types. type SearchHits struct { Fields []string Types []string diff --git a/core/search/service.go b/core/search/service.go index 4c46a45e..c2df20f3 100644 --- a/core/search/service.go +++ b/core/search/service.go @@ -5,22 +5,28 @@ import ( "errors" ) +// ErrEmptyQueryString indicates a missing query string in the search request. var ( ErrEmptyQueryString = errors.New("query string cannot be empty") - ErrEmptySchemaID = errors.New("schema_id cannot be empty") + // ErrEmptySchemaID indicates a missing schema ID when one is required. + ErrEmptySchemaID = errors.New("schema_id cannot be empty") + // ErrEmptyNamespaceID indicates a missing namespace ID when one is required. ErrEmptyNamespaceID = errors.New("namespace_id cannot be empty") ) +// Service provides search operations over schemas. type Service struct { repo Repository } +// NewService creates a new search service with the given repository. func NewService(repository Repository) *Service { return &Service{ repo: repository, } } +// Search executes a search query and returns matching results. func (s *Service) Search(ctx context.Context, req *SearchRequest) (*SearchResponse, error) { if req.Query == "" { return nil, ErrEmptyQueryString diff --git a/formats/json/utils.go b/formats/json/utils.go index e4a05472..dff1a3f0 100644 --- a/formats/json/utils.go +++ b/formats/json/utils.go @@ -48,7 +48,7 @@ func exploreAdditionalItems(jsonSchema *jsonschema.Schema, locationSchemaMap map } func exploreProperties(jsonSchema *jsonschema.Schema, locationSchemaMap map[string]*jsonschema.Schema, baseLocation string) { - if jsonSchema.Properties == nil || len(jsonSchema.Properties) == 0 { + if len(jsonSchema.Properties) == 0 { return } for _, schema := range jsonSchema.Properties { @@ -57,7 +57,7 @@ func exploreProperties(jsonSchema *jsonschema.Schema, locationSchemaMap map[stri } func exploreAnyOf(jsonSchema *jsonschema.Schema, locationSchemaMap map[string]*jsonschema.Schema, baseLocation string) { - if jsonSchema.AnyOf == nil || len(jsonSchema.AnyOf) == 0 { + if len(jsonSchema.AnyOf) == 0 { return } for _, schema := range jsonSchema.AnyOf { @@ -66,7 +66,7 @@ func exploreAnyOf(jsonSchema *jsonschema.Schema, locationSchemaMap map[string]*j } func exploreOneOf(jsonSchema *jsonschema.Schema, locationSchemaMap map[string]*jsonschema.Schema, baseLocation string) { - if jsonSchema.OneOf == nil || len(jsonSchema.OneOf) == 0 { + if len(jsonSchema.OneOf) == 0 { return } for _, schema := range jsonSchema.OneOf { @@ -75,7 +75,7 @@ func exploreOneOf(jsonSchema *jsonschema.Schema, locationSchemaMap map[string]*j } func exploreAllOf(jsonSchema *jsonschema.Schema, locationSchemaMap map[string]*jsonschema.Schema, baseLocation string) { - if jsonSchema.AllOf == nil || len(jsonSchema.AllOf) == 0 { + if len(jsonSchema.AllOf) == 0 { return } for _, schema := range jsonSchema.AllOf { diff --git a/formats/protobuf/compatibility.go b/formats/protobuf/compatibility.go index 55a432bd..625897ef 100644 --- a/formats/protobuf/compatibility.go +++ b/formats/protobuf/compatibility.go @@ -115,7 +115,7 @@ func compareMessages(current, prev protoreflect.MessageDescriptor, diffs *compat for i := 0; i < prevRanges.Len(); i++ { prevRange := prevRanges.Get(i) start, end := prevRange[0], prevRange[1] - if !(current.ReservedRanges().Has(start) && current.ReservedRanges().Has(end-1)) { + if !current.ReservedRanges().Has(start) || !current.ReservedRanges().Has(end-1) { diffs.add(nonInclusivereservedRange, current, "previous reserved range (%d, %d) is not inclusive of current range", start, end) } } @@ -172,7 +172,7 @@ func compareEnums(current, prev protoreflect.EnumDescriptor, diffs *compatibilit for i := 0; i < prevRanges.Len(); i++ { prevRange := prevRanges.Get(i) start, end := prevRange[0], prevRange[1] - if !(current.ReservedRanges().Has(start) && current.ReservedRanges().Has(end)) { + if !current.ReservedRanges().Has(start) || !current.ReservedRanges().Has(end) { if start == end { diffs.add(nonInclusivereservedRange, current, "previous reserved number (%d) is not inclusive of current range", start) } else { diff --git a/formats/protobuf/provider_test.go b/formats/protobuf/provider_test.go index d7ac1a26..dda335b1 100644 --- a/formats/protobuf/provider_test.go +++ b/formats/protobuf/provider_test.go @@ -2,11 +2,11 @@ package protobuf_test import ( "bytes" + "crypto/rand" "encoding/hex" "fmt" - "io/ioutil" "log" - "math/rand" + "os" "os/exec" "path/filepath" "testing" @@ -68,7 +68,7 @@ func getDescriptorData(t *testing.T, path string, includeImports bool) []byte { targetFile := filepath.Join(t.TempDir(), getRandomName()) err := runProtoc(root, includeImports, targetFile) assert.NoError(t, err) - data, err := ioutil.ReadFile(targetFile) + data, err := os.ReadFile(targetFile) assert.NoError(t, err) return data } diff --git a/internal/store/errors.go b/internal/store/errors.go index f59adda7..2858f59a 100644 --- a/internal/store/errors.go +++ b/internal/store/errors.go @@ -60,6 +60,7 @@ func (e StorageErr) Unwrap() error { return e.err } +// Is reports whether the target error matches this StorageErr's kind. func (e StorageErr) Is(err error) bool { sErr, ok := err.(StorageErr) if !ok { diff --git a/internal/store/postgres/postgres_test.go b/internal/store/postgres/postgres_test.go index b1c8baf6..475da686 100644 --- a/internal/store/postgres/postgres_test.go +++ b/internal/store/postgres/postgres_test.go @@ -1,9 +1,11 @@ package postgres_test import ( + "errors" "os" "testing" + "github.com/golang-migrate/migrate/v4" "github.com/raystack/stencil/internal/store/postgres" "github.com/stretchr/testify/assert" ) @@ -17,6 +19,8 @@ func tearDown(t *testing.T) { } m, err := postgres.NewHTTPFSMigrator(connectionString) if assert.NoError(t, err) { - m.Down() + if err := m.Down(); err != nil && !errors.Is(err, migrate.ErrNoChange) { + t.Fatalf("failed to rollback migrations: %v", err) + } } } diff --git a/pkg/graph/graph.go b/pkg/graph/graph.go index 3d397222..6dbfca0d 100644 --- a/pkg/graph/graph.go +++ b/pkg/graph/graph.go @@ -9,12 +9,14 @@ import ( "google.golang.org/protobuf/types/descriptorpb" ) +// Graph node rendering constants. const ( NodeShape = "note" NodeStyle = "filled" NodeColor = "cornsilk" ) +// GetProtoFileDependencyGraph builds a directed graph of proto file dependencies. func GetProtoFileDependencyGraph(file *descriptorpb.FileDescriptorSet) (*dot.Graph, error) { files, err := protodesc.NewFiles(file) if err != nil { diff --git a/ui/embed.go b/ui/embed.go index 540dc893..49f660c2 100644 --- a/ui/embed.go +++ b/ui/embed.go @@ -1,3 +1,4 @@ +// Package ui provides embedded frontend assets. package ui import "embed"