Add linter, fix linting issue

This commit is contained in:
David Stotijn
2021-04-25 16:23:53 +02:00
parent ad3dc0da70
commit ca3a729c36
18 changed files with 442 additions and 231 deletions

View File

@ -43,7 +43,7 @@ func (u *reqURL) Scan(value interface{}) error {
parsed, err := url.Parse(rawURL)
if err != nil {
return fmt.Errorf("sqlite: could not parse URL: %v", err)
return fmt.Errorf("sqlite: could not parse URL: %w", err)
}
*u = reqURL(*parsed)

View File

@ -6,6 +6,7 @@ import (
"sort"
sq "github.com/Masterminds/squirrel"
"github.com/dstotijn/hetty/pkg/search"
)
@ -57,20 +58,24 @@ func parseInfixExpr(expr *search.InfixExpression) (sq.Sqlizer, error) {
if err != nil {
return nil, err
}
right, err := parseSearchExpr(expr.Right)
if err != nil {
return nil, err
}
return sq.And{left, right}, nil
case search.TokOpOr:
left, err := parseSearchExpr(expr.Left)
if err != nil {
return nil, err
}
right, err := parseSearchExpr(expr.Right)
if err != nil {
return nil, err
}
return sq.Or{left, right}, nil
}
@ -78,6 +83,7 @@ func parseInfixExpr(expr *search.InfixExpression) (sq.Sqlizer, error) {
if !ok {
return nil, errors.New("left operand must be a string literal")
}
right, ok := expr.Right.(*search.StringLiteral)
if !ok {
return nil, errors.New("right operand must be a string literal")
@ -113,14 +119,17 @@ func parseInfixExpr(expr *search.InfixExpression) (sq.Sqlizer, error) {
func parseStringLiteral(strLiteral *search.StringLiteral) (sq.Sqlizer, error) {
// Sorting is not necessary, but makes it easier to do assertions in tests.
sortedKeys := make([]string, 0, len(stringLiteralMap))
for _, v := range stringLiteralMap {
sortedKeys = append(sortedKeys, v)
}
sort.Strings(sortedKeys)
or := make(sq.Or, len(stringLiteralMap))
for i, value := range sortedKeys {
or[i] = sq.Like{value: "%" + strLiteral.Value + "%"}
}
return or, nil
}

View File

@ -10,6 +10,8 @@ import (
)
func TestParseSearchExpr(t *testing.T) {
t.Parallel()
tests := []struct {
name string
searchExpr search.Expression
@ -206,6 +208,8 @@ func TestParseSearchExpr(t *testing.T) {
}
func assertError(t *testing.T, exp, got error) {
t.Helper()
switch {
case exp == nil && got != nil:
t.Fatalf("expected: nil, got: %v", got)

View File

@ -15,14 +15,14 @@ import (
"strings"
"time"
"github.com/dstotijn/hetty/pkg/proj"
"github.com/dstotijn/hetty/pkg/reqlog"
"github.com/dstotijn/hetty/pkg/scope"
"github.com/99designs/gqlgen/graphql"
sq "github.com/Masterminds/squirrel"
"github.com/jmoiron/sqlx"
"github.com/mattn/go-sqlite3"
"github.com/dstotijn/hetty/pkg/proj"
"github.com/dstotijn/hetty/pkg/reqlog"
"github.com/dstotijn/hetty/pkg/scope"
)
var regexpFn = func(pattern string, value interface{}) (bool, error) {
@ -55,10 +55,7 @@ type httpRequestLogsQuery struct {
func init() {
sql.Register("sqlite3_with_regexp", &sqlite3.SQLiteDriver{
ConnectHook: func(conn *sqlite3.SQLiteConn) error {
if err := conn.RegisterFunc("regexp", regexpFn, false); err != nil {
return err
}
return nil
return conn.RegisterFunc("regexp", regexpFn, false)
},
})
}
@ -66,9 +63,10 @@ func init() {
func New(dbPath string) (*Client, error) {
if _, err := os.Stat(dbPath); os.IsNotExist(err) {
if err := os.MkdirAll(dbPath, 0755); err != nil {
return nil, fmt.Errorf("proj: could not create project directory: %v", err)
return nil, fmt.Errorf("proj: could not create project directory: %w", err)
}
}
return &Client{
dbPath: dbPath,
}, nil
@ -85,17 +83,18 @@ func (c *Client) OpenProject(name string) error {
dbPath := filepath.Join(c.dbPath, name+".db")
dsn := fmt.Sprintf("file:%v?%v", dbPath, opts.Encode())
db, err := sqlx.Open("sqlite3_with_regexp", dsn)
if err != nil {
return fmt.Errorf("sqlite: could not open database: %v", err)
return fmt.Errorf("sqlite: could not open database: %w", err)
}
if err := db.Ping(); err != nil {
return fmt.Errorf("sqlite: could not ping database: %v", err)
return fmt.Errorf("sqlite: could not ping database: %w", err)
}
if err := prepareSchema(db); err != nil {
return fmt.Errorf("sqlite: could not prepare schema: %v", err)
return fmt.Errorf("sqlite: could not prepare schema: %w", err)
}
c.db = db
@ -107,10 +106,11 @@ func (c *Client) OpenProject(name string) error {
func (c *Client) Projects() ([]proj.Project, error) {
files, err := ioutil.ReadDir(c.dbPath)
if err != nil {
return nil, fmt.Errorf("sqlite: could not read projects directory: %v", err)
return nil, fmt.Errorf("sqlite: could not read projects directory: %w", err)
}
projects := make([]proj.Project, len(files))
for i, file := range files {
projName := strings.TrimSuffix(file.Name(), ".db")
projects[i] = proj.Project{
@ -132,7 +132,7 @@ func prepareSchema(db *sqlx.DB) error {
timestamp DATETIME
)`)
if err != nil {
return fmt.Errorf("could not create http_requests table: %v", err)
return fmt.Errorf("could not create http_requests table: %w", err)
}
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS http_responses (
@ -145,7 +145,7 @@ func prepareSchema(db *sqlx.DB) error {
timestamp DATETIME
)`)
if err != nil {
return fmt.Errorf("could not create http_responses table: %v", err)
return fmt.Errorf("could not create http_responses table: %w", err)
}
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS http_headers (
@ -156,7 +156,7 @@ func prepareSchema(db *sqlx.DB) error {
value TEXT
)`)
if err != nil {
return fmt.Errorf("could not create http_headers table: %v", err)
return fmt.Errorf("could not create http_headers table: %w", err)
}
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS settings (
@ -164,7 +164,7 @@ func prepareSchema(db *sqlx.DB) error {
settings TEXT
)`)
if err != nil {
return fmt.Errorf("could not create settings table: %v", err)
return fmt.Errorf("could not create settings table: %w", err)
}
return nil
@ -175,8 +175,9 @@ func (c *Client) Close() error {
if c.db == nil {
return nil
}
if err := c.db.Close(); err != nil {
return fmt.Errorf("sqlite: could not close database: %v", err)
return fmt.Errorf("sqlite: could not close database: %w", err)
}
c.db = nil
@ -187,7 +188,7 @@ func (c *Client) Close() error {
func (c *Client) DeleteProject(name string) error {
if err := os.Remove(filepath.Join(c.dbPath, name+".db")); err != nil {
return fmt.Errorf("sqlite: could not remove database file: %v", err)
return fmt.Errorf("sqlite: could not remove database file: %w", err)
}
return nil
@ -219,10 +220,12 @@ func (c *Client) ClearRequestLogs(ctx context.Context) error {
if c.db == nil {
return proj.ErrNoProject
}
_, err := c.db.Exec("DELETE FROM http_requests")
if err != nil {
return fmt.Errorf("sqlite: could not delete requests: %v", err)
return fmt.Errorf("sqlite: could not delete requests: %w", err)
}
return nil
}
@ -247,11 +250,13 @@ func (c *Client) FindRequestLogs(
if filter.OnlyInScope && scope != nil {
var ruleExpr []sq.Sqlizer
for _, rule := range scope.Rules() {
if rule.URL != nil {
ruleExpr = append(ruleExpr, sq.Expr("regexp(?, req.url)", rule.URL.String()))
}
}
if len(ruleExpr) > 0 {
reqQuery = reqQuery.Where(sq.Or(ruleExpr))
}
@ -260,37 +265,41 @@ func (c *Client) FindRequestLogs(
if filter.SearchExpr != nil {
sqlizer, err := parseSearchExpr(filter.SearchExpr)
if err != nil {
return nil, fmt.Errorf("sqlite: could not parse search expression: %v", err)
return nil, fmt.Errorf("sqlite: could not parse search expression: %w", err)
}
reqQuery = reqQuery.Where(sqlizer)
}
sql, args, err := reqQuery.ToSql()
if err != nil {
return nil, fmt.Errorf("sqlite: could not parse query: %v", err)
return nil, fmt.Errorf("sqlite: could not parse query: %w", err)
}
rows, err := c.db.QueryxContext(ctx, sql, args...)
if err != nil {
return nil, fmt.Errorf("sqlite: could not execute query: %v", err)
return nil, fmt.Errorf("sqlite: could not execute query: %w", err)
}
defer rows.Close()
for rows.Next() {
var dto httpRequest
err = rows.StructScan(&dto)
if err != nil {
return nil, fmt.Errorf("sqlite: could not scan row: %v", err)
return nil, fmt.Errorf("sqlite: could not scan row: %w", err)
}
reqLogs = append(reqLogs, dto.toRequestLog())
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("sqlite: could not iterate over rows: %v", err)
return nil, fmt.Errorf("sqlite: could not iterate over rows: %w", err)
}
rows.Close()
defer rows.Close()
if err := c.queryHeaders(ctx, httpReqLogsQuery, reqLogs); err != nil {
return nil, fmt.Errorf("sqlite: could not query headers: %v", err)
return nil, fmt.Errorf("sqlite: could not query headers: %w", err)
}
return reqLogs, nil
@ -300,35 +309,38 @@ func (c *Client) FindRequestLogByID(ctx context.Context, id int64) (reqlog.Reque
if c.db == nil {
return reqlog.Request{}, proj.ErrNoProject
}
httpReqLogsQuery := parseHTTPRequestLogsQuery(ctx)
httpReqLogsQuery := parseHTTPRequestLogsQuery(ctx)
reqQuery := sq.
Select(httpReqLogsQuery.requestCols...).
From("http_requests req").
Where("req.id = ?")
if httpReqLogsQuery.joinResponse {
reqQuery = reqQuery.LeftJoin("http_responses res ON req.id = res.req_id")
}
reqSQL, _, err := reqQuery.ToSql()
if err != nil {
return reqlog.Request{}, fmt.Errorf("sqlite: could not parse query: %v", err)
return reqlog.Request{}, fmt.Errorf("sqlite: could not parse query: %w", err)
}
row := c.db.QueryRowxContext(ctx, reqSQL, id)
var dto httpRequest
err = row.StructScan(&dto)
if err == sql.ErrNoRows {
return reqlog.Request{}, reqlog.ErrRequestNotFound
}
if err != nil {
return reqlog.Request{}, fmt.Errorf("sqlite: could not scan row: %v", err)
}
reqLog := dto.toRequestLog()
var dto httpRequest
err = row.StructScan(&dto)
if errors.Is(err, sql.ErrNoRows) {
return reqlog.Request{}, reqlog.ErrRequestNotFound
} else if err != nil {
return reqlog.Request{}, fmt.Errorf("sqlite: could not scan row: %w", err)
}
reqLog := dto.toRequestLog()
reqLogs := []reqlog.Request{reqLog}
if err := c.queryHeaders(ctx, httpReqLogsQuery, reqLogs); err != nil {
return reqlog.Request{}, fmt.Errorf("sqlite: could not query headers: %v", err)
return reqlog.Request{}, fmt.Errorf("sqlite: could not query headers: %w", err)
}
return reqLogs[0], nil
@ -352,8 +364,9 @@ func (c *Client) AddRequestLog(
tx, err := c.db.BeginTxx(ctx, nil)
if err != nil {
return nil, fmt.Errorf("sqlite: could not start transaction: %v", err)
return nil, fmt.Errorf("sqlite: could not start transaction: %w", err)
}
defer tx.Rollback()
reqStmt, err := tx.PrepareContext(ctx, `INSERT INTO http_requests (
@ -364,7 +377,7 @@ func (c *Client) AddRequestLog(
timestamp
) VALUES (?, ?, ?, ?, ?)`)
if err != nil {
return nil, fmt.Errorf("sqlite: could not prepare statement: %v", err)
return nil, fmt.Errorf("sqlite: could not prepare statement: %w", err)
}
defer reqStmt.Close()
@ -376,13 +389,14 @@ func (c *Client) AddRequestLog(
reqLog.Timestamp,
)
if err != nil {
return nil, fmt.Errorf("sqlite: could not execute statement: %v", err)
return nil, fmt.Errorf("sqlite: could not execute statement: %w", err)
}
reqID, err := result.LastInsertId()
if err != nil {
return nil, fmt.Errorf("sqlite: could not get last insert ID: %v", err)
return nil, fmt.Errorf("sqlite: could not get last insert ID: %w", err)
}
reqLog.ID = reqID
headerStmt, err := tx.PrepareContext(ctx, `INSERT INTO http_headers (
@ -391,17 +405,17 @@ func (c *Client) AddRequestLog(
value
) VALUES (?, ?, ?)`)
if err != nil {
return nil, fmt.Errorf("sqlite: could not prepare statement: %v", err)
return nil, fmt.Errorf("sqlite: could not prepare statement: %w", err)
}
defer headerStmt.Close()
err = insertHeaders(ctx, headerStmt, reqID, reqLog.Request.Header)
if err != nil {
return nil, fmt.Errorf("sqlite: could not insert http headers: %v", err)
return nil, fmt.Errorf("sqlite: could not insert http headers: %w", err)
}
if err := tx.Commit(); err != nil {
return nil, fmt.Errorf("sqlite: could not commit transaction: %v", err)
return nil, fmt.Errorf("sqlite: could not commit transaction: %w", err)
}
return reqLog, nil
@ -424,9 +438,10 @@ func (c *Client) AddResponseLog(
Body: body,
Timestamp: timestamp,
}
tx, err := c.db.BeginTx(ctx, nil)
if err != nil {
return nil, fmt.Errorf("sqlite: could not start transaction: %v", err)
return nil, fmt.Errorf("sqlite: could not start transaction: %w", err)
}
defer tx.Rollback()
@ -439,7 +454,7 @@ func (c *Client) AddResponseLog(
timestamp
) VALUES (?, ?, ?, ?, ?, ?)`)
if err != nil {
return nil, fmt.Errorf("sqlite: could not prepare statement: %v", err)
return nil, fmt.Errorf("sqlite: could not prepare statement: %w", err)
}
defer resStmt.Close()
@ -457,13 +472,14 @@ func (c *Client) AddResponseLog(
resLog.Timestamp,
)
if err != nil {
return nil, fmt.Errorf("sqlite: could not execute statement: %v", err)
return nil, fmt.Errorf("sqlite: could not execute statement: %w", err)
}
resID, err := result.LastInsertId()
if err != nil {
return nil, fmt.Errorf("sqlite: could not get last insert ID: %v", err)
return nil, fmt.Errorf("sqlite: could not get last insert ID: %w", err)
}
resLog.ID = resID
headerStmt, err := tx.PrepareContext(ctx, `INSERT INTO http_headers (
@ -472,17 +488,17 @@ func (c *Client) AddResponseLog(
value
) VALUES (?, ?, ?)`)
if err != nil {
return nil, fmt.Errorf("sqlite: could not prepare statement: %v", err)
return nil, fmt.Errorf("sqlite: could not prepare statement: %w", err)
}
defer headerStmt.Close()
err = insertHeaders(ctx, headerStmt, resID, resLog.Response.Header)
if err != nil {
return nil, fmt.Errorf("sqlite: could not insert http headers: %v", err)
return nil, fmt.Errorf("sqlite: could not insert http headers: %w", err)
}
if err := tx.Commit(); err != nil {
return nil, fmt.Errorf("sqlite: could not commit transaction: %v", err)
return nil, fmt.Errorf("sqlite: could not commit transaction: %w", err)
}
return resLog, nil
@ -496,14 +512,14 @@ func (c *Client) UpsertSettings(ctx context.Context, module string, settings int
jsonSettings, err := json.Marshal(settings)
if err != nil {
return fmt.Errorf("sqlite: could not encode settings as JSON: %v", err)
return fmt.Errorf("sqlite: could not encode settings as JSON: %w", err)
}
_, err = c.db.ExecContext(ctx,
`INSERT INTO settings (module, settings) VALUES (?, ?)
ON CONFLICT(module) DO UPDATE SET settings = ?`, module, jsonSettings, jsonSettings)
if err != nil {
return fmt.Errorf("sqlite: could not insert scope settings: %v", err)
return fmt.Errorf("sqlite: could not insert scope settings: %w", err)
}
return nil
@ -515,17 +531,18 @@ func (c *Client) FindSettingsByModule(ctx context.Context, module string, settin
}
var jsonSettings []byte
row := c.db.QueryRowContext(ctx, `SELECT settings FROM settings WHERE module = ?`, module)
err := row.Scan(&jsonSettings)
if err == sql.ErrNoRows {
if errors.Is(err, sql.ErrNoRows) {
return proj.ErrNoSettings
}
if err != nil {
return fmt.Errorf("sqlite: could not scan row: %v", err)
} else if err != nil {
return fmt.Errorf("sqlite: could not scan row: %w", err)
}
if err := json.Unmarshal(jsonSettings, &settings); err != nil {
return fmt.Errorf("sqlite: could not decode settings from JSON: %v", err)
return fmt.Errorf("sqlite: could not decode settings from JSON: %w", err)
}
return nil
@ -535,42 +552,46 @@ func insertHeaders(ctx context.Context, stmt *sql.Stmt, id int64, headers http.H
for key, values := range headers {
for _, value := range values {
if _, err := stmt.ExecContext(ctx, id, key, value); err != nil {
return fmt.Errorf("could not execute statement: %v", err)
return fmt.Errorf("could not execute statement: %w", err)
}
}
}
return nil
}
func findHeaders(ctx context.Context, stmt *sql.Stmt, id int64) (http.Header, error) {
headers := make(http.Header)
rows, err := stmt.QueryContext(ctx, id)
if err != nil {
return nil, fmt.Errorf("sqlite: could not execute query: %v", err)
return nil, fmt.Errorf("sqlite: could not execute query: %w", err)
}
defer rows.Close()
for rows.Next() {
var key, value string
err := rows.Scan(
&key,
&value,
)
err := rows.Scan(&key, &value)
if err != nil {
return nil, fmt.Errorf("sqlite: could not scan row: %v", err)
return nil, fmt.Errorf("sqlite: could not scan row: %w", err)
}
headers.Add(key, value)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("sqlite: could not iterate over rows: %v", err)
return nil, fmt.Errorf("sqlite: could not iterate over rows: %w", err)
}
return headers, nil
}
func parseHTTPRequestLogsQuery(ctx context.Context) httpRequestLogsQuery {
var joinResponse bool
var reqHeaderCols, resHeaderCols []string
var (
joinResponse bool
reqHeaderCols, resHeaderCols []string
)
opCtx := graphql.GetOperationContext(ctx)
reqFields := graphql.CollectFieldsCtx(ctx, nil)
@ -580,6 +601,7 @@ func parseHTTPRequestLogsQuery(ctx context.Context) httpRequestLogsQuery {
if col, ok := reqFieldToColumnMap[reqField.Name]; ok {
reqCols = append(reqCols, "req."+col)
}
if reqField.Name == "headers" {
headerFields := graphql.CollectFields(opCtx, reqField.Selections, nil)
for _, headerField := range headerFields {
@ -588,19 +610,23 @@ func parseHTTPRequestLogsQuery(ctx context.Context) httpRequestLogsQuery {
}
}
}
if reqField.Name == "response" {
joinResponse = true
resFields := graphql.CollectFields(opCtx, reqField.Selections, nil)
for _, resField := range resFields {
if resField.Name == "headers" {
reqCols = append(reqCols, "res.id AS res_id")
headerFields := graphql.CollectFields(opCtx, resField.Selections, nil)
for _, headerField := range headerFields {
if col, ok := headerFieldToColumnMap[headerField.Name]; ok {
resHeaderCols = append(resHeaderCols, col)
}
}
}
if col, ok := resFieldToColumnMap[resField.Name]; ok {
reqCols = append(reqCols, "res."+col)
}
@ -627,18 +653,21 @@ func (c *Client) queryHeaders(
From("http_headers").Where("req_id = ?").
ToSql()
if err != nil {
return fmt.Errorf("could not parse request headers query: %v", err)
return fmt.Errorf("could not parse request headers query: %w", err)
}
reqHeadersStmt, err := c.db.PrepareContext(ctx, reqHeadersQuery)
if err != nil {
return fmt.Errorf("could not prepare statement: %v", err)
return fmt.Errorf("could not prepare statement: %w", err)
}
defer reqHeadersStmt.Close()
for i := range reqLogs {
headers, err := findHeaders(ctx, reqHeadersStmt, reqLogs[i].ID)
if err != nil {
return fmt.Errorf("could not query request headers: %v", err)
return fmt.Errorf("could not query request headers: %w", err)
}
reqLogs[i].Request.Header = headers
}
}
@ -649,21 +678,25 @@ func (c *Client) queryHeaders(
From("http_headers").Where("res_id = ?").
ToSql()
if err != nil {
return fmt.Errorf("could not parse response headers query: %v", err)
return fmt.Errorf("could not parse response headers query: %w", err)
}
resHeadersStmt, err := c.db.PrepareContext(ctx, resHeadersQuery)
if err != nil {
return fmt.Errorf("could not prepare statement: %v", err)
return fmt.Errorf("could not prepare statement: %w", err)
}
defer resHeadersStmt.Close()
for i := range reqLogs {
if reqLogs[i].Response == nil {
continue
}
headers, err := findHeaders(ctx, resHeadersStmt, reqLogs[i].Response.ID)
if err != nil {
return fmt.Errorf("could not query response headers: %v", err)
return fmt.Errorf("could not query response headers: %w", err)
}
reqLogs[i].Response.Response.Header = headers
}
}