feat: improve error handling
This commit is contained in:
parent
6b20f00c3c
commit
af9ab60092
8 changed files with 290 additions and 90 deletions
|
@ -2,14 +2,87 @@ package lib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
"runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
type code uint8
|
||||||
ErrInternal = errors.New("internal")
|
|
||||||
ErrUnexpected = errors.New("unexpected")
|
const (
|
||||||
ErrNoPermission = errors.New("no permission")
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrBadRole = errors.New("bad role")
|
ErrBadRole = errors.New("bad role")
|
||||||
)
|
)
|
||||||
|
@ -18,3 +91,51 @@ var (
|
||||||
ErrBadTestingStrategy = errors.New("bad testing strategy")
|
ErrBadTestingStrategy = errors.New("bad testing strategy")
|
||||||
ErrBadResult = errors.New("bad result")
|
ErrBadResult = errors.New("bad result")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"git.sch9.ru/new_gate/ms-tester/internal/lib"
|
||||||
"git.sch9.ru/new_gate/ms-tester/internal/models"
|
"git.sch9.ru/new_gate/ms-tester/internal/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -16,9 +17,14 @@ type PandocClient interface {
|
||||||
ConvertLatexToHtml5(ctx context.Context, text string) (string, error)
|
ConvertLatexToHtml5(ctx context.Context, text string) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IPermissionService interface {
|
||||||
|
Allowed(ctx context.Context, user *models.User, action string) bool
|
||||||
|
}
|
||||||
|
|
||||||
type ProblemService struct {
|
type ProblemService struct {
|
||||||
problemStorage ProblemStorage
|
problemStorage ProblemStorage
|
||||||
pandocClient PandocClient
|
pandocClient PandocClient
|
||||||
|
permissionService IPermissionService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProblemService(
|
func NewProblemService(
|
||||||
|
@ -31,30 +37,66 @@ func NewProblemService(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service *ProblemService) CreateProblem(ctx context.Context, problem *models.Problem, ch <-chan []byte) (int32, error) {
|
func extractUser(ctx context.Context) *models.User {
|
||||||
//userId := ctx.Value("user_id").(int32)
|
return ctx.Value("user").(*models.User)
|
||||||
//html, err := service.pandocClient.ConvertLatexToHtml5(*problem.Description)
|
}
|
||||||
//if err != nil {
|
|
||||||
// return 0, err
|
func (service *ProblemService) CanCreateProblem(ctx context.Context) error {
|
||||||
//}
|
if !service.permissionService.Allowed(ctx, extractUser(ctx), "create") {
|
||||||
panic("access control is not implemented yet")
|
return lib.ServiceError(nil, lib.ErrNoPermission, "permission denied")
|
||||||
//return service.problemStorage.CreateProblem(ctx, problem)
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *ProblemService) CanReadProblemById(ctx context.Context) error {
|
||||||
|
if !service.permissionService.Allowed(ctx, extractUser(ctx), "read") {
|
||||||
|
return lib.ServiceError(nil, lib.ErrNoPermission, "permission denied")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *ProblemService) CanUpdateProblem(ctx context.Context) error {
|
||||||
|
if !service.permissionService.Allowed(ctx, extractUser(ctx), "update") {
|
||||||
|
return lib.ServiceError(nil, lib.ErrNoPermission, "permission denied")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *ProblemService) CanDeleteProblem(ctx context.Context) error {
|
||||||
|
if !service.permissionService.Allowed(ctx, extractUser(ctx), "delete") {
|
||||||
|
return lib.ServiceError(nil, lib.ErrNoPermission, "permission denied")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *ProblemService) CreateProblem(ctx context.Context, problem *models.Problem) (int32, error) {
|
||||||
|
if err := service.CanCreateProblem(ctx); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
_, err := service.pandocClient.ConvertLatexToHtml5(ctx, *problem.Description)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return service.problemStorage.CreateProblem(ctx, problem, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service *ProblemService) ReadProblemById(ctx context.Context, id int32) (*models.Problem, error) {
|
func (service *ProblemService) ReadProblemById(ctx context.Context, id int32) (*models.Problem, error) {
|
||||||
//userId := ctx.Value("user_id").(int32)
|
if err := service.CanReadProblemById(ctx); err != nil {
|
||||||
panic("access control is not implemented yet")
|
return nil, err
|
||||||
//return service.problemStorage.ReadProblemById(ctx, id)
|
}
|
||||||
|
return service.problemStorage.ReadProblemById(ctx, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service *ProblemService) UpdateProblem(ctx context.Context, problem *models.Problem) error {
|
func (service *ProblemService) UpdateProblem(ctx context.Context, problem *models.Problem) error {
|
||||||
//userId := ctx.Value("user_id").(int32)
|
if err := service.CanUpdateProblem(ctx); err != nil {
|
||||||
panic("access control is not implemented yet")
|
return err
|
||||||
//return service.problemStorage.UpdateProblem(ctx, problem)
|
}
|
||||||
|
return service.problemStorage.UpdateProblem(ctx, problem)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service *ProblemService) DeleteProblem(ctx context.Context, id int32) error {
|
func (service *ProblemService) DeleteProblem(ctx context.Context, id int32) error {
|
||||||
//userId := ctx.Value("user_id").(int32)
|
if err := service.CanDeleteProblem(ctx); err != nil {
|
||||||
panic("access control is not implemented yet")
|
return err
|
||||||
//return service.problemStorage.DeleteProblem(ctx, id)
|
}
|
||||||
|
return service.problemStorage.DeleteProblem(ctx, id)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,12 +10,14 @@ import (
|
||||||
func handlePgErr(err error) error {
|
func handlePgErr(err error) error {
|
||||||
var pgErr *pgconn.PgError
|
var pgErr *pgconn.PgError
|
||||||
if !errors.As(err, &pgErr) {
|
if !errors.As(err, &pgErr) {
|
||||||
//storage.logger.DPanic("unexpected error from postgres", zap.String("err", err.Error()))
|
return lib.StorageError(err, lib.ErrUnknown, "unexpected error from postgres")
|
||||||
return lib.ErrUnexpected
|
|
||||||
}
|
}
|
||||||
if pgerrcode.IsIntegrityConstraintViolation(pgErr.Code) {
|
if pgerrcode.IsIntegrityConstraintViolation(pgErr.Code) {
|
||||||
return errors.New("unique key violation") // FIXME
|
// TODO: probably should specify which constraint
|
||||||
|
return lib.StorageError(err, lib.ErrConflict, pgErr.Message)
|
||||||
}
|
}
|
||||||
//storage.logger.DPanic("unexpected internal error from postgres", zap.String("err", err.Error()))
|
if pgerrcode.IsNoData(pgErr.Code) {
|
||||||
return lib.ErrInternal
|
return lib.StorageError(err, lib.ErrNotFound, pgErr.Message)
|
||||||
|
}
|
||||||
|
return lib.StorageError(err, lib.ErrUnimplemented, "unimplemented error")
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"git.sch9.ru/new_gate/ms-tester/internal/models"
|
"git.sch9.ru/new_gate/ms-tester/internal/models"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
@ -21,6 +22,9 @@ func NewProblemStorage(db *sqlx.DB, logger *zap.Logger) *ProblemStorage {
|
||||||
|
|
||||||
func (storage *ProblemStorage) CreateProblem(ctx context.Context, problem *models.Problem, testGroupData []models.TestGroupData) (int32, error) {
|
func (storage *ProblemStorage) CreateProblem(ctx context.Context, problem *models.Problem, testGroupData []models.TestGroupData) (int32, error) {
|
||||||
tx, err := storage.db.Beginx()
|
tx, err := storage.db.Beginx()
|
||||||
|
if err != nil {
|
||||||
|
return 0, handlePgErr(err)
|
||||||
|
}
|
||||||
query := tx.Rebind(`
|
query := tx.Rebind(`
|
||||||
INSERT INTO problems
|
INSERT INTO problems
|
||||||
(name,description,time_limit,memory_limit)
|
(name,description,time_limit,memory_limit)
|
||||||
|
@ -36,7 +40,7 @@ RETURNING id
|
||||||
problem.MemoryLimit,
|
problem.MemoryLimit,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, handlePgErr(err)
|
return 0, handlePgErr(errors.Join(err, tx.Rollback()))
|
||||||
}
|
}
|
||||||
for _, tgd := range testGroupData {
|
for _, tgd := range testGroupData {
|
||||||
query := tx.Rebind(`
|
query := tx.Rebind(`
|
||||||
|
@ -47,7 +51,7 @@ RETURNING id
|
||||||
`)
|
`)
|
||||||
rows, err = tx.QueryxContext(ctx, query, tgd.Ts)
|
rows, err = tx.QueryxContext(ctx, query, tgd.Ts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, handlePgErr(err)
|
return 0, handlePgErr(errors.Join(err, tx.Rollback()))
|
||||||
}
|
}
|
||||||
var i int32 = 0
|
var i int32 = 0
|
||||||
for ; i < tgd.TestAmount; i++ {
|
for ; i < tgd.TestAmount; i++ {
|
||||||
|
@ -59,7 +63,7 @@ RETURNING id
|
||||||
`)
|
`)
|
||||||
rows, err = tx.QueryxContext(ctx, query, tgd.Ts)
|
rows, err = tx.QueryxContext(ctx, query, tgd.Ts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, handlePgErr(err)
|
return 0, handlePgErr(errors.Join(err, tx.Rollback()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,11 +86,11 @@ func (storage *SolutionStorage) RejudgeSolution(ctx context.Context, id int32) e
|
||||||
return handlePgErr(err)
|
return handlePgErr(err)
|
||||||
}
|
}
|
||||||
query := tx.Rebind("UPDATE solutions SET result = ? WHERE id = ?")
|
query := tx.Rebind("UPDATE solutions SET result = ? WHERE id = ?")
|
||||||
tx.QueryxContext(ctx, query, models.NotTested, id)
|
tx.QueryxContext(ctx, query, models.NotTested, id) // FIXME
|
||||||
query = tx.Rebind("UPDATE subtaskruns SET result = ?,score = 0 WHERE solution_id = ?")
|
query = tx.Rebind("UPDATE subtaskruns SET result = ?,score = 0 WHERE solution_id = ?")
|
||||||
tx.QueryxContext(ctx, query, models.NotTested, id)
|
tx.QueryxContext(ctx, query, models.NotTested, id) // FIXME
|
||||||
query = tx.Rebind("UPDATE testruns SET result = ?, score = 0 WHERE testgrouprun_id IN (SELECT id FROM tesgrouprun WHERE solution_id = ?)")
|
query = tx.Rebind("UPDATE testruns SET result = ?, score = 0 WHERE testgrouprun_id IN (SELECT id FROM tesgrouprun WHERE solution_id = ?)")
|
||||||
tx.QueryxContext(ctx, query, models.NotTested, id)
|
tx.QueryxContext(ctx, query, models.NotTested, id) // FIXME
|
||||||
err = tx.Commit()
|
err = tx.Commit()
|
||||||
var solution models.Solution
|
var solution models.Solution
|
||||||
query = storage.db.Rebind("SELECT * from solutions WHERE id=? LIMIT 1")
|
query = storage.db.Rebind("SELECT * from solutions WHERE id=? LIMIT 1")
|
||||||
|
@ -98,7 +98,7 @@ func (storage *SolutionStorage) RejudgeSolution(ctx context.Context, id int32) e
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return handlePgErr(err)
|
return handlePgErr(err)
|
||||||
}
|
}
|
||||||
storage.updateResult(ctx, *solution.ParticipantId, *solution.TaskId)
|
storage.updateResult(ctx, *solution.ParticipantId, *solution.TaskId) // FIXME
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,22 +18,18 @@ var defaultUser = &models.User{
|
||||||
UpdatedAt: nil,
|
UpdatedAt: nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractToken(ctx context.Context) (string, error) {
|
func extractToken(ctx context.Context) string {
|
||||||
md, ok := metadata.FromIncomingContext(ctx)
|
md, ok := metadata.FromIncomingContext(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", errors.New("no metadata") // FIXME
|
return ""
|
||||||
}
|
}
|
||||||
tokens := md.Get("token")
|
tokens := md.Get("token")
|
||||||
|
|
||||||
if len(tokens) == 0 {
|
if len(tokens) == 0 {
|
||||||
return "", errors.New("no token in metadata") // FIXME
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
token := tokens[0]
|
return tokens[0]
|
||||||
if token == "" {
|
|
||||||
return "", errors.New("empty token in metadata") // FIXME
|
|
||||||
}
|
|
||||||
return token, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TesterServer) readSessionAndReadUser(ctx context.Context, token string) (*models.User, error) {
|
func (s *TesterServer) readSessionAndReadUser(ctx context.Context, token string) (*models.User, error) {
|
||||||
|
@ -41,20 +37,22 @@ func (s *TesterServer) readSessionAndReadUser(ctx context.Context, token string)
|
||||||
// FIXME: maybe use single connection instead of multiple requests
|
// FIXME: maybe use single connection instead of multiple requests
|
||||||
userId, err := s.sessionClient.Read(ctx, &sessionv1.ReadSessionRequest{Token: token})
|
userId, err := s.sessionClient.Read(ctx, &sessionv1.ReadSessionRequest{Token: token})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.Unauthenticated, "") // FIXME
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := s.userService.ReadUserById(ctx, userId.GetUserId()) // FIXME: must be cached!
|
user, err := s.userService.ReadUserById(ctx, userId.GetUserId()) // FIXME: must be cached!
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// FIXME: if error is "not found" (when error codes module is written)
|
if errors.Is(err, lib.ErrNotFound) {
|
||||||
// means user has no record, so we should create it
|
|
||||||
user = &models.User{
|
user = &models.User{
|
||||||
UserId: lib.AsInt32P(userId.GetUserId()),
|
UserId: lib.AsInt32P(userId.GetUserId()),
|
||||||
Role: models.RoleParticipant.AsPointer(),
|
Role: models.RoleParticipant.AsPointer(),
|
||||||
}
|
}
|
||||||
err = s.userService.CreateUser(ctx, user)
|
err = s.userService.CreateUser(ctx, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.Unauthenticated, "") // FIXME
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,14 +63,10 @@ func insertUser(ctx context.Context, user *models.User) context.Context {
|
||||||
return context.WithValue(ctx, "user", user)
|
return context.WithValue(ctx, "user", user)
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractUser(ctx context.Context) *models.User {
|
|
||||||
return ctx.Value("user").(*models.User)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *TesterServer) AuthUnaryInterceptor() grpc.UnaryServerInterceptor {
|
func (s *TesterServer) AuthUnaryInterceptor() grpc.UnaryServerInterceptor {
|
||||||
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||||
token, err := extractToken(ctx)
|
token := extractToken(ctx)
|
||||||
if err != nil {
|
if token == "" {
|
||||||
return handler(insertUser(ctx, defaultUser), req)
|
return handler(insertUser(ctx, defaultUser), req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,8 +92,8 @@ func (s *TesterServer) AuthStreamInterceptor() grpc.StreamServerInterceptor {
|
||||||
return func(server interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
return func(server interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||||
ctx := ss.Context()
|
ctx := ss.Context()
|
||||||
|
|
||||||
token, err := extractToken(ctx)
|
token := extractToken(ctx)
|
||||||
if err != nil {
|
if token == "" {
|
||||||
return handler(server, &ssWrapper{ServerStream: ss, ctx: insertUser(ctx, defaultUser)})
|
return handler(server, &ssWrapper{ServerStream: ss, ctx: insertUser(ctx, defaultUser)})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,3 +105,51 @@ func (s *TesterServer) AuthStreamInterceptor() grpc.StreamServerInterceptor {
|
||||||
return handler(server, &ssWrapper{ServerStream: ss, ctx: insertUser(ctx, user)})
|
return handler(server, &ssWrapper{ServerStream: ss, ctx: insertUser(ctx, user)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ToGrpcError(err error) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// should I use map instead?
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, lib.ErrValidationFailed):
|
||||||
|
return status.Error(codes.InvalidArgument, err.Error())
|
||||||
|
case errors.Is(err, lib.ErrInternal):
|
||||||
|
return status.Error(codes.Internal, err.Error())
|
||||||
|
case errors.Is(err, lib.ErrExternal):
|
||||||
|
return status.Error(codes.Unavailable, err.Error())
|
||||||
|
case errors.Is(err, lib.ErrNoPermission):
|
||||||
|
return status.Error(codes.PermissionDenied, err.Error())
|
||||||
|
case errors.Is(err, lib.ErrUnknown):
|
||||||
|
return status.Error(codes.Unknown, err.Error())
|
||||||
|
case errors.Is(err, lib.ErrDeadlineExceeded):
|
||||||
|
return status.Error(codes.DeadlineExceeded, err.Error())
|
||||||
|
case errors.Is(err, lib.ErrNotFound):
|
||||||
|
return status.Error(codes.NotFound, err.Error())
|
||||||
|
case errors.Is(err, lib.ErrAlreadyExists):
|
||||||
|
return status.Error(codes.AlreadyExists, err.Error())
|
||||||
|
case errors.Is(err, lib.ErrConflict):
|
||||||
|
return status.Error(codes.Unimplemented, err.Error())
|
||||||
|
case errors.Is(err, lib.ErrUnimplemented):
|
||||||
|
return status.Error(codes.Unimplemented, err.Error())
|
||||||
|
case errors.Is(err, lib.ErrUnauthenticated):
|
||||||
|
return status.Error(codes.Unauthenticated, err.Error())
|
||||||
|
default:
|
||||||
|
return status.Error(codes.Unknown, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TesterServer) ErrUnwrappingUnaryInterceptor() grpc.UnaryServerInterceptor {
|
||||||
|
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||||
|
resp, err := handler(ctx, req)
|
||||||
|
return resp, ToGrpcError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TesterServer) ErrUnwrappingStreamInterceptor() grpc.StreamServerInterceptor {
|
||||||
|
return func(server interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||||
|
err := handler(server, ss)
|
||||||
|
return ToGrpcError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -15,17 +15,17 @@ import (
|
||||||
func (s *TesterServer) CreateProblem(server problemv1.ProblemService_CreateProblemServer) error {
|
func (s *TesterServer) CreateProblem(server problemv1.ProblemService_CreateProblemServer) error {
|
||||||
ctx := server.Context()
|
ctx := server.Context()
|
||||||
|
|
||||||
if !s.permissionService.Allowed(ctx, extractUser(ctx), "create") {
|
if err := s.problemService.CanCreateProblem(ctx); err != nil {
|
||||||
return status.Errorf(codes.PermissionDenied, "") // FIXME
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := server.Recv() // receive problem
|
req, err := server.Recv() // receive problem
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err // FIXME
|
return lib.TransportError(err, lib.ErrBadInput, "can't receive problem")
|
||||||
}
|
}
|
||||||
problem := req.GetProblem()
|
problem := req.GetProblem()
|
||||||
if problem == nil {
|
if problem == nil {
|
||||||
return status.Errorf(codes.Unknown, "") // FIXME
|
return lib.TransportError(nil, lib.ErrBadInput, "empty problem")
|
||||||
}
|
}
|
||||||
|
|
||||||
p := &models.Problem{
|
p := &models.Problem{
|
||||||
|
@ -42,22 +42,23 @@ func (s *TesterServer) CreateProblem(server problemv1.ProblemService_CreateProbl
|
||||||
return err // FIXME
|
return err // FIXME
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err := s.problemService.CreateProblem(ctx, p, nil) // FIXME
|
id, err := s.problemService.CreateProblem(ctx, p) // FIXME
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return status.Errorf(codes.Unknown, "") // FIXME
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = server.SendAndClose(&problemv1.CreateProblemResponse{
|
err = server.SendAndClose(&problemv1.CreateProblemResponse{
|
||||||
Id: id,
|
Id: id,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err // FIXME
|
return lib.TransportError(err, lib.ErrBadInput, "can't send response")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeChunks(ctx context.Context, chunks <-chan []byte) error {
|
func writeChunks(ctx context.Context, chunks <-chan []byte) error {
|
||||||
|
// use s3
|
||||||
// FIXME: use ctx?
|
// FIXME: use ctx?
|
||||||
f, err := os.Create("out.txt") // FIXME: uuidv4 as initial temp name?
|
f, err := os.Create("out.txt") // FIXME: uuidv4 as initial temp name?
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -113,13 +114,9 @@ func readChunks(ctx context.Context, server problemv1.ProblemService_CreateProbl
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TesterServer) ReadProblem(ctx context.Context, req *problemv1.ReadProblemRequest) (*problemv1.ReadProblemResponse, error) {
|
func (s *TesterServer) ReadProblem(ctx context.Context, req *problemv1.ReadProblemRequest) (*problemv1.ReadProblemResponse, error) {
|
||||||
if !s.permissionService.Allowed(ctx, extractUser(ctx), "read") {
|
|
||||||
return nil, status.Errorf(codes.PermissionDenied, "") // FIXME
|
|
||||||
}
|
|
||||||
|
|
||||||
problem, err := s.problemService.ReadProblemById(ctx, req.GetId())
|
problem, err := s.problemService.ReadProblemById(ctx, req.GetId())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.Unknown, err.Error()) // FIXME
|
return nil, err
|
||||||
}
|
}
|
||||||
return &problemv1.ReadProblemResponse{
|
return &problemv1.ReadProblemResponse{
|
||||||
Problem: &problemv1.ReadProblemResponse_Problem{
|
Problem: &problemv1.ReadProblemResponse_Problem{
|
||||||
|
@ -157,12 +154,9 @@ func (s *TesterServer) ReadProblem(ctx context.Context, req *problemv1.ReadProbl
|
||||||
//}
|
//}
|
||||||
|
|
||||||
func (s *TesterServer) DeleteProblem(ctx context.Context, req *problemv1.DeleteProblemRequest) (*emptypb.Empty, error) {
|
func (s *TesterServer) DeleteProblem(ctx context.Context, req *problemv1.DeleteProblemRequest) (*emptypb.Empty, error) {
|
||||||
if !s.permissionService.Allowed(ctx, extractUser(ctx), "delete") {
|
|
||||||
return nil, status.Errorf(codes.PermissionDenied, "") // FIXME
|
|
||||||
}
|
|
||||||
err := s.problemService.DeleteProblem(ctx, req.GetId())
|
err := s.problemService.DeleteProblem(ctx, req.GetId())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, status.Errorf(codes.Unknown, err.Error()) // FIXME
|
return nil, err
|
||||||
}
|
}
|
||||||
return &emptypb.Empty{}, nil
|
return &emptypb.Empty{}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProblemService interface {
|
type ProblemService interface {
|
||||||
CreateProblem(ctx context.Context, problem *models.Problem, ch <-chan []byte) (int32, error)
|
CanCreateProblem(ctx context.Context) error
|
||||||
|
CreateProblem(ctx context.Context, problem *models.Problem) (int32, error)
|
||||||
ReadProblemById(ctx context.Context, id int32) (*models.Problem, error)
|
ReadProblemById(ctx context.Context, id int32) (*models.Problem, error)
|
||||||
UpdateProblem(ctx context.Context, problem *models.Problem) error
|
UpdateProblem(ctx context.Context, problem *models.Problem) error
|
||||||
DeleteProblem(ctx context.Context, id int32) error
|
DeleteProblem(ctx context.Context, id int32) error
|
||||||
|
@ -32,10 +33,6 @@ type UserService interface {
|
||||||
ReadUserById(ctx context.Context, userId int32) (*models.User, error)
|
ReadUserById(ctx context.Context, userId int32) (*models.User, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type PermissionService interface {
|
|
||||||
Allowed(ctx context.Context, user *models.User, action string) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type TesterServer struct {
|
type TesterServer struct {
|
||||||
problemv1.UnimplementedProblemServiceServer
|
problemv1.UnimplementedProblemServiceServer
|
||||||
problemService ProblemService
|
problemService ProblemService
|
||||||
|
@ -43,8 +40,6 @@ type TesterServer struct {
|
||||||
sessionClient SessionClient
|
sessionClient SessionClient
|
||||||
userService UserService
|
userService UserService
|
||||||
|
|
||||||
permissionService PermissionService
|
|
||||||
|
|
||||||
grpcServer *grpc.Server
|
grpcServer *grpc.Server
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
}
|
}
|
||||||
|
@ -52,19 +47,19 @@ type TesterServer struct {
|
||||||
func NewTesterServer(
|
func NewTesterServer(
|
||||||
problemService ProblemService,
|
problemService ProblemService,
|
||||||
sessionClient SessionClient,
|
sessionClient SessionClient,
|
||||||
permissionService PermissionService,
|
|
||||||
userService UserService,
|
userService UserService,
|
||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
) *TesterServer {
|
) *TesterServer {
|
||||||
server := &TesterServer{
|
server := &TesterServer{
|
||||||
problemService: problemService,
|
problemService: problemService,
|
||||||
sessionClient: sessionClient,
|
sessionClient: sessionClient,
|
||||||
permissionService: permissionService,
|
|
||||||
userService: userService,
|
userService: userService,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
}
|
}
|
||||||
|
|
||||||
grpcServer := grpc.NewServer(
|
grpcServer := grpc.NewServer(
|
||||||
|
grpc.UnaryInterceptor(server.ErrUnwrappingUnaryInterceptor()),
|
||||||
|
grpc.StreamInterceptor(server.ErrUnwrappingStreamInterceptor()),
|
||||||
grpc.UnaryInterceptor(server.AuthUnaryInterceptor()),
|
grpc.UnaryInterceptor(server.AuthUnaryInterceptor()),
|
||||||
grpc.StreamInterceptor(server.AuthStreamInterceptor()),
|
grpc.StreamInterceptor(server.AuthStreamInterceptor()),
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue