diff --git a/internal/tester/delivery/rest/handlers.go b/internal/tester/delivery/rest/handlers.go index e926057..b9a5edb 100644 --- a/internal/tester/delivery/rest/handlers.go +++ b/internal/tester/delivery/rest/handlers.go @@ -2,6 +2,7 @@ package rest import ( "git.sch9.ru/new_gate/ms-tester/internal/tester" + "git.sch9.ru/new_gate/ms-tester/pkg" testerv1 "git.sch9.ru/new_gate/ms-tester/proto/tester/v1" "github.com/gofiber/fiber/v2" ) @@ -29,7 +30,7 @@ func (h *TesterHandlers) ListProblems(c *fiber.Ctx) error { func (h *TesterHandlers) CreateContest(c *fiber.Ctx) error { id, err := h.contestsUC.CreateContest(c.Context(), "Название контеста") if err != nil { - return err + return c.SendStatus(pkg.ToREST(err)) } return c.JSON(testerv1.CreateContestResponse{ @@ -40,7 +41,7 @@ func (h *TesterHandlers) CreateContest(c *fiber.Ctx) error { func (h *TesterHandlers) DeleteContest(c *fiber.Ctx, id int32) error { err := h.contestsUC.DeleteContest(c.Context(), id) if err != nil { - return err + return c.SendStatus(pkg.ToREST(err)) } return c.SendStatus(fiber.StatusOK) @@ -49,7 +50,7 @@ func (h *TesterHandlers) DeleteContest(c *fiber.Ctx, id int32) error { func (h *TesterHandlers) GetContest(c *fiber.Ctx, id int32) error { contest, err := h.contestsUC.ReadContestById(c.Context(), id) if err != nil { - return err + return c.SendStatus(pkg.ToREST(err)) } return c.JSON(testerv1.GetContestResponse{ @@ -65,7 +66,7 @@ func (h *TesterHandlers) GetContest(c *fiber.Ctx, id int32) error { func (h *TesterHandlers) DeleteParticipant(c *fiber.Ctx, id int32, params testerv1.DeleteParticipantParams) error { err := h.contestsUC.DeleteParticipant(c.Context(), params.ParticipantId) if err != nil { - return err + return c.SendStatus(pkg.ToREST(err)) } return c.SendStatus(fiber.StatusOK) @@ -74,7 +75,7 @@ func (h *TesterHandlers) DeleteParticipant(c *fiber.Ctx, id int32, params tester func (h *TesterHandlers) AddParticipant(c *fiber.Ctx, id int32, params testerv1.AddParticipantParams) error { id, err := h.contestsUC.AddParticipant(c.Context(), id, params.UserId) if err != nil { - return err + return c.SendStatus(pkg.ToREST(err)) } return c.JSON(testerv1.AddParticipantResponse{ @@ -85,7 +86,7 @@ func (h *TesterHandlers) AddParticipant(c *fiber.Ctx, id int32, params testerv1. func (h *TesterHandlers) DeleteTask(c *fiber.Ctx, id int32, params testerv1.DeleteTaskParams) error { err := h.contestsUC.DeleteTask(c.Context(), params.TaskId) if err != nil { - return err + return c.SendStatus(pkg.ToREST(err)) } return c.SendStatus(fiber.StatusOK) @@ -94,7 +95,7 @@ func (h *TesterHandlers) DeleteTask(c *fiber.Ctx, id int32, params testerv1.Dele func (h *TesterHandlers) AddTask(c *fiber.Ctx, id int32, params testerv1.AddTaskParams) error { id, err := h.contestsUC.AddTask(c.Context(), id, params.ProblemId) if err != nil { - return err + return c.SendStatus(pkg.ToREST(err)) } return c.SendStatus(fiber.StatusNotImplemented) @@ -103,7 +104,7 @@ func (h *TesterHandlers) AddTask(c *fiber.Ctx, id int32, params testerv1.AddTask func (h *TesterHandlers) CreateProblem(c *fiber.Ctx) error { id, err := h.problemsUC.CreateProblem(c.Context(), "Название задачи") if err != nil { - return err + return c.SendStatus(pkg.ToREST(err)) } return c.JSON(testerv1.CreateProblemResponse{ @@ -114,7 +115,7 @@ func (h *TesterHandlers) CreateProblem(c *fiber.Ctx) error { func (h *TesterHandlers) DeleteProblem(c *fiber.Ctx, id int32) error { err := h.problemsUC.DeleteProblem(c.Context(), id) if err != nil { - return err + return c.SendStatus(pkg.ToREST(err)) } return c.SendStatus(fiber.StatusOK) @@ -124,7 +125,7 @@ func (h *TesterHandlers) DeleteProblem(c *fiber.Ctx, id int32) error { func (h *TesterHandlers) GetProblem(c *fiber.Ctx, id int32) error { problem, err := h.problemsUC.ReadProblemById(c.Context(), id) if err != nil { - return err + return c.SendStatus(pkg.ToREST(err)) } return c.JSON( diff --git a/internal/tester/repository/error.go b/internal/tester/repository/error.go index cdd4408..53b33a0 100644 --- a/internal/tester/repository/error.go +++ b/internal/tester/repository/error.go @@ -1,23 +1,27 @@ package repository import ( + "database/sql" "errors" - "git.sch9.ru/new_gate/ms-tester/pkg/utils" + "git.sch9.ru/new_gate/ms-tester/pkg" "github.com/jackc/pgerrcode" "github.com/jackc/pgx/v5/pgconn" ) -func handlePgErr(err error) error { +func handlePgErr(err error, op string) error { var pgErr *pgconn.PgError - if !errors.As(err, &pgErr) { - return utils.StorageError(err, utils.ErrUnknown, "unexpected error from postgres") + if errors.As(err, &pgErr) { + if pgerrcode.IsIntegrityConstraintViolation(pgErr.Code) { + return pkg.Wrap(pkg.ErrBadInput, err, op, pgErr.Message) + } + if pgerrcode.IsNoData(pgErr.Code) { + return pkg.Wrap(pkg.ErrNotFound, err, op, pgErr.Message) + } } - if pgerrcode.IsIntegrityConstraintViolation(pgErr.Code) { - // TODO: probably should specify which constraint - return utils.StorageError(err, utils.ErrConflict, pgErr.Message) + + if errors.Is(err, sql.ErrNoRows) { + return pkg.Wrap(pkg.ErrNotFound, err, op, "no rows found") } - if pgerrcode.IsNoData(pgErr.Code) { - return utils.StorageError(err, utils.ErrNotFound, pgErr.Message) - } - return utils.StorageError(err, utils.ErrUnimplemented, "unimplemented error") + + return pkg.Wrap(pkg.ErrUnhandled, err, op, "unexpected error") } diff --git a/internal/tester/repository/pg_contests_repository.go b/internal/tester/repository/pg_contests_repository.go index 6cc5ca1..406cb90 100644 --- a/internal/tester/repository/pg_contests_repository.go +++ b/internal/tester/repository/pg_contests_repository.go @@ -22,11 +22,13 @@ func NewContestRepository(db *sqlx.DB, logger *zap.Logger) *ContestRepository { const createContestQuery = "INSERT INTO contests (title) VALUES (?) RETURNING id" func (r *ContestRepository) CreateContest(ctx context.Context, title string) (int32, error) { + const op = "ContestRepository.CreateContest" + query := r.db.Rebind(createContestQuery) rows, err := r.db.QueryxContext(ctx, query, title) if err != nil { - return 0, handlePgErr(err) + return 0, handlePgErr(err, op) } defer rows.Close() @@ -34,7 +36,7 @@ func (r *ContestRepository) CreateContest(ctx context.Context, title string) (in rows.Next() err = rows.Scan(&id) if err != nil { - return 0, handlePgErr(err) + return 0, handlePgErr(err, op) } return id, nil @@ -43,11 +45,13 @@ func (r *ContestRepository) CreateContest(ctx context.Context, title string) (in const readContestByIdQuery = "SELECT * from contests WHERE id=? LIMIT 1" func (r *ContestRepository) ReadContestById(ctx context.Context, id int32) (*models.Contest, error) { + const op = "ContestRepository.ReadContestById" + var contest models.Contest query := r.db.Rebind(readContestByIdQuery) err := r.db.GetContext(ctx, &contest, query, id) if err != nil { - return nil, handlePgErr(err) + return nil, handlePgErr(err, op) } return &contest, nil } @@ -55,10 +59,12 @@ func (r *ContestRepository) ReadContestById(ctx context.Context, id int32) (*mod const deleteContestQuery = "DELETE FROM contests WHERE id=?" func (r *ContestRepository) DeleteContest(ctx context.Context, id int32) error { + const op = "ContestRepository.DeleteContest" + query := r.db.Rebind(deleteContestQuery) _, err := r.db.ExecContext(ctx, query, id) if err != nil { - return handlePgErr(err) + return handlePgErr(err, op) } return nil @@ -67,17 +73,19 @@ func (r *ContestRepository) DeleteContest(ctx context.Context, id int32) error { const addTaskQuery = "INSERT INTO tasks (problem_id, contest_id, position) VALUES (?, ?,COALESCE(SELECT MAX(position) FROM task WHERE contest_id = ? ,0) + 1) RETURNING id" func (r *ContestRepository) AddTask(ctx context.Context, contestId int32, problem_id int32) (int32, error) { + const op = "ContestRepository.AddTask" + query := r.db.Rebind(addTaskQuery) rows, err := r.db.QueryxContext(ctx, query, problem_id, contestId, contestId) if err != nil { - return 0, handlePgErr(err) + return 0, handlePgErr(err, op) } defer rows.Close() var id int32 rows.Next() err = rows.Scan(&id) if err != nil { - return 0, handlePgErr(err) + return 0, handlePgErr(err, op) } return id, nil } @@ -85,10 +93,12 @@ func (r *ContestRepository) AddTask(ctx context.Context, contestId int32, proble const deleteTaskQuery = "DELETE FROM tasks WHERE id=?" func (r *ContestRepository) DeleteTask(ctx context.Context, taskId int32) error { + const op = "ContestRepository.DeleteTask" + query := r.db.Rebind(deleteTaskQuery) _, err := r.db.ExecContext(ctx, query, taskId) if err != nil { - return handlePgErr(err) + return handlePgErr(err, op) } return nil } @@ -96,11 +106,13 @@ func (r *ContestRepository) DeleteTask(ctx context.Context, taskId int32) error const addParticipantQuery = "INSERT INTO participants (user_id ,contest_id, name) VALUES (?, ?, ?) RETURNING id" func (r *ContestRepository) AddParticipant(ctx context.Context, contestId int32, userId int32) (int32, error) { + const op = "ContestRepository.AddParticipant" + query := r.db.Rebind(addParticipantQuery) name := "" rows, err := r.db.QueryxContext(ctx, query, contestId, userId, name) if err != nil { - return 0, handlePgErr(err) + return 0, handlePgErr(err, op) } defer rows.Close() var id int32 @@ -115,10 +127,12 @@ func (r *ContestRepository) AddParticipant(ctx context.Context, contestId int32, const deleteParticipantQuery = "DELETE FROM participants WHERE id=?" func (r *ContestRepository) DeleteParticipant(ctx context.Context, participantId int32) error { + const op = "ContestRepository.DeleteParticipant" + query := r.db.Rebind(deleteParticipantQuery) _, err := r.db.ExecContext(ctx, query, participantId) if err != nil { - return handlePgErr(err) + return handlePgErr(err, op) } return nil } diff --git a/internal/tester/repository/pg_problems_repository.go b/internal/tester/repository/pg_problems_repository.go index 6c66414..8758481 100644 --- a/internal/tester/repository/pg_problems_repository.go +++ b/internal/tester/repository/pg_problems_repository.go @@ -22,10 +22,12 @@ func NewProblemRepository(db *sqlx.DB, logger *zap.Logger) *ProblemRepository { const createProblemQuery = "INSERT INTO problems (title) VALUES (?) RETURNING id" func (r *ProblemRepository) CreateProblem(ctx context.Context, title string) (int32, error) { + const op = "ProblemRepository.CreateProblem" + query := r.db.Rebind(createProblemQuery) rows, err := r.db.QueryxContext(ctx, query, title) if err != nil { - return 0, handlePgErr(err) + return 0, handlePgErr(err, op) } defer rows.Close() @@ -33,7 +35,7 @@ func (r *ProblemRepository) CreateProblem(ctx context.Context, title string) (in rows.Next() err = rows.Scan(&id) if err != nil { - return 0, handlePgErr(err) + return 0, handlePgErr(err, op) } return id, nil @@ -42,11 +44,13 @@ func (r *ProblemRepository) CreateProblem(ctx context.Context, title string) (in const readProblemQuery = "SELECT * from problems WHERE id=? LIMIT 1" func (r *ProblemRepository) ReadProblemById(ctx context.Context, id int32) (*models.Problem, error) { + const op = "ProblemRepository.ReadProblemById" + var problem models.Problem query := r.db.Rebind(readProblemQuery) err := r.db.GetContext(ctx, &problem, query, id) if err != nil { - return nil, handlePgErr(err) + return nil, handlePgErr(err, op) } return &problem, nil } @@ -54,10 +58,12 @@ func (r *ProblemRepository) ReadProblemById(ctx context.Context, id int32) (*mod const deleteProblemQuery = "DELETE FROM problems WHERE id=?" func (r *ProblemRepository) DeleteProblem(ctx context.Context, id int32) error { + const op = "ProblemRepository.DeleteProblem" + query := r.db.Rebind(deleteProblemQuery) _, err := r.db.ExecContext(ctx, query, id) if err != nil { - return handlePgErr(err) + return handlePgErr(err, op) } return nil diff --git a/main.go b/main.go index 950e3cc..0aaf32e 100644 --- a/main.go +++ b/main.go @@ -6,7 +6,7 @@ import ( "git.sch9.ru/new_gate/ms-tester/internal/tester/delivery/rest" problemsRepository "git.sch9.ru/new_gate/ms-tester/internal/tester/repository" testerUseCase "git.sch9.ru/new_gate/ms-tester/internal/tester/usecase" - "git.sch9.ru/new_gate/ms-tester/pkg/external/postgres" + "git.sch9.ru/new_gate/ms-tester/pkg" testerv1 "git.sch9.ru/new_gate/ms-tester/proto/tester/v1" "github.com/gofiber/fiber/v2" fiberlogger "github.com/gofiber/fiber/v2/middleware/logger" @@ -34,7 +34,7 @@ func main() { } logger.Info("connecting to postgres") - db, err := postgres.NewPostgresDB(cfg.PostgresDSN) + db, err := pkg.NewPostgresDB(cfg.PostgresDSN) if err != nil { panic(err) } diff --git a/pkg/aws-s3-client.go b/pkg/aws-s3-client.go new file mode 100644 index 0000000..c1caffe --- /dev/null +++ b/pkg/aws-s3-client.go @@ -0,0 +1 @@ +package pkg diff --git a/pkg/errors.go b/pkg/errors.go new file mode 100644 index 0000000..6efa146 --- /dev/null +++ b/pkg/errors.go @@ -0,0 +1,37 @@ +package pkg + +import ( + "errors" + "fmt" + "net/http" +) + +var ( + NoPermission = errors.New("no permission") + ErrUnauthenticated = errors.New("unauthenticated") + ErrUnhandled = errors.New("unhandled") + ErrNotFound = errors.New("not found") + ErrBadInput = errors.New("bad input") + ErrInternal = errors.New("internal") +) + +func Wrap(basic error, err error, op string, msg string) error { + return errors.Join(basic, err, fmt.Errorf("during %s: %s", op, msg)) +} + +func ToREST(err error) int { + switch { + case errors.Is(err, ErrUnauthenticated): + return http.StatusUnauthorized + case errors.Is(err, ErrBadInput): + return http.StatusBadRequest + case errors.Is(err, ErrNotFound): + return http.StatusNotFound + case errors.Is(err, ErrInternal): + return http.StatusInternalServerError + case errors.Is(err, NoPermission): + return http.StatusForbidden + } + + return http.StatusInternalServerError +} diff --git a/pkg/external/aws/client.go b/pkg/external/aws/client.go deleted file mode 100644 index a1f9c0e..0000000 --- a/pkg/external/aws/client.go +++ /dev/null @@ -1 +0,0 @@ -package aws diff --git a/pkg/external/pandoc/client.go b/pkg/pandoc-client.go similarity index 98% rename from pkg/external/pandoc/client.go rename to pkg/pandoc-client.go index af2427a..75a4adf 100644 --- a/pkg/external/pandoc/client.go +++ b/pkg/pandoc-client.go @@ -1,4 +1,4 @@ -package pandoc +package pkg import ( "bytes" diff --git a/pkg/external/postgres/client.go b/pkg/postgres-client.go similarity index 97% rename from pkg/external/postgres/client.go rename to pkg/postgres-client.go index b959972..d8b74a9 100644 --- a/pkg/external/postgres/client.go +++ b/pkg/postgres-client.go @@ -1,4 +1,4 @@ -package postgres +package pkg import ( _ "github.com/jackc/pgx/v5/stdlib" diff --git a/pkg/external/rabbitmq/client.go b/pkg/rabbitmq-client.go similarity index 88% rename from pkg/external/rabbitmq/client.go rename to pkg/rabbitmq-client.go index 09732fa..9d30d48 100644 --- a/pkg/external/rabbitmq/client.go +++ b/pkg/rabbitmq-client.go @@ -1,4 +1,4 @@ -package rabbitmq +package pkg import amqp "github.com/rabbitmq/amqp091-go" diff --git a/pkg/utils/errors.go b/pkg/utils/errors.go deleted file mode 100644 index 98bd286..0000000 --- a/pkg/utils/errors.go +++ /dev/null @@ -1,132 +0,0 @@ -package utils - -import ( - "errors" - "fmt" - "go.uber.org/zap/zapcore" - "runtime" -) - -type code uint8 - -const ( - ErrValidationFailed code = 1 - ErrInternal code = 2 - ErrExternal code = 3 - ErrNoPermission code = 4 - ErrUnknown code = 5 - ErrDeadlineExceeded code = 6 - ErrNotFound code = 7 - ErrAlreadyExists code = 8 - ErrConflict code = 9 - ErrUnimplemented code = 10 - ErrBadInput code = 11 - ErrUnauthenticated code = 12 -) - -func (c code) String() string { - switch { - case errors.Is(c, ErrValidationFailed): - return "validation error" - case errors.Is(c, ErrInternal): - return "internal error" - case errors.Is(c, ErrExternal): - return "external error" - case errors.Is(c, ErrNoPermission): - return "permission error" - case errors.Is(c, ErrUnknown): - return "unknown error" - case errors.Is(c, ErrDeadlineExceeded): - return "deadline error" - case errors.Is(c, ErrNotFound): - return "not found error" - case errors.Is(c, ErrAlreadyExists): - return "already exists error" - case errors.Is(c, ErrConflict): - return "conflict error" - case errors.Is(c, ErrUnimplemented): - return "unimplemented error" - case errors.Is(c, ErrBadInput): - return "bad input error" - } - - panic("unimplemented") -} - -func (c code) Error() string { - return c.String() -} - -type layer uint8 - -const ( - LayerTransport layer = 1 - LayerService layer = 2 - LayerStorage layer = 3 -) - -func (l layer) String() string { - switch l { - case LayerTransport: - return "transport" - case LayerService: - return "service" - case LayerStorage: - return "storage" - } - - panic("unimplemented") -} - -func location(skip int) string { - _, file, line, _ := runtime.Caller(skip) - return fmt.Sprintf("%s:%d", file, line) -} - -type Error struct { - src error - layer layer - code code - msg string - loc string -} - -func wrap(src error, layer layer, class code, msg string, loc string) *Error { - return &Error{ - src: src, - layer: layer, - code: class, - msg: msg, - loc: loc, - } -} - -func (e *Error) Unwrap() []error { - return []error{e.src, e.code} -} - -func (e *Error) Error() string { - return fmt.Sprintf("%s: %s", e.code.String(), e.msg) -} - -func (e *Error) MarshalLogObject(encoder zapcore.ObjectEncoder) error { - if e.src != nil { - encoder.AddString("src", e.src.Error()) - } - encoder.AddString("layer", e.layer.String()) - encoder.AddString("code", e.code.String()) - encoder.AddString("msg", e.msg) - return nil -} - -func TransportError(src error, code code, msg string) error { - return wrap(src, LayerTransport, code, msg, location(2)) -} - -func ServiceError(src error, code code, msg string) error { - return wrap(src, LayerService, code, msg, location(2)) -} - -func StorageError(src error, code code, msg string) error { - return wrap(src, LayerStorage, code, msg, location(2)) -} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go deleted file mode 100644 index 8492339..0000000 --- a/pkg/utils/utils.go +++ /dev/null @@ -1,33 +0,0 @@ -package utils - -import ( - "google.golang.org/protobuf/types/known/timestamppb" - "time" -) - -func AsTimeP(t time.Time) *time.Time { - return &t -} - -func AsInt32P(v int32) *int32 { - return &v -} - -func AsStringP(str string) *string { - return &str -} - -func TimeP(t *timestamppb.Timestamp) *time.Time { - if t == nil { - return nil - } - tt := t.AsTime() - return &tt -} - -func TimestampP(t *time.Time) *timestamppb.Timestamp { - if t == nil { - return nil - } - return timestamppb.New(*t) -} diff --git a/pkg/external/valkey/client.go b/pkg/valkey-client.go similarity index 93% rename from pkg/external/valkey/client.go rename to pkg/valkey-client.go index c5a4900..87e4681 100644 --- a/pkg/external/valkey/client.go +++ b/pkg/valkey-client.go @@ -1,4 +1,4 @@ -package valkey +package pkg import "github.com/valkey-io/valkey-go"