diff --git a/.gitignore b/.gitignore index 206992f..4afc560 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,5 @@ Makefile # local test directory docker-compose.yml tmp + +staticlint diff --git a/cmd/reset/main.go b/cmd/reset/main.go new file mode 100644 index 0000000..28181ad --- /dev/null +++ b/cmd/reset/main.go @@ -0,0 +1,179 @@ +package main + +import ( + "bytes" + "fmt" + "go/ast" + "go/format" + "go/parser" + "go/token" + "log" + "os" + "path/filepath" + "strings" + + "golang.org/x/tools/go/packages" +) + +func main() { + // Настройка конфигурации. + cfg := &packages.Config{ + Mode: packages.NeedName | packages.NeedFiles | packages.NeedSyntax | packages.NeedTypes, + Tests: false, + Fset: token.NewFileSet(), + // заставляет парсер сохранять комментарии + ParseFile: func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) { + return parser.ParseFile(fset, filename, src, parser.ParseComments) + }, + } + + fmt.Println("Сканирую пакеты...") + // parser.ParseDir устарел + pkgs, err := packages.Load(cfg, "./...") + if err != nil { + log.Fatal(err) + } + + for _, pkg := range pkgs { + if len(pkg.Errors) > 0 { + fmt.Printf("Ошибки в пакете %s: %v\n", pkg.PkgPath, pkg.Errors) + continue + } + processPackage(pkg) + } +} + +func hasResetComment(doc *ast.CommentGroup) bool { + if doc == nil { + return false + } + for _, c := range doc.List { + if strings.Contains(c.Text, "// generate:reset") { + return true + } + } + return false +} + +func processPackage(pkg *packages.Package) { + var buf bytes.Buffer + hasMatches := false + + // Перебираем файлы в пакете + for _, file := range pkg.Syntax { + for _, decl := range file.Decls { + genDecl, ok := decl.(*ast.GenDecl) + // Ищем объявления TYPE + if !ok || genDecl.Tok != token.TYPE { + continue + } + + // Проверяем наличие комментария + if !hasResetComment(genDecl.Doc) { + continue + } + + for _, spec := range genDecl.Specs { + typeSpec, ok := spec.(*ast.TypeSpec) + if !ok { + continue + } + + structType, ok := typeSpec.Type.(*ast.StructType) + if !ok { + continue + } + + if !hasMatches { + buf.WriteString(fmt.Sprintf("// Code generated by reset-gen. DO NOT EDIT.\npackage %s\n\n", pkg.Name)) + hasMatches = true + } + + structName := typeSpec.Name.Name + buf.WriteString(fmt.Sprintf("func (s *%s) Reset() {\n", structName)) + + // Генерация логики для каждого поля + for _, field := range structType.Fields.List { + for _, name := range field.Names { + genFieldReset(&buf, name.Name, field.Type) + } + } + + buf.WriteString("}\n\n") + } + } + } + + if hasMatches { + formatted, err := format.Source(buf.Bytes()) + if err != nil { + fmt.Printf("Ошибка форматирования: %v\n", err) + formatted = buf.Bytes() + } + + dir := filepath.Dir(pkg.GoFiles[0]) + outputPath := filepath.Join(dir, "reset.gen.go") + os.WriteFile(outputPath, formatted, 0644) + fmt.Printf("Сгенерирован файл: %s\n", outputPath) + } +} + +// genFieldReset записывает логику сброса в зависимости от типа поля +func genFieldReset(buf *bytes.Buffer, fieldName string, fieldType ast.Expr) { + switch t := fieldType.(type) { + case *ast.ArrayType: + // Правило: слайсы обрезаются, но не зануляются + buf.WriteString(fmt.Sprintf("\ts.%s = s.%s[:0]\n", fieldName, fieldName)) + + case *ast.MapType: + // Правило: мапы очищаются через clear() + buf.WriteString(fmt.Sprintf("\tclear(s.%s)\n", fieldName)) + + case *ast.StarExpr: + // Правило: не nil указатели сбрасывают свои значения + buf.WriteString(fmt.Sprintf("\tif s.%s != nil {\n\t\ts.%s.Reset()\n\t}\n", fieldName, fieldName)) + + case *ast.Ident: + // Примитивы (int, string, bool) или вложенные структуры в том же пакете + if isPrimitive(t.Name) { + buf.WriteString(fmt.Sprintf("\ts.%s = %s\n", fieldName, getZeroValue(t.Name))) + } else { + // Reset() должен быть реализован для вложенных структур + buf.WriteString(fmt.Sprintf("\ts.%s.Reset()\n", fieldName)) + } + + case *ast.SelectorExpr: + // Другие пакеты (например, time.Time) + // Вариантов типа бесконечно много, предполагаю что у них свой Reset + buf.WriteString(fmt.Sprintf("\ts.%s.Reset()\n", fieldName)) + + default: + // Универсальный сброс к zero value для всего остального + buf.WriteString(fmt.Sprintf("\ts.%s = *new(%s)\n", fieldName, "interface{}")) + } +} + +func isPrimitive(name string) bool { + switch name { + case "bool", "string", "int", "int8", "int16", "int32", "int64", + "uint", "uint8", "uint16", "uint32", "uint64", "uintptr", + "byte", "rune", "float32", "float64", "complex64", "complex128": + return true + } + return false +} + +func getZeroValue(name string) string { + switch name { + case "bool": + return "false" + case "string": + return `""` + case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "uintptr", "byte", "rune": + return "0" + case "float32", "float64": + return "0.0" + default: + return "nil" + } +} diff --git a/go.mod b/go.mod index 5a9df9a..9a70dca 100644 --- a/go.mod +++ b/go.mod @@ -6,11 +6,16 @@ require ( github.com/gin-contrib/cors v1.7.6 github.com/gin-gonic/gin v1.11.0 github.com/jackc/pgx/v5 v5.8.0 + github.com/kisielk/errcheck v1.10.0 github.com/pressly/goose/v3 v3.26.0 + github.com/timakin/bodyclose v0.0.0-20260129054331-73d1f95b84b4 go.uber.org/zap v1.27.1 + golang.org/x/tools v0.43.0 + honnef.co/go/tools v0.7.0 ) require ( + github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect github.com/bytedance/gopkg v0.1.3 // indirect github.com/bytedance/sonic v1.14.2 // indirect github.com/bytedance/sonic/loader v0.4.0 // indirect @@ -22,6 +27,8 @@ require ( github.com/go-playground/validator/v10 v10.28.0 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-yaml v1.18.0 // indirect + github.com/gostaticanalysis/analysisutil v0.7.1 // indirect + github.com/gostaticanalysis/comment v1.4.2 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect @@ -41,10 +48,13 @@ require ( go.uber.org/mock v0.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/arch v0.23.0 // indirect - golang.org/x/crypto v0.46.0 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.39.0 // indirect - golang.org/x/text v0.32.0 // indirect + golang.org/x/crypto v0.49.0 // indirect + golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 // indirect + golang.org/x/mod v0.34.0 // indirect + golang.org/x/net v0.52.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/text v0.35.0 // indirect + golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect google.golang.org/protobuf v1.36.10 // indirect ) diff --git a/go.sum b/go.sum index c5df47d..a65942b 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE= @@ -31,11 +33,21 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= +github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= +github.com/gostaticanalysis/comment v1.4.2 h1:hlnx5+S2fY9Zo9ePo4AhgYsYHbM2+eAv8m/s1JiCd6Q= +github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= +github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4 h1:d2/eIbH9XjD1fFwD5SHv8x168fjbQ9PB8hvs8DSEC08= +github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= +github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= +github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= @@ -46,6 +58,8 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.10.0 h1:Lvs/YAHP24YKg08LA8oDw2z9fJVme090RAXd90S+rrw= +github.com/kisielk/errcheck v1.10.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= @@ -61,6 +75,12 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/otiai10/copy v1.2.0 h1:HvG945u96iNadPoG2/Ja2+AUJeW5YuFQMixq9yirC+k= +github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= +github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= +github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= +github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= +github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -87,10 +107,17 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= +github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= +github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= +github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= +github.com/timakin/bodyclose v0.0.0-20260129054331-73d1f95b84b4 h1:SiHe5XLTn9sFWJ5pBwJ5FN/4j34q9ZlOAD//kMoMYp0= +github.com/timakin/bodyclose v0.0.0-20260129054331-73d1f95b84b4/go.mod h1:sDHLK7rb/59v/ZxZ7KtymgcoxuUMxjXq8gtu9VMOK8M= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 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/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= @@ -101,27 +128,63 @@ go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg= golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= -golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= +golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -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/exp/typeparams v0.0.0-20231108232855-2478ac86f678 h1:1P7xPZEwZMoBoz0Yze5Nx2/4pxj6nw9ZqHWXqP0iRgQ= +golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= +golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +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= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= +golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= +golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= +golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= +golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= +golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= +golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= +golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +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/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.7.0 h1:w6WUp1VbkqPEgLz4rkBzH/CSU6HkoqNLp6GstyTx3lU= +honnef.co/go/tools v0.7.0/go.mod h1:pm29oPxeP3P82ISxZDgIYeOaf9ta6Pi0EWvCFoLG2vc= modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ= modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= diff --git a/internal/audit/audit.go b/internal/audit/audit.go index 123875b..40ab71c 100644 --- a/internal/audit/audit.go +++ b/internal/audit/audit.go @@ -1,3 +1,4 @@ +// Package audit реализует подсистему аудита событий сервиса. package audit import "sync" diff --git a/internal/audit/file.go b/internal/audit/file.go index 8841d23..58e7ab9 100644 --- a/internal/audit/file.go +++ b/internal/audit/file.go @@ -35,7 +35,7 @@ func (f *FileObserver) Notify(event AuditEvent) { log.Printf("audit file: failed to open file %s: %v", f.path, err) return } - defer file.Close() + defer func() { _ = file.Close() }() if _, err := file.Write(data); err != nil { log.Printf("audit file: failed to write event: %v", err) diff --git a/internal/audit/http.go b/internal/audit/http.go index be70393..dc0b7c1 100644 --- a/internal/audit/http.go +++ b/internal/audit/http.go @@ -37,5 +37,5 @@ func (h *HTTPObserver) Notify(event AuditEvent) { log.Printf("audit http: failed to send event to %s: %v", h.url, err) return } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() } diff --git a/internal/handler/example_test.go b/internal/handler/example_test.go index 618a66c..086bb3e 100644 --- a/internal/handler/example_test.go +++ b/internal/handler/example_test.go @@ -103,7 +103,7 @@ func ExampleURLHandler_Shorten() { fmt.Println("Status:", w.Code) var resp handler.APIResponse - json.Unmarshal(w.Body.Bytes(), &resp) + _ = json.Unmarshal(w.Body.Bytes(), &resp) fmt.Println("Result contains base URL:", strings.Contains(resp.Result, "http://localhost:8080")) // Output: // Status: 201 @@ -153,7 +153,7 @@ func ExampleURLHandler_BatchShorten() { fmt.Println("Status:", w.Code) var resp []handler.ItemBatchResponse - json.Unmarshal(w.Body.Bytes(), &resp) + _ = json.Unmarshal(w.Body.Bytes(), &resp) fmt.Println("Items:", len(resp)) fmt.Println("Has correlation_id:", resp[0].CorrelationID == "1") // Output: diff --git a/internal/middleware/middleware.go b/internal/middleware/middleware.go index f701f2b..71ef7ef 100644 --- a/internal/middleware/middleware.go +++ b/internal/middleware/middleware.go @@ -1,3 +1,4 @@ +// Package middleware содержит HTTP-мидлвары для сервиса. package middleware import ( @@ -78,7 +79,7 @@ type gzipResponseWriter struct { // Flush сбрасывает буфер gzip-писателя. func (g *gzipResponseWriter) Flush() { if g.shouldCompress && g.Writer != nil { - g.Writer.Flush() + _ = g.Writer.Flush() } } @@ -123,7 +124,7 @@ func CompressionResponse() gin.HandlerFunc { gzWriter := &gzipResponseWriter{Writer: wc, ResponseWriter: wo} defer func() { if gzWriter.Writer != nil { - gzWriter.Writer.Close() + _ = gzWriter.Writer.Close() gzipWriterPool.Put(wc) } }() @@ -148,10 +149,10 @@ func DecompressionRequest() gin.HandlerFunc { if encodingType == "gzip" { body, err := gzip.NewReader(c.Request.Body) if err != nil { - c.AbortWithError(http.StatusBadRequest, err) + _ = c.AbortWithError(http.StatusBadRequest, err) return } - defer body.Close() + defer func() { _ = body.Close() }() c.Request.Body = body } c.Next() diff --git a/internal/repository/db/postgres/postgres.go b/internal/repository/db/postgres/postgres.go index 558f26f..5cf5c84 100644 --- a/internal/repository/db/postgres/postgres.go +++ b/internal/repository/db/postgres/postgres.go @@ -1,3 +1,4 @@ +// Package postgres реализует репозиторий для работы с PostgreSQL. package postgres import ( @@ -118,7 +119,7 @@ func (r *Repository) GetURLs(ctx context.Context) ([]repository.Data, error) { } return data, fmt.Errorf("failed to query URLs in GetURLs: %w", err) } - defer rows.Close() + defer func() { _ = rows.Close() }() for rows.Next() { var id, shortURL, originalURL, userID string @@ -146,7 +147,7 @@ func (r *Repository) InsertURLsBatch(ctx context.Context, data []repository.Data if err != nil { return fmt.Errorf("failed to get connection in InsertURLsBatch: %w", err) } - defer conn.Close() + defer func() { _ = conn.Close() }() err = conn.Raw(func(driverConn any) error { pgConn := driverConn.(*stdlib.Conn).Conn() @@ -158,16 +159,16 @@ func (r *Repository) InsertURLsBatch(ctx context.Context, data []repository.Data } br := pgConn.SendBatch(ctx, batch) - defer br.Close() + defer func() { _ = br.Close() }() // Process results for i := 0; i < len(data); i++ { // можно делать for idx, _ := range data {}, но так понятнее - _, err := br.Exec() - if err != nil { - if isUniqueViolation(err) { + _, execErr := br.Exec() + if execErr != nil { + if isUniqueViolation(execErr) { return fmt.Errorf("failed to insert URL (id=%s) in batch: %w", data[i].ID, ErrUniqueViolation) } - return fmt.Errorf("failed to insert URL (id=%s) in batch: %w", data[i].ID, err) + return fmt.Errorf("failed to insert URL (id=%s) in batch: %w", data[i].ID, execErr) } } @@ -191,7 +192,7 @@ func (r *Repository) GetURLsByUser(ctx context.Context, userID string) ([]reposi } return data, fmt.Errorf("failed to query URLs by user in GetURLsByUser: %w", err) } - defer rows.Close() + defer func() { _ = rows.Close() }() for rows.Next() { var id, shortURL, originalURL, uid string diff --git a/internal/service/pprof_test.go b/internal/service/pprof_test.go index c8cb50a..3e52a5f 100644 --- a/internal/service/pprof_test.go +++ b/internal/service/pprof_test.go @@ -73,7 +73,7 @@ func TestProfileMemory(t *testing.T) { if err != nil { t.Fatalf("не удалось создать файл профиля %s: %v", outPath, err) } - defer f.Close() + defer func() { _ = f.Close() }() if err := pprof.WriteHeapProfile(f); err != nil { t.Fatalf("не удалось записать профиль: %v", err) diff --git a/internal/worker/delete_worker.go b/internal/worker/delete_worker.go index 04debeb..346cbfa 100644 --- a/internal/worker/delete_worker.go +++ b/internal/worker/delete_worker.go @@ -1,3 +1,4 @@ +// Package worker реализует фоновую обработку запросов на удаление URL. package worker import ( diff --git a/migrations/migrations.go b/migrations/migrations.go index eb05421..4f3bac9 100644 --- a/migrations/migrations.go +++ b/migrations/migrations.go @@ -1,3 +1,4 @@ +// Package migrations содержит встроенные SQL-миграции для базы данных. package migrations import ( diff --git a/pkg/pool/pool.go b/pkg/pool/pool.go new file mode 100644 index 0000000..000522e --- /dev/null +++ b/pkg/pool/pool.go @@ -0,0 +1,42 @@ +package pool + +// Интерфейс ограничение +type Resettable interface { + Reset() +} + +// Pool с generic +type Pool[T Resettable] struct { + items []T + new func() T // подглядел у sync.Pool :) +} + +// New - конструктор *Pool[T]. cap — вместимость пула, factory — функция создания нового объекта +func New[T Resettable](cap int, factory func() T) *Pool[T] { + return &Pool[T]{ + items: make([]T, 0, cap), + new: factory, + } +} + +// Put — кладет объект в пул, предварительно сбросив его +func (p *Pool[T]) Put(item T) { + item.Reset() + p.items = append(p.items, item) +} + +// Get — возвращает объект из пула +// если запрашиваем объект, которого нет в пуле, создаем его через factory, +// т.к. мы не можем запрашивать то, чего нет +func (p *Pool[T]) Get() T { + n := len(p.items) + + if n == 0 { + return p.new() + } + + item := p.items[n-1] + p.items = p.items[:n-1] + + return item +}