From ecd7b319102cc63df70b887c07b030994b995a93 Mon Sep 17 00:00:00 2001 From: Hein Date: Tue, 11 Nov 2025 11:32:30 +0200 Subject: [PATCH] Fixed linting issues --- README.md | 2 ++ pkg/common/adapters/database/bun.go | 4 +++ pkg/common/adapters/database/gorm.go | 11 ++++-- pkg/common/adapters/router/mux.go | 2 +- pkg/common/interfaces.go | 4 +++ pkg/reflection/generic_model.go | 9 ++--- pkg/resolvespec/handler.go | 18 +++++----- pkg/restheadspec/handler.go | 38 ++++++++++++++------- pkg/restheadspec/headers.go | 9 ++--- pkg/restheadspec/restheadspec.go | 5 ++- pkg/security/callbacks_example.go | 1 - pkg/security/hooks.go | 6 ++-- pkg/security/middleware.go | 9 +++-- pkg/security/provider.go | 51 +++++++++++++++------------- 14 files changed, 104 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index 990ac3e..509d4c1 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # 📜 ResolveSpec 📜 +![Tests](https://github.com/bitechdev/ResolveSpec/workflows/Tests/badge.svg) + ResolveSpec is a flexible and powerful REST API specification and implementation that provides GraphQL-like capabilities while maintaining REST simplicity. It offers **two complementary approaches**: 1. **ResolveSpec** - Body-based API with JSON request options diff --git a/pkg/common/adapters/database/bun.go b/pkg/common/adapters/database/bun.go index 2fe04b1..a28e13b 100644 --- a/pkg/common/adapters/database/bun.go +++ b/pkg/common/adapters/database/bun.go @@ -100,6 +100,10 @@ func (b *BunSelectQuery) Model(model interface{}) common.SelectQuery { b.schema, b.tableName = parseTableName(fullTableName) } + if provider, ok := model.(common.TableAliasProvider); ok { + b.tableAlias = provider.TableAlias() + } + return b } diff --git a/pkg/common/adapters/database/gorm.go b/pkg/common/adapters/database/gorm.go index 6372bd8..7da609a 100644 --- a/pkg/common/adapters/database/gorm.go +++ b/pkg/common/adapters/database/gorm.go @@ -86,6 +86,10 @@ func (g *GormSelectQuery) Model(model interface{}) common.SelectQuery { g.schema, g.tableName = parseTableName(fullTableName) } + if provider, ok := model.(common.TableAliasProvider); ok { + g.tableAlias = provider.TableAlias() + } + return g } @@ -267,11 +271,12 @@ func (g *GormInsertQuery) Returning(columns ...string) common.InsertQuery { func (g *GormInsertQuery) Exec(ctx context.Context) (common.Result, error) { var result *gorm.DB - if g.model != nil { + switch { + case g.model != nil: result = g.db.WithContext(ctx).Create(g.model) - } else if g.values != nil { + case g.values != nil: result = g.db.WithContext(ctx).Create(g.values) - } else { + default: result = g.db.WithContext(ctx).Create(map[string]interface{}{}) } return &GormResult{result: result}, result.Error diff --git a/pkg/common/adapters/router/mux.go b/pkg/common/adapters/router/mux.go index 2eb199e..40b0a59 100644 --- a/pkg/common/adapters/router/mux.go +++ b/pkg/common/adapters/router/mux.go @@ -130,7 +130,7 @@ func (h *HTTPRequest) AllHeaders() map[string]string { // HTTPResponseWriter adapts our ResponseWriter interface to standard http.ResponseWriter type HTTPResponseWriter struct { resp http.ResponseWriter - w common.ResponseWriter + w common.ResponseWriter //nolint:unused status int } diff --git a/pkg/common/interfaces.go b/pkg/common/interfaces.go index 5b668c4..1c116e2 100644 --- a/pkg/common/interfaces.go +++ b/pkg/common/interfaces.go @@ -131,6 +131,10 @@ type TableNameProvider interface { TableName() string } +type TableAliasProvider interface { + TableAlias() string +} + // PrimaryKeyNameProvider interface for models that provide primary key column names type PrimaryKeyNameProvider interface { GetIDName() string diff --git a/pkg/reflection/generic_model.go b/pkg/reflection/generic_model.go index 010adf9..d5e08f3 100644 --- a/pkg/reflection/generic_model.go +++ b/pkg/reflection/generic_model.go @@ -49,12 +49,13 @@ func GetModelColumnDetail(record reflect.Value) []ModelFieldDetail { fielddetail.DataType = fieldtype.Type.Name() fielddetail.SQLName = fnFindKeyVal(gormdetail, "column:") fielddetail.SQLDataType = fnFindKeyVal(gormdetail, "type:") - if strings.Index(strings.ToLower(gormdetail), "identity") > 0 || - strings.Index(strings.ToLower(gormdetail), "primary_key") > 0 { + gormdetailLower := strings.ToLower(gormdetail) + switch { + case strings.Index(gormdetailLower, "identity") > 0 || strings.Index(gormdetailLower, "primary_key") > 0: fielddetail.SQLKey = "primary_key" - } else if strings.Contains(strings.ToLower(gormdetail), "unique") { + case strings.Contains(gormdetailLower, "unique"): fielddetail.SQLKey = "unique" - } else if strings.Contains(strings.ToLower(gormdetail), "uniqueindex") { + case strings.Contains(gormdetailLower, "uniqueindex"): fielddetail.SQLKey = "uniqueindex" } diff --git a/pkg/resolvespec/handler.go b/pkg/resolvespec/handler.go index 8244cda..96c5d41 100644 --- a/pkg/resolvespec/handler.go +++ b/pkg/resolvespec/handler.go @@ -481,11 +481,12 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, url case map[string]interface{}: // Determine the ID to use var targetID interface{} - if urlID != "" { + switch { + case urlID != "": targetID = urlID - } else if reqID != nil { + case reqID != nil: targetID = reqID - } else if updates["id"] != nil { + case updates["id"] != nil: targetID = updates["id"] } @@ -723,11 +724,12 @@ func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, id var itemID interface{} // Check if item is a string ID or object with id field - if idStr, ok := item.(string); ok { - itemID = idStr - } else if itemMap, ok := item.(map[string]interface{}); ok { - itemID = itemMap["id"] - } else { + switch v := item.(type) { + case string: + itemID = v + case map[string]interface{}: + itemID = v["id"] + default: // Try to use the item directly as ID itemID = item } diff --git a/pkg/restheadspec/handler.go b/pkg/restheadspec/handler.go index a092755..6e7b024 100644 --- a/pkg/restheadspec/handler.go +++ b/pkg/restheadspec/handler.go @@ -781,11 +781,12 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, id query := h.db.NewUpdate().Table(tableName).SetMap(dataMap) // Apply ID filter - if id != "" { + switch { + case id != "": query = query.Where("id = ?", id) - } else if idPtr != nil { + case idPtr != nil: query = query.Where("id = ?", *idPtr) - } else { + default: h.sendError(w, http.StatusBadRequest, "missing_id", "ID is required for update", nil) return } @@ -902,11 +903,12 @@ func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, id var itemID interface{} // Check if item is a string ID or object with id field - if idStr, ok := item.(string); ok { - itemID = idStr - } else if itemMap, ok := item.(map[string]interface{}); ok { - itemID = itemMap["id"] - } else { + switch v := item.(type) { + case string: + itemID = v + case map[string]interface{}: + itemID = v["id"] + default: itemID = item } @@ -1330,7 +1332,9 @@ func (h *Handler) sendResponse(w common.ResponseWriter, data interface{}, metada Metadata: metadata, } w.WriteHeader(http.StatusOK) - w.WriteJSON(response) + if err := w.WriteJSON(response); err != nil { + logger.Error("Failed to write JSON response: %v", err) + } } // sendFormattedResponse sends response with formatting options @@ -1350,7 +1354,9 @@ func (h *Handler) sendFormattedResponse(w common.ResponseWriter, data interface{ case "simple": // Simple format: just return the data array w.WriteHeader(http.StatusOK) - w.WriteJSON(data) + if err := w.WriteJSON(data); err != nil { + logger.Error("Failed to write JSON response: %v", err) + } case "syncfusion": // Syncfusion format: { result: data, count: total } response := map[string]interface{}{ @@ -1360,7 +1366,9 @@ func (h *Handler) sendFormattedResponse(w common.ResponseWriter, data interface{ response["count"] = metadata.Total } w.WriteHeader(http.StatusOK) - w.WriteJSON(response) + if err := w.WriteJSON(response); err != nil { + logger.Error("Failed to write JSON response: %v", err) + } default: // Default/detail format: standard response with metadata response := common.Response{ @@ -1369,7 +1377,9 @@ func (h *Handler) sendFormattedResponse(w common.ResponseWriter, data interface{ Metadata: metadata, } w.WriteHeader(http.StatusOK) - w.WriteJSON(response) + if err := w.WriteJSON(response); err != nil { + logger.Error("Failed to write JSON response: %v", err) + } } } @@ -1397,7 +1407,9 @@ func (h *Handler) sendError(w common.ResponseWriter, statusCode int, code, messa }, } w.WriteHeader(statusCode) - w.WriteJSON(response) + if err := w.WriteJSON(response); err != nil { + logger.Error("Failed to write JSON error response: %v", err) + } } // FetchRowNumber calculates the row number of a specific record based on sorting and filtering diff --git a/pkg/restheadspec/headers.go b/pkg/restheadspec/headers.go index 31d426e..361bec8 100644 --- a/pkg/restheadspec/headers.go +++ b/pkg/restheadspec/headers.go @@ -416,16 +416,17 @@ func (h *Handler) parseSorting(options *ExtendedRequestOptions, value string) { direction := "ASC" colName := field - if strings.HasPrefix(field, "-") { + switch { + case strings.HasPrefix(field, "-"): direction = "DESC" colName = strings.TrimPrefix(field, "-") - } else if strings.HasPrefix(field, "+") { + case strings.HasPrefix(field, "+"): direction = "ASC" colName = strings.TrimPrefix(field, "+") - } else if strings.HasSuffix(field, " desc") { + case strings.HasSuffix(field, " desc"): direction = "DESC" colName = strings.TrimSuffix(field, "desc") - } else if strings.HasSuffix(field, " asc") { + case strings.HasSuffix(field, " asc"): direction = "ASC" colName = strings.TrimSuffix(field, "asc") } diff --git a/pkg/restheadspec/restheadspec.go b/pkg/restheadspec/restheadspec.go index e34d200..736ed9c 100644 --- a/pkg/restheadspec/restheadspec.go +++ b/pkg/restheadspec/restheadspec.go @@ -62,6 +62,7 @@ import ( "github.com/bitechdev/ResolveSpec/pkg/common/adapters/database" "github.com/bitechdev/ResolveSpec/pkg/common/adapters/router" + "github.com/bitechdev/ResolveSpec/pkg/logger" "github.com/bitechdev/ResolveSpec/pkg/modelregistry" ) @@ -252,5 +253,7 @@ func ExampleBunRouterWithBunDB(bunDB *bun.DB) { r := routerAdapter.GetBunRouter() // Start server - http.ListenAndServe(":8080", r) + if err := http.ListenAndServe(":8080", r); err != nil { + logger.Error("Server failed to start: %v", err) + } } diff --git a/pkg/security/callbacks_example.go b/pkg/security/callbacks_example.go index 5ddd8f9..1935652 100644 --- a/pkg/security/callbacks_example.go +++ b/pkg/security/callbacks_example.go @@ -5,7 +5,6 @@ import ( "net/http" "strconv" "strings" - // DBM "github.com/bitechdev/GoCore/pkg/models" ) // This file provides example implementations of the required security callbacks. diff --git a/pkg/security/hooks.go b/pkg/security/hooks.go index be7e0d3..1a2485a 100644 --- a/pkg/security/hooks.go +++ b/pkg/security/hooks.go @@ -27,9 +27,7 @@ func RegisterSecurityHooks(handler *restheadspec.Handler, securityList *Security }) // Hook 4 (Optional): Audit logging - handler.Hooks().Register(restheadspec.AfterRead, func(hookCtx *restheadspec.HookContext) error { - return logDataAccess(hookCtx) - }) + handler.Hooks().Register(restheadspec.AfterRead, logDataAccess) } // loadSecurityRules loads security configuration for the user and entity @@ -162,7 +160,7 @@ func applyColumnSecurity(hookCtx *restheadspec.HookContext, securityList *Securi resultValue = resultValue.Elem() } - err, maskedResult := securityList.ApplyColumnSecurity(resultValue, modelType, userID, schema, tablename) + maskedResult, err := securityList.ApplyColumnSecurity(resultValue, modelType, userID, schema, tablename) if err != nil { logger.Warn("Column security error: %v", err) // Don't fail the request, just log the issue diff --git a/pkg/security/middleware.go b/pkg/security/middleware.go index 7263edd..a44ca19 100644 --- a/pkg/security/middleware.go +++ b/pkg/security/middleware.go @@ -5,11 +5,14 @@ import ( "net/http" ) +// contextKey is a custom type for context keys to avoid collisions +type contextKey string + const ( // Context keys for user information - UserIDKey = "user_id" - UserRolesKey = "user_roles" - UserTokenKey = "user_token" + UserIDKey contextKey = "user_id" + UserRolesKey contextKey = "user_roles" + UserTokenKey contextKey = "user_token" ) // AuthMiddleware extracts user authentication from request and adds to context diff --git a/pkg/security/provider.go b/pkg/security/provider.go index 8f4f7d7..4e05cab 100644 --- a/pkg/security/provider.go +++ b/pkg/security/provider.go @@ -73,8 +73,9 @@ type SecurityList struct { LoadColumnSecurityCallback LoadColumnSecurityFunc LoadRowSecurityCallback LoadRowSecurityFunc } +type CONTEXT_KEY string -const SECURITY_CONTEXT_KEY = "SecurityList" +const SECURITY_CONTEXT_KEY CONTEXT_KEY = "SecurityList" var GlobalSecurity SecurityList @@ -105,22 +106,22 @@ func maskString(pString string, maskStart, maskEnd int, maskChar string, invert } for index, char := range pString { if invert && index >= middleIndex-maskStart && index <= middleIndex { - newStr = newStr + maskChar + newStr += maskChar continue } if invert && index <= middleIndex+maskEnd && index >= middleIndex { - newStr = newStr + maskChar + newStr += maskChar continue } if !invert && index <= maskStart { - newStr = newStr + maskChar + newStr += maskChar continue } if !invert && index >= strLen-1-maskEnd { - newStr = newStr + maskChar + newStr += maskChar continue } - newStr = newStr + string(char) + newStr += string(char) } return newStr @@ -145,7 +146,8 @@ func (m *SecurityList) ColumSecurityApplyOnRecord(prevRecord reflect.Value, newR return cols, fmt.Errorf("no security data") } - for _, colsec := range colsecList { + for i := range colsecList { + colsec := &colsecList[i] if !strings.EqualFold(colsec.Accesstype, "mask") && !strings.EqualFold(colsec.Accesstype, "hide") { continue } @@ -262,24 +264,25 @@ func setColSecValue(fieldsrc reflect.Value, colsec ColumnSecurity, fieldTypeName fieldval = fieldval.Elem() } - if strings.Contains(strings.ToLower(fieldval.Kind().String()), "int") && - (strings.EqualFold(colsec.Accesstype, "mask") || strings.EqualFold(colsec.Accesstype, "hide")) { + fieldKindLower := strings.ToLower(fieldval.Kind().String()) + switch { + case strings.Contains(fieldKindLower, "int") && + (strings.EqualFold(colsec.Accesstype, "mask") || strings.EqualFold(colsec.Accesstype, "hide")): if fieldval.CanInt() && fieldval.CanSet() { fieldval.SetInt(0) } - } else if (strings.Contains(strings.ToLower(fieldval.Kind().String()), "time") || - strings.Contains(strings.ToLower(fieldval.Kind().String()), "date")) && - (strings.EqualFold(colsec.Accesstype, "mask") || strings.EqualFold(colsec.Accesstype, "hide")) { + case (strings.Contains(fieldKindLower, "time") || strings.Contains(fieldKindLower, "date")) && + (strings.EqualFold(colsec.Accesstype, "mask") || strings.EqualFold(colsec.Accesstype, "hide")): fieldval.SetZero() - } else if strings.Contains(strings.ToLower(fieldval.Kind().String()), "string") { + case strings.Contains(fieldKindLower, "string"): strVal := fieldval.String() if strings.EqualFold(colsec.Accesstype, "mask") { fieldval.SetString(maskString(strVal, colsec.MaskStart, colsec.MaskEnd, colsec.MaskChar, colsec.MaskInvert)) } else if strings.EqualFold(colsec.Accesstype, "hide") { fieldval.SetString("") } - } else if strings.Contains(fieldTypeName, "json") && - (strings.EqualFold(colsec.Accesstype, "mask") || strings.EqualFold(colsec.Accesstype, "hide")) { + case strings.Contains(fieldTypeName, "json") && + (strings.EqualFold(colsec.Accesstype, "mask") || strings.EqualFold(colsec.Accesstype, "hide")): if len(colsec.Path) < 2 { return 1, fieldval } @@ -300,11 +303,11 @@ func setColSecValue(fieldsrc reflect.Value, colsec ColumnSecurity, fieldTypeName return 0, fieldsrc } -func (m *SecurityList) ApplyColumnSecurity(records reflect.Value, modelType reflect.Type, pUserID int, pSchema, pTablename string) (error, reflect.Value) { +func (m *SecurityList) ApplyColumnSecurity(records reflect.Value, modelType reflect.Type, pUserID int, pSchema, pTablename string) (reflect.Value, error) { defer logger.CatchPanic("ApplyColumnSecurity") if m.ColumnSecurity == nil { - return fmt.Errorf("security not initialized"), records + return records, fmt.Errorf("security not initialized") } m.ColumnSecurityMutex.RLock() @@ -312,10 +315,11 @@ func (m *SecurityList) ApplyColumnSecurity(records reflect.Value, modelType refl colsecList, ok := m.ColumnSecurity[fmt.Sprintf("%s.%s@%d", pSchema, pTablename, pUserID)] if !ok || colsecList == nil { - return fmt.Errorf("no security data"), records + return records, fmt.Errorf("no security data") } - for _, colsec := range colsecList { + for i := range colsecList { + colsec := &colsecList[i] if !strings.EqualFold(colsec.Accesstype, "mask") && !strings.EqualFold(colsec.Accesstype, "hide") { continue } @@ -353,7 +357,7 @@ func (m *SecurityList) ApplyColumnSecurity(records reflect.Value, modelType refl if i == pathLen-1 { if nameType == "sql" || nameType == "struct" { - setColSecValue(field, colsec, fieldName) + setColSecValue(field, *colsec, fieldName) } break } @@ -365,7 +369,7 @@ func (m *SecurityList) ApplyColumnSecurity(records reflect.Value, modelType refl } } - return nil, records + return records, nil } func (m *SecurityList) LoadColumnSecurity(pUserID int, pSchema, pTablename string, pOverwrite bool) error { @@ -407,9 +411,10 @@ func (m *SecurityList) ClearSecurity(pUserID int, pSchema, pTablename string) er return nil } - for _, cs := range list { + for i := range list { + cs := &list[i] if cs.Schema != pSchema && cs.Tablename != pTablename && cs.UserID != pUserID { - filtered = append(filtered, cs) + filtered = append(filtered, *cs) } }