diff --git a/go/cmd/open-ve/gen/gen.go b/go/cmd/open-ve/gen/gen.go index 4628a49..caf69e3 100644 --- a/go/cmd/open-ve/gen/gen.go +++ b/go/cmd/open-ve/gen/gen.go @@ -44,7 +44,7 @@ func gen(cmd *cobra.Command, args []string) { filePath := args[1] outputDir := args[2] - logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})) + logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})) logger.Info("🏭 generating open-ve schema", slog.String("fileType", fileType), slog.String("filePath", filePath), slog.String("outputDir", outputDir)) var serialized []byte @@ -71,5 +71,5 @@ func gen(cmd *cobra.Command, args []string) { panic(fmt.Errorf("failed to write file: %w", err)) } - logger.Info("🎉 generated open-ve schema", slog.String("outputPath", outputPath)) + logger.Info("✅ generated open-ve schema", slog.String("outputPath", outputPath)) } diff --git a/go/cmd/open-ve/run/run.go b/go/cmd/open-ve/run/run.go index 6c7078e..7b6b478 100644 --- a/go/cmd/open-ve/run/run.go +++ b/go/cmd/open-ve/run/run.go @@ -34,15 +34,15 @@ func NewRunCommand() *cobra.Command { if mode == "slave" { id := viper.GetString("slave.id") if id == "" { - return failure.New(appError.ErrConfigFileSyntaxError, failure.Message("ID of the slave server is required")) + return failure.New(appError.ErrConfigError, failure.Message("ID of the slave server is required")) } slaveHTTPAddr := viper.GetString("slave.slaveHTTPAddr") if slaveHTTPAddr == "" { - return failure.New(appError.ErrConfigFileSyntaxError, failure.Message("HTTP address of the slave server is required")) + return failure.New(appError.ErrConfigError, failure.Message("HTTP address of the slave server is required")) } masterHTTPAddr := viper.GetString("slave.masterHTTPAddr") if masterHTTPAddr == "" { - return failure.New(appError.ErrConfigFileSyntaxError, failure.Message("HTTP address of the master server is required")) + return failure.New(appError.ErrConfigError, failure.Message("HTTP address of the master server is required")) } } return nil diff --git a/go/cmd/open-ve/test/test.go b/go/cmd/open-ve/test/test.go index 92d5898..1195b17 100644 --- a/go/cmd/open-ve/test/test.go +++ b/go/cmd/open-ve/test/test.go @@ -5,7 +5,8 @@ import ( "log/slog" "os" - "github.com/shibukazu/open-ve/go/pkg/dsl" + "github.com/shibukazu/open-ve/go/pkg/dsl/tester" + "github.com/shibukazu/open-ve/go/pkg/dsl/util" "github.com/spf13/cobra" ) @@ -35,22 +36,34 @@ func validateArgs(cmd *cobra.Command, args []string) error { func test(cmd *cobra.Command, args []string) { filePath := args[0] - logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})) - logger.Info("🧪 test open-ve schema", slog.String("filePath", filePath)) + logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})) + logger.Info("🧪 testing open-ve schema", slog.String("filePath", filePath)) - dsl, err := dsl.ParseYAML(filePath) + dsl, err := util.ParseDSLYAML(filePath) if err != nil { panic(fmt.Errorf("failed to parse schema: %w", err)) } - result, err := dsl.Test() + result, err := tester.TestDSL(dsl) if err != nil { panic(fmt.Errorf("failed to test schema: %w", err)) } + numPassed := 0 + numFailed := 0 + numNotFound := 0 for _, validationResult := range result.ValidationResults { - if len(validationResult.FailedTestCases) > 0 { - logger.Info("❌ test failed", slog.String("id", validationResult.ID), slog.String("failedTestCases", fmt.Sprintf("%v", validationResult.FailedTestCases))) + if validationResult.TestCaseNotFound { + numNotFound++ + logger.Info(fmt.Sprintf("✅ NoutFound: %s", validationResult.ID)) + } else if len(validationResult.FailedTestCases) > 0 { + numFailed++ + logger.Info(fmt.Sprintf("❌ FAILED : %s", validationResult.ID)) + for _, failedTestCase := range validationResult.FailedTestCases { + logger.Info(fmt.Sprintf(" - %s", failedTestCase)) + } } else { - logger.Info("✅ test passed", slog.String("id", validationResult.ID)) + numPassed++ + logger.Info(fmt.Sprintf("✅ PASS : %s", validationResult.ID)) } } + logger.Info(fmt.Sprintf("📊 Results: %d passed, %d failed, %d not found", numPassed, numFailed, numNotFound)) } diff --git a/go/pkg/appError/appError.go b/go/pkg/appError/appError.go new file mode 100644 index 0000000..7061b55 --- /dev/null +++ b/go/pkg/appError/appError.go @@ -0,0 +1,54 @@ +package appError + +import ( + "fmt" + + "github.com/morikuni/failure/v2" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +const ( + ErrConfigError = "ConfigError" + ErrDSLSyntaxError = "DSLSyntaxError" + ErrDSLGenerationFailed = "DSLGenerationFailed" + ErrStoreOperationFailed = "StoreOperationFailed" + ErrRequestParameterInvalid = "RequestParameterInvalid" + ErrServerError = "ServerError" + ErrRequestForwardFailed = "RequestForwardError" + ErrAuthenticationFailed = "AuthenticationFailed" +) + +func ToGRPCError(err error) error { + var code codes.Code + switch failure.CodeOf(err) { + case ErrConfigError: + code = codes.InvalidArgument + case ErrDSLSyntaxError: + code = codes.InvalidArgument + case ErrDSLGenerationFailed: + code = codes.Internal + case ErrStoreOperationFailed: + code = codes.Internal + case ErrRequestParameterInvalid: + code = codes.InvalidArgument + case ErrAuthenticationFailed: + code = codes.Unauthenticated + case ErrServerError: + code = codes.Internal + case ErrRequestForwardFailed: + code = codes.Internal + default: + code = codes.Unknown + } + return status.Error(code, getGRPCErrorMessage(err)) +} + +func getGRPCErrorMessage(err error) string { + code := failure.CodeOf(err) + message := failure.MessageOf(err) + cause := failure.CauseOf(err) + ret := fmt.Sprintf("code: %s, message: %s, cause: %s", code, message, cause) + + return ret +} diff --git a/go/pkg/appError/serverError.go b/go/pkg/appError/serverError.go deleted file mode 100644 index 5cd98b8..0000000 --- a/go/pkg/appError/serverError.go +++ /dev/null @@ -1,10 +0,0 @@ -package appError - -const ( - ErrServerStartFailed = "ServerStartFailed" - ErrServerShutdownFailed = "ServerShutdownFailed" - ErrServerInternalError = "ServerInternalError" - ErrConfigFileNotFound = "ConfigFileNotFound" - ErrConfigFileSyntaxError = "ConfigFileSyntaxError" - ErrSlaveRegistrationFailed = "SlaveRegistrationFailed" -) diff --git a/go/pkg/appError/serviceError.go b/go/pkg/appError/serviceError.go deleted file mode 100644 index f374a7a..0000000 --- a/go/pkg/appError/serviceError.go +++ /dev/null @@ -1,67 +0,0 @@ -package appError - -import ( - "fmt" - - "github.com/morikuni/failure/v2" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" -) - -const ( - ErrYAMLSyntaxError = "YAMLSyntaxError" - ErrDSLSyntaxError = "DSLSyntaxError" - ErrRedisOperationFailed = "RedisOperationFailed" - ErrCELSyantaxError = "CELSyantaxError" - - ErrRequestParameterInvalid = "RequestParameterInvalid" - - ErrValidateServiceIDNotFound = "ValidateServiceIDNotFound" - ErrValidateServiceForwardFailed = "ValidateServiceForwardFailed" - - ErrDSLServiceDSLSyntaxError = "DSLServiceDSLSyntaxError" - - ErrAuthMissingToken = "AuthMissingToken" - ErrAuthUnauthorized = "AuthUnauthorized" - - ErrInternalError = "Internal" -) - -func ToGRPCError(err error) error { - var code codes.Code - switch failure.CodeOf(err) { - case ErrYAMLSyntaxError: - code = codes.InvalidArgument - case ErrDSLSyntaxError: - code = codes.InvalidArgument - case ErrRedisOperationFailed: - code = codes.Internal - case ErrCELSyantaxError: - code = codes.InvalidArgument - case ErrRequestParameterInvalid: - code = codes.InvalidArgument - case ErrValidateServiceIDNotFound: - code = codes.NotFound - case ErrDSLServiceDSLSyntaxError: - code = codes.InvalidArgument - case ErrAuthMissingToken: - code = codes.Unauthenticated - case ErrAuthUnauthorized: - code = codes.Unauthenticated - case ErrInternalError: - code = codes.Internal - default: - code = codes.Unknown - } - return status.Error(code, getMessage(err)) -} - -func getMessage(err error) string { - code := failure.CodeOf(err) - cause := failure.CauseOf(err) - additionalInfo := failure.MessageOf(err) - detail := fmt.Sprintf("%+v\n", err) - message := fmt.Sprintf("code: %s, cause: %s, additionalInfo: %s, detail: %s", code, cause, additionalInfo, detail) - - return message -} diff --git a/go/pkg/authn/presharedkey.go b/go/pkg/authn/presharedkey.go index 18bec57..675afea 100644 --- a/go/pkg/authn/presharedkey.go +++ b/go/pkg/authn/presharedkey.go @@ -19,11 +19,11 @@ func NewPresharedKeyAuthenticator(key string) *PresharedKeyAuthenticator { func (a *PresharedKeyAuthenticator) Authenticate(ctx context.Context) (string, error) { authHeader, err := grpcauth.AuthFromMD(ctx, "Bearer") if err != nil { - return "", failure.Translate(err, appError.ErrAuthMissingToken) + return "", failure.Translate(err, appError.ErrAuthenticationFailed, failure.Messagef("failed to get auth header")) } if authHeader != a.key { - return "", failure.New(appError.ErrAuthUnauthorized) + return "", failure.New(appError.ErrAuthenticationFailed, failure.Messagef("invalid key")) } return "", nil diff --git a/go/pkg/dsl/dsl.go b/go/pkg/dsl/dsl.go index fe145b4..aae5c15 100644 --- a/go/pkg/dsl/dsl.go +++ b/go/pkg/dsl/dsl.go @@ -1,52 +1,10 @@ package dsl -import ( - "io" - "os" - - "github.com/google/cel-go/cel" - "github.com/morikuni/failure/v2" - "github.com/shibukazu/open-ve/go/pkg/appError" - "gopkg.in/yaml.v3" -) - type Variable struct { Name string `yaml:"name" json:"name"` Type string `yaml:"type" json:"type"` } -func (v *Variable) ParseVariable() (cel.EnvOption, error) { - switch v.Type { - case "int": - return cel.Variable(v.Name, cel.IntType), nil - case "uint": - return cel.Variable(v.Name, cel.UintType), nil - case "double": - return cel.Variable(v.Name, cel.DoubleType), nil - case "bool": - return cel.Variable(v.Name, cel.BoolType), nil - case "bytes": - return cel.Variable(v.Name, cel.BytesType), nil - case "string": - return cel.Variable(v.Name, cel.StringType), nil - // TODO: listとmap向けの再帰パースの実装 - default: - return nil, failure.New(appError.ErrDSLSyntaxError, failure.Messagef("Unsupported variable type: %s\nPlease specify one of the following types: int, uint, double, bool, string, bytes", v.Type)) - } -} - -func ToCELVariables(vars []Variable) ([]cel.EnvOption, error) { - celVars := make([]cel.EnvOption, 0, len(vars)) - for _, v := range vars { - v, err := v.ParseVariable() - if err != nil { - return nil, err - } - celVars = append(celVars, v) - } - return celVars, nil -} - type TestVeriable struct { Name string `yaml:"name" json:"name"` Value interface{} `yaml:"value" json:"value"` @@ -68,24 +26,3 @@ type Validation struct { type DSL struct { Validations []Validation `yaml:"validations" json:"validations"` } - -func ParseYAML(yamlFilePath string) (*DSL, error) { - yamlFile, err := os.Open(yamlFilePath) - if err != nil { - return nil, err - } - defer yamlFile.Close() - - yamlBytes, err := io.ReadAll(yamlFile) - if err != nil { - return nil, err - } - - dsl := &DSL{} - err = yaml.Unmarshal(yamlBytes, &dsl) - if err != nil { - return nil, err - } - - return dsl, nil -} diff --git a/go/pkg/dsl/generator/openapi.go b/go/pkg/dsl/generator/openapi.go index afb86f8..5a36ee5 100644 --- a/go/pkg/dsl/generator/openapi.go +++ b/go/pkg/dsl/generator/openapi.go @@ -7,6 +7,7 @@ import ( "github.com/go-openapi/loads" "github.com/go-openapi/spec" "github.com/morikuni/failure/v2" + "github.com/shibukazu/open-ve/go/pkg/appError" "github.com/shibukazu/open-ve/go/pkg/dsl" ) @@ -16,12 +17,12 @@ func GenerateFromOpenAPI2(logger *slog.Logger, filePath string) (*dsl.DSL, error swaggerDoc, err := loads.Spec(filePath) if err != nil { - return nil, failure.Translate(err, failure.Messagef("failed to load OpenAPI schema file: %s", filePath)) + return nil, failure.Translate(err, appError.ErrDSLGenerationFailed, failure.Messagef("failed to load OpenAPI schema file: %s", filePath)) } paths := swaggerDoc.Spec().Paths for path, pathItem := range paths.Paths { - logger.Info("🔍 parsing...", slog.String("path", path)) + logger.Info("📈 parsing...", slog.String("path", path)) if pathItem.Post != nil { for _, param := range pathItem.Post.Parameters { if param.Schema != nil { @@ -53,7 +54,7 @@ func resolveSchemaReference(doc *spec.Swagger, schema *spec.Schema) (*spec.Schem if schema.Ref.String() != "" { ref, err := spec.ResolveRef(doc, &schema.Ref) if err != nil { - return nil, "", failure.Translate(err, failure.Messagef("failed to resolve schema reference")) + return nil, "", failure.Translate(err, appError.ErrDSLGenerationFailed, failure.Messagef("failed to resolve schema reference")) } refParts := strings.Split(schema.Ref.String(), "/") @@ -68,7 +69,7 @@ func resolveSchemaReference(doc *spec.Swagger, schema *spec.Schema) (*spec.Schem func parseParamSchema(doc *spec.Swagger, schema *spec.Schema, parentObjectName string, propName string, variables *[]dsl.Variable) error { if schema == nil { - return failure.New(failure.Messagef("schema is nil")) + return failure.New(appError.ErrDSLGenerationFailed, failure.Messagef("schema is nil")) } if schema.Properties != nil { @@ -104,12 +105,12 @@ func parseParamSchema(doc *spec.Swagger, schema *spec.Schema, parentObjectName s } else if schema.Items != nil { // TODO: Support Array - return failure.New(failure.Messagef("Array is not supported")) + return failure.New(appError.ErrDSLGenerationFailed, failure.Messagef("Array is not supported")) } else { // Primitive if schema.Type != nil { if len(schema.Type) != 1 { - return failure.New(failure.Messagef("schema.Type length is not 1")) + return failure.New(appError.ErrDSLGenerationFailed, failure.Messagef("schema.Type length is not 1")) } typeName := schema.Type[0] celType := openAPITypeToCELType(typeName, schema.Format) diff --git a/go/pkg/dsl/reader/reader.go b/go/pkg/dsl/reader/reader.go index ea37dd4..86dba5b 100644 --- a/go/pkg/dsl/reader/reader.go +++ b/go/pkg/dsl/reader/reader.go @@ -8,6 +8,7 @@ import ( "github.com/morikuni/failure/v2" "github.com/shibukazu/open-ve/go/pkg/appError" dslPkg "github.com/shibukazu/open-ve/go/pkg/dsl" + "github.com/shibukazu/open-ve/go/pkg/dsl/util" "github.com/shibukazu/open-ve/go/pkg/store" "google.golang.org/protobuf/proto" ) @@ -73,30 +74,30 @@ func (r *DSLReader) parseAndSaveDSL(dsl *dslPkg.DSL) error { return err } - celVariables, err := dslPkg.ToCELVariables(v.Variables) + celVariables, err := util.DSLVariablesToCELVariables(v.Variables) if err != nil { return err } env, err := cel.NewEnv(celVariables...) if err != nil { - return failure.Translate(err, appError.ErrCELSyantaxError) + return failure.Translate(err, appError.ErrDSLSyntaxError, failure.Messagef("failed to create CEL environment")) } allEncodedAST := make([][]byte, 0, len(v.Cels)) for _, inputCel := range v.Cels { ast, issues := env.Compile(inputCel) if issues != nil && issues.Err() != nil { - return failure.Translate(issues.Err(), appError.ErrCELSyantaxError) + return failure.Translate(issues.Err(), appError.ErrDSLSyntaxError, failure.Messagef("failed to compile CEL")) } // Convert AST to Proto expr, err := cel.AstToCheckedExpr(ast) if err != nil { - return failure.Translate(err, appError.ErrCELSyantaxError) + return failure.Translate(err, appError.ErrDSLSyntaxError, failure.Messagef("failed to convert AST to Proto")) } encodedAST, err := proto.Marshal(expr) if err != nil { - return failure.Translate(err, appError.ErrCELSyantaxError) + return failure.Translate(err, appError.ErrDSLSyntaxError, failure.Messagef("failed to encode AST")) } allEncodedAST = append(allEncodedAST, encodedAST) } diff --git a/go/pkg/dsl/tester.go b/go/pkg/dsl/tester/tester.go similarity index 60% rename from go/pkg/dsl/tester.go rename to go/pkg/dsl/tester/tester.go index 9a93024..43467fc 100644 --- a/go/pkg/dsl/tester.go +++ b/go/pkg/dsl/tester/tester.go @@ -1,9 +1,11 @@ -package dsl +package tester import ( "github.com/google/cel-go/cel" "github.com/morikuni/failure/v2" "github.com/shibukazu/open-ve/go/pkg/appError" + "github.com/shibukazu/open-ve/go/pkg/dsl" + "github.com/shibukazu/open-ve/go/pkg/dsl/util" ) type Result struct { @@ -11,22 +13,31 @@ type Result struct { } type ValidationResult struct { - ID string - FailedTestCases []string + ID string + FailedTestCases []string + TestCaseNotFound bool } -func (d *DSL) Test() (*Result, error) { +func TestDSL(d *dsl.DSL) (*Result, error) { result := &Result{} result.ValidationResults = make([]ValidationResult, 0) for _, validation := range d.Validations { + if len(validation.TestCases) == 0 { + result.ValidationResults = append(result.ValidationResults, ValidationResult{ + ID: validation.ID, + FailedTestCases: []string{}, + TestCaseNotFound: true, + }) + continue + } variables := validation.Variables - celVariables, err := ToCELVariables(variables) + celVariables, err := util.DSLVariablesToCELVariables(variables) if err != nil { return nil, err } env, err := cel.NewEnv(celVariables...) if err != nil { - return nil, failure.Translate(err, appError.ErrCELSyantaxError) + return nil, failure.Translate(err, appError.ErrDSLSyntaxError, failure.Messagef("failed to create CEL environment: %v", err)) } cels := validation.Cels @@ -36,11 +47,11 @@ func (d *DSL) Test() (*Result, error) { for _, cel := range cels { ast, issues := env.Compile(cel) if issues != nil && issues.Err() != nil { - return nil, failure.Translate(err, failure.Messagef("Failed to compile CEL: %v", issues.Err())) + return nil, failure.Translate(err, failure.Messagef("failed to compile CEL: %v", issues.Err())) } prg, err := env.Program(ast) if err != nil { - return nil, failure.Translate(err, failure.Messagef("Failed to create program: %v", err)) + return nil, failure.Translate(err, failure.Messagef("failed to create program: %v", err)) } inputVariables := make(map[string]interface{}) for _, v := range testCase.Variables { @@ -48,11 +59,11 @@ func (d *DSL) Test() (*Result, error) { } res, _, err := prg.Eval(inputVariables) if err != nil { - return nil, failure.Translate(err, failure.Messagef("Failed to evaluate program: %v", err)) + return nil, failure.Translate(err, failure.Messagef("failed to evaluate program: %v", err)) } pass, ok := res.Value().(bool) if !ok { - return nil, failure.New(appError.ErrDSLSyntaxError, failure.Messagef("Unsupported result type: %T\nPlease specify one of the following types: bool", res.Value())) + return nil, failure.New(appError.ErrDSLSyntaxError, failure.Messagef("unsupported result type: %T\nplease specify one of the following types: bool", res.Value())) } passAll = passAll && pass } @@ -61,8 +72,9 @@ func (d *DSL) Test() (*Result, error) { } } validationResult := ValidationResult{ - ID: validation.ID, - FailedTestCases: failedTestCases, + ID: validation.ID, + FailedTestCases: failedTestCases, + TestCaseNotFound: false, } result.ValidationResults = append(result.ValidationResults, validationResult) } diff --git a/go/pkg/dsl/util/util.go b/go/pkg/dsl/util/util.go new file mode 100644 index 0000000..1e7aa95 --- /dev/null +++ b/go/pkg/dsl/util/util.go @@ -0,0 +1,65 @@ +package util + +import ( + "io" + "os" + + "github.com/google/cel-go/cel" + "github.com/morikuni/failure/v2" + "github.com/shibukazu/open-ve/go/pkg/appError" + "github.com/shibukazu/open-ve/go/pkg/dsl" + "gopkg.in/yaml.v3" +) + +func DSLVariableToCELVariable(v *dsl.Variable) (cel.EnvOption, error) { + switch v.Type { + case "int": + return cel.Variable(v.Name, cel.IntType), nil + case "uint": + return cel.Variable(v.Name, cel.UintType), nil + case "double": + return cel.Variable(v.Name, cel.DoubleType), nil + case "bool": + return cel.Variable(v.Name, cel.BoolType), nil + case "bytes": + return cel.Variable(v.Name, cel.BytesType), nil + case "string": + return cel.Variable(v.Name, cel.StringType), nil + // TODO: listとmap向けの再帰パースの実装 + default: + return nil, failure.New(appError.ErrDSLSyntaxError, failure.Messagef("unsupported variable type: %s\nplease specify one of the following types: int, uint, double, bool, string, bytes", v.Type)) + } +} + +func DSLVariablesToCELVariables(vars []dsl.Variable) ([]cel.EnvOption, error) { + celVars := make([]cel.EnvOption, 0, len(vars)) + for _, v := range vars { + v, err := DSLVariableToCELVariable(&v) + if err != nil { + return nil, err + } + celVars = append(celVars, v) + } + return celVars, nil +} + +func ParseDSLYAML(yamlFilePath string) (*dsl.DSL, error) { + yamlFile, err := os.Open(yamlFilePath) + if err != nil { + return nil, err + } + defer yamlFile.Close() + + yamlBytes, err := io.ReadAll(yamlFile) + if err != nil { + return nil, err + } + + dsl := &dsl.DSL{} + err = yaml.Unmarshal(yamlBytes, &dsl) + if err != nil { + return nil, err + } + + return dsl, nil +} diff --git a/go/pkg/logger/logger.go b/go/pkg/logger/logger.go index f442d5b..f7e18cd 100644 --- a/go/pkg/logger/logger.go +++ b/go/pkg/logger/logger.go @@ -14,7 +14,7 @@ func NewLogger( ) *slog.Logger { var level slog.LevelVar if err := level.UnmarshalText([]byte(logConfig.Level)); err != nil { - panic(failure.Translate(err, appError.ErrConfigFileSyntaxError, failure.Messagef("invalid log level: %s", logConfig.Level))) + panic(failure.Translate(err, appError.ErrConfigError, failure.Messagef("invalid log level: %s", logConfig.Level))) } logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: &level})) diff --git a/go/pkg/logger/util.go b/go/pkg/logger/util.go new file mode 100644 index 0000000..a517d0f --- /dev/null +++ b/go/pkg/logger/util.go @@ -0,0 +1,12 @@ +package logger + +import ( + "fmt" + "log/slog" + + "github.com/morikuni/failure/v2" +) + +func LogError(logger *slog.Logger, err error) { + logger.Error(string(failure.MessageOf(err)), slog.Any("code", failure.CodeOf(err)), slog.String("details", fmt.Sprintf("%+v", err))) +} diff --git a/go/pkg/server/gateway.go b/go/pkg/server/gateway.go index e8d36f7..50639e7 100644 --- a/go/pkg/server/gateway.go +++ b/go/pkg/server/gateway.go @@ -18,6 +18,7 @@ import ( "github.com/shibukazu/open-ve/go/pkg/appError" "github.com/shibukazu/open-ve/go/pkg/config" "github.com/shibukazu/open-ve/go/pkg/dsl/reader" + "github.com/shibukazu/open-ve/go/pkg/logger" "github.com/shibukazu/open-ve/go/pkg/slave" pbDSL "github.com/shibukazu/open-ve/go/proto/dsl/v1" pbSlave "github.com/shibukazu/open-ve/go/proto/slave/v1" @@ -61,11 +62,11 @@ func (g *Gateway) Run(ctx context.Context, wg *sync.WaitGroup) { if g.gRPCConfig.TLS.Enabled { if g.gRPCConfig.TLS.CertPath == "" || g.gRPCConfig.TLS.KeyPath == "" { - panic(failure.New(appError.ErrServerStartFailed, failure.Message("certPath and keyPath must be set"))) + panic(failure.New(appError.ErrServerError, failure.Message("certPath and keyPath must be set"))) } creds, err := credentials.NewClientTLSFromFile(g.gRPCConfig.TLS.CertPath, "") if err != nil { - panic(failure.Translate(err, appError.ErrServerStartFailed)) + panic(failure.Translate(err, appError.ErrServerError, failure.Messagef("failed to load TLS cert"))) } dialOpts = append(dialOpts, grpc.WithTransportCredentials(creds)) } else { @@ -74,7 +75,7 @@ func (g *Gateway) Run(ctx context.Context, wg *sync.WaitGroup) { conn, err := grpc.NewClient(":"+g.gRPCConfig.Port, dialOpts...) if err != nil { - panic(failure.Translate(err, appError.ErrServerStartFailed, failure.Messagef("failed to dial gRPC server"))) + panic(failure.Translate(err, appError.ErrServerError, failure.Messagef("failed to dial gRPC server"))) } defer conn.Close() @@ -85,16 +86,16 @@ func (g *Gateway) Run(ctx context.Context, wg *sync.WaitGroup) { grpcGateway := runtime.NewServeMux(muxOpts...) if err := pbValidate.RegisterValidateServiceHandlerFromEndpoint(ctx, grpcGateway, ":"+g.gRPCConfig.Port, dialOpts); err != nil { - panic(failure.Translate(err, appError.ErrServerStartFailed, failure.Messagef("failed to register validate service on gateway"))) + panic(failure.Translate(err, appError.ErrServerError, failure.Messagef("failed to register validate service on gateway"))) } if err := pbDSL.RegisterDSLServiceHandlerFromEndpoint(ctx, grpcGateway, ":"+g.gRPCConfig.Port, dialOpts); err != nil { - panic(failure.Translate(err, appError.ErrServerStartFailed, failure.Messagef("failed to register dsl service on gateway"))) + panic(failure.Translate(err, appError.ErrServerError, failure.Messagef("failed to register dsl service on gateway"))) } if g.mode == "master" { if err := pbSlave.RegisterSlaveServiceHandlerFromEndpoint(ctx, grpcGateway, ":"+g.gRPCConfig.Port, dialOpts); err != nil { - panic(failure.Translate(err, appError.ErrServerStartFailed, failure.Messagef("failed to register slave service on gateway"))) + panic(failure.Translate(err, appError.ErrServerError, failure.Messagef("failed to register slave service on gateway"))) } } @@ -116,14 +117,14 @@ func (g *Gateway) Run(ctx context.Context, wg *sync.WaitGroup) { go func() { if g.httpConfig.TLS.Enabled { if g.httpConfig.TLS.CertPath == "" || g.httpConfig.TLS.KeyPath == "" { - panic(failure.New(appError.ErrServerStartFailed, failure.Messagef("TLS certPath and keyPath must be specified"))) + panic(failure.New(appError.ErrServerError, failure.Messagef("TLS certPath and keyPath must be specified"))) } if err := g.server.ListenAndServeTLS(g.httpConfig.TLS.CertPath, g.httpConfig.TLS.KeyPath); err != nil && err != http.ErrServerClosed { - g.logger.Error(failure.Translate(err, appError.ErrServerInternalError).Error()) + panic(failure.Translate(err, appError.ErrServerError, failure.Messagef("failed to start gateway server with TLS"))) } } else { if err := g.server.ListenAndServe(); err != nil && err != http.ErrServerClosed { - g.logger.Error(failure.Translate(err, appError.ErrServerInternalError).Error()) + panic(failure.Translate(err, appError.ErrServerError, failure.Messagef("failed to start gateway server"))) } } }() @@ -144,7 +145,7 @@ func (g *Gateway) Run(ctx context.Context, wg *sync.WaitGroup) { func (g *Gateway) shutdown(ctx context.Context) { if err := g.server.Shutdown(ctx); err != nil { - g.logger.Error(failure.Translate(err, appError.ErrServerShutdownFailed).Error()) + panic(failure.Translate(err, appError.ErrServerError, failure.Message("failed to shutdown gateway server"))) } g.logger.Info("🛑 gateway server is stopped") } @@ -173,13 +174,17 @@ func (g *Gateway) forwardCheckRequestMiddleware(next http.Handler) http.Handler var reqBody map[string]interface{} var resBody map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil { - http.Error(w, failure.Translate(err, appError.ErrRequestParameterInvalid).Error(), http.StatusBadRequest) + err = failure.Translate(err, appError.ErrRequestParameterInvalid, failure.Messagef("failed to decode request body")) + http.Error(w, err.Error(), http.StatusBadRequest) + logger.LogError(g.logger, err) return } validations, ok := reqBody["validations"].([]interface{}) if !ok { - http.Error(w, failure.New(appError.ErrRequestParameterInvalid, failure.Messagef("validations field is invalid")).Error(), http.StatusBadRequest) + err := failure.New(appError.ErrRequestParameterInvalid, failure.Messagef("validations field is invalid")) + http.Error(w, err.Error(), http.StatusBadRequest) + logger.LogError(g.logger, err) return } @@ -195,12 +200,16 @@ func (g *Gateway) forwardCheckRequestMiddleware(next http.Handler) http.Handler for _, validation := range validations { validation, ok := validation.(map[string]interface{}) if !ok { - http.Error(w, failure.New(appError.ErrRequestParameterInvalid, failure.Messagef("validation field is invalid")).Error(), http.StatusBadRequest) + err := failure.New(appError.ErrRequestParameterInvalid, failure.Messagef("validation field is invalid")) + http.Error(w, err.Error(), http.StatusBadRequest) + logger.LogError(g.logger, err) return } id, ok := validation["id"].(string) if !ok { - http.Error(w, failure.New(appError.ErrRequestParameterInvalid, failure.Messagef("id field is invalid")).Error(), http.StatusBadRequest) + err := failure.New(appError.ErrRequestParameterInvalid, failure.Messagef("id field is invalid")) + http.Error(w, err.Error(), http.StatusBadRequest) + logger.LogError(g.logger, err) return } @@ -241,12 +250,12 @@ func (g *Gateway) forwardCheckRequestMiddleware(next http.Handler) http.Handler } body, err := json.Marshal(reqBody) if err != nil { - errCh <- failure.Translate(err, appError.ErrValidateServiceForwardFailed) + errCh <- failure.Translate(err, appError.ErrRequestForwardFailed, failure.Messagef("failed to marshal request body")) return } req, err := http.NewRequest("POST", slaveNode.Addr+"/v1/check", bytes.NewBuffer(body)) if err != nil { - errCh <- failure.Translate(err, appError.ErrValidateServiceForwardFailed) + errCh <- failure.Translate(err, appError.ErrRequestForwardFailed, failure.Messagef("failed to create forward equest")) return } req.Header.Set("Content-Type", "application/json") @@ -258,19 +267,19 @@ func (g *Gateway) forwardCheckRequestMiddleware(next http.Handler) http.Handler resp, err := client.Do(req) if err != nil { - errCh <- failure.Translate(err, appError.ErrValidateServiceForwardFailed) + errCh <- failure.Translate(err, appError.ErrRequestForwardFailed, failure.Messagef("failed to forward request to slave id:%s", id)) return } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - errCh <- failure.New(appError.ErrValidateServiceForwardFailed, failure.Messagef("Failed to forward the validate request to slave: %d", resp.StatusCode)) + errCh <- failure.New(appError.ErrRequestForwardFailed, failure.Messagef("failed to forward request to slave id:%s", id)) return } var respBody map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&respBody); err != nil { - errCh <- failure.Translate(err, appError.ErrValidateServiceForwardFailed) + errCh <- failure.Translate(err, appError.ErrRequestForwardFailed, failure.Messagef("failed to decode response body")) return } results, ok := respBody["results"].([]interface{}) @@ -290,18 +299,23 @@ func (g *Gateway) forwardCheckRequestMiddleware(next http.Handler) http.Handler select { case err := <-errCh: http.Error(w, err.Error(), http.StatusInternalServerError) + logger.LogError(g.logger, err) return case results := <-ch: validationResults = append(validationResults, results...) case <-time.After(30 * time.Second): - http.Error(w, failure.New(appError.ErrValidateServiceForwardFailed, failure.Message("Timeout")).Error(), http.StatusInternalServerError) + err := failure.New(appError.ErrRequestForwardFailed, failure.Message("request forward timeout")) + http.Error(w, err.Error(), http.StatusInternalServerError) + logger.LogError(g.logger, err) } } reqBody["validations"] = modifiedRequestValidations modifiedReqBody, err := json.Marshal(reqBody) if err != nil { - http.Error(w, failure.Translate(err, appError.ErrRequestParameterInvalid).Error(), http.StatusInternalServerError) + err = failure.Translate(err, appError.ErrRequestParameterInvalid, failure.Messagef("failed to marshal modified request body")) + http.Error(w, err.Error(), http.StatusInternalServerError) + logger.LogError(g.logger, err) return } r.Body = io.NopCloser(bytes.NewBuffer(modifiedReqBody)) @@ -315,18 +329,24 @@ func (g *Gateway) forwardCheckRequestMiddleware(next http.Handler) http.Handler // Concat the validation results if err := json.Unmarshal(rec.body.Bytes(), &resBody); err != nil { - http.Error(w, failure.Translate(err, appError.ErrRequestParameterInvalid).Error(), http.StatusInternalServerError) + err = failure.Translate(err, appError.ErrRequestParameterInvalid, failure.Messagef("failed to decode response body")) + http.Error(w, err.Error(), http.StatusInternalServerError) + logger.LogError(g.logger, err) return } originalValidationResults, ok := resBody["results"].([]interface{}) if !ok { - http.Error(w, failure.New(appError.ErrRequestParameterInvalid, failure.Messagef("results field is invalid")).Error(), http.StatusInternalServerError) + err := failure.New(appError.ErrRequestParameterInvalid, failure.Messagef("results field is invalid")) + http.Error(w, err.Error(), http.StatusInternalServerError) + logger.LogError(g.logger, err) return } resBody["results"] = append(originalValidationResults, validationResults...) resBodyJson, err := json.Marshal(resBody) if err != nil { - http.Error(w, failure.Translate(err, appError.ErrRequestParameterInvalid).Error(), http.StatusInternalServerError) + err = failure.Translate(err, appError.ErrRequestParameterInvalid, failure.Messagef("failed to marshal response body")) + http.Error(w, err.Error(), http.StatusInternalServerError) + logger.LogError(g.logger, err) return } @@ -335,7 +355,7 @@ func (g *Gateway) forwardCheckRequestMiddleware(next http.Handler) http.Handler w.WriteHeader(http.StatusOK) _, err = w.Write(resBodyJson) if err != nil { - g.logger.Error(failure.Translate(err, appError.ErrServerInternalError).Error()) + g.logger.Error(failure.Translate(err, appError.ErrServerError, failure.Messagef("failed to write response")).Error()) } } else { next.ServeHTTP(w, r) @@ -348,38 +368,49 @@ func (g *Gateway) validateRequestTypeConvertMiddleware(next http.Handler) http.H if r.URL.Path == "/v1/check" && r.Method == "POST" { var body map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - http.Error(w, failure.Translate(err, appError.ErrRequestParameterInvalid).Error(), http.StatusBadRequest) + err = failure.Translate(err, appError.ErrRequestParameterInvalid, failure.Messagef("failed to decode request body")) + http.Error(w, err.Error(), http.StatusBadRequest) + logger.LogError(g.logger, err) return } validations, ok := body["validations"].([]interface{}) if !ok { - http.Error(w, failure.New(appError.ErrRequestParameterInvalid, failure.Messagef("validations field is invalid")).Error(), http.StatusBadRequest) + err := failure.New(appError.ErrRequestParameterInvalid, failure.Messagef("validations field is invalid")) + http.Error(w, err.Error(), http.StatusBadRequest) + logger.LogError(g.logger, err) return } for idx, validation := range validations { validation, ok := validation.(map[string]interface{}) if !ok { - http.Error(w, failure.New(appError.ErrRequestParameterInvalid, failure.Messagef("validation field is invalid")).Error(), http.StatusBadRequest) + err := failure.New(appError.ErrRequestParameterInvalid, failure.Messagef("validation field is invalid")) + http.Error(w, err.Error(), http.StatusBadRequest) + logger.LogError(g.logger, err) return } id, ok := validation["id"].(string) if !ok { - http.Error(w, failure.New(appError.ErrRequestParameterInvalid, failure.Messagef("id field is invalid")).Error(), http.StatusBadRequest) + err := failure.New(appError.ErrRequestParameterInvalid, failure.Messagef("id field is invalid")) + http.Error(w, err.Error(), http.StatusBadRequest) + logger.LogError(g.logger, err) return } variables, ok := validation["variables"].(map[string]interface{}) if !ok { - http.Error(w, failure.New(appError.ErrRequestParameterInvalid, failure.Messagef("variables field is invalid")).Error(), http.StatusBadRequest) + err := failure.New(appError.ErrRequestParameterInvalid, failure.Messagef("variables field is invalid")) + http.Error(w, err.Error(), http.StatusBadRequest) + logger.LogError(g.logger, err) return } variableNameToCELType, err := g.dslReader.GetVariableNameToCELType(context.Background(), id) if err != nil { - http.Error(w, failure.Translate(err, appError.ErrRequestParameterInvalid).Error(), http.StatusBadRequest) + http.Error(w, err.Error(), http.StatusInternalServerError) + logger.LogError(g.logger, err) return } @@ -389,6 +420,7 @@ func (g *Gateway) validateRequestTypeConvertMiddleware(next http.Handler) http.H convertedType, err := convertCELTypeToGoogleProtobufType(celType) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) + logger.LogError(g.logger, err) return } variable := make(map[string]interface{}, 2) @@ -404,7 +436,9 @@ func (g *Gateway) validateRequestTypeConvertMiddleware(next http.Handler) http.H body["validations"] = validations convertedBody, err := json.Marshal(body) if err != nil { - http.Error(w, failure.Translate(err, appError.ErrRequestParameterInvalid).Error(), http.StatusInternalServerError) + err = failure.Translate(err, appError.ErrRequestParameterInvalid, failure.Messagef("failed to marshal request body")) + http.Error(w, err.Error(), http.StatusBadRequest) + logger.LogError(g.logger, err) return } @@ -430,6 +464,6 @@ func convertCELTypeToGoogleProtobufType(celType string) (string, error) { case "bytes": return "type.googleapis.com/google.protobuf.BytesValue", nil default: - return "", failure.New(appError.ErrRequestParameterInvalid, failure.Messagef("Unsupported variable type: %s\nPlease specify one of the following types: int, uint, double, bool, string, bytes", celType)) + return "", failure.New(appError.ErrRequestParameterInvalid, failure.Messagef("unsupported variable type: %s\nplease specify one of the following types: int, uint, double, bool, string, bytes", celType)) } } diff --git a/go/pkg/server/grpc.go b/go/pkg/server/grpc.go index 19d6526..af0f489 100644 --- a/go/pkg/server/grpc.go +++ b/go/pkg/server/grpc.go @@ -68,7 +68,7 @@ func (g *GRPC) Run(ctx context.Context, wg *sync.WaitGroup, mode string) { listen, err := net.Listen("tcp", ":"+g.gRPCConfig.Port) if err != nil { - panic(failure.Translate(err, appError.ErrServerStartFailed)) + panic(failure.Translate(err, appError.ErrServerError, failure.Message("failed to listen"))) } grpcServerOpts := []grpc.ServerOption{} @@ -78,28 +78,28 @@ func (g *GRPC) Run(ctx context.Context, wg *sync.WaitGroup, mode string) { }...)) if g.gRPCConfig.TLS.Enabled { if g.gRPCConfig.TLS.CertPath == "" || g.gRPCConfig.TLS.KeyPath == "" { - panic(failure.New(appError.ErrServerStartFailed, failure.Message("certPath and keyPath must be set"))) + panic(failure.New(appError.ErrServerError, failure.Message("certPath and keyPath must be set"))) } creds, err := credentials.NewServerTLSFromFile(g.gRPCConfig.TLS.CertPath, g.gRPCConfig.TLS.KeyPath) if err != nil { - panic(failure.Translate(err, appError.ErrServerStartFailed)) + panic(failure.Translate(err, appError.ErrServerError, failure.Message("failed to load TLS credentials"))) } grpcServerOpts = append(grpcServerOpts, grpc.Creds(creds)) } g.server = grpc.NewServer(grpcServerOpts...) - validateService := svcValidate.NewService(ctx, g.validator) + validateService := svcValidate.NewService(ctx, g.logger, g.validator) pbValidate.RegisterValidateServiceServer(g.server, validateService) - dslService := svcDSL.NewService(ctx, mode, g.dslReader, g.slaveRegistrar) + dslService := svcDSL.NewService(ctx, g.logger, mode, g.dslReader, g.slaveRegistrar) pbDSL.RegisterDSLServiceServer(g.server, dslService) healthService := svcHealth.NewService(ctx) pbHealth.RegisterHealthServer(g.server, healthService) if mode == "master" { - slaveService := svcSlave.NewService(ctx, g.slaveManager) + slaveService := svcSlave.NewService(ctx, g.logger, g.slaveManager) pbSlave.RegisterSlaveServiceServer(g.server, slaveService) } @@ -107,7 +107,7 @@ func (g *GRPC) Run(ctx context.Context, wg *sync.WaitGroup, mode string) { go func() { if err := g.server.Serve(listen); err != nil { - g.logger.Error(failure.Translate(err, appError.ErrServerInternalError).Error()) + panic(failure.Translate(err, appError.ErrServerError, failure.Message("failed to serve grpc server"))) } }() diff --git a/go/pkg/services/dsl/v1/read.go b/go/pkg/services/dsl/v1/read.go index e2f63e6..fb1ae85 100644 --- a/go/pkg/services/dsl/v1/read.go +++ b/go/pkg/services/dsl/v1/read.go @@ -5,12 +5,14 @@ import ( "github.com/shibukazu/open-ve/go/pkg/appError" dslPkg "github.com/shibukazu/open-ve/go/pkg/dsl" + "github.com/shibukazu/open-ve/go/pkg/logger" pb "github.com/shibukazu/open-ve/go/proto/dsl/v1" ) func (s *Service) Read(ctx context.Context, req *pb.ReadRequest) (*pb.ReadResponse, error) { dsl, err := s.dslReader.Read(ctx) if err != nil { + logger.LogError(s.logger, err) return nil, appError.ToGRPCError(err) } return toProto(dsl), nil diff --git a/go/pkg/services/dsl/v1/register.go b/go/pkg/services/dsl/v1/register.go index 96b72cb..c0f03c6 100644 --- a/go/pkg/services/dsl/v1/register.go +++ b/go/pkg/services/dsl/v1/register.go @@ -6,19 +6,26 @@ import ( "github.com/morikuni/failure/v2" "github.com/shibukazu/open-ve/go/pkg/appError" dslPkg "github.com/shibukazu/open-ve/go/pkg/dsl" + "github.com/shibukazu/open-ve/go/pkg/logger" pb "github.com/shibukazu/open-ve/go/proto/dsl/v1" ) func (s *Service) Register(ctx context.Context, req *pb.RegisterRequest) (*pb.RegisterResponse, error) { dsl, err := toDSL(req) if err != nil { + logger.LogError(s.logger, err) return nil, appError.ToGRPCError(err) } if err := s.dslReader.Register(ctx, dsl); err != nil { + logger.LogError(s.logger, err) return nil, appError.ToGRPCError(err) } if s.mode == "slave" { - s.slaveRegistrar.Register(ctx) + err := s.slaveRegistrar.Register(ctx) + if err != nil { + logger.LogError(s.logger, err) + return nil, appError.ToGRPCError(err) + } } return &pb.RegisterResponse{}, nil @@ -27,18 +34,18 @@ func (s *Service) Register(ctx context.Context, req *pb.RegisterRequest) (*pb.Re func toDSL(req *pb.RegisterRequest) (*dslPkg.DSL, error) { dsl := &dslPkg.DSL{} if req.Validations == nil { - return nil, failure.New(appError.ErrDSLServiceDSLSyntaxError, failure.Messagef("Validations is required")) + return nil, failure.New(appError.ErrDSLSyntaxError, failure.Messagef("validations is required")) } dsl.Validations = make([]dslPkg.Validation, len(req.Validations)) for i, validation := range req.Validations { if validation.Id == "" { - return nil, failure.New(appError.ErrDSLServiceDSLSyntaxError, failure.Messagef("Id is required")) + return nil, failure.New(appError.ErrDSLSyntaxError, failure.Messagef("id is required")) } if validation.Cels == nil { - return nil, failure.New(appError.ErrDSLServiceDSLSyntaxError, failure.Messagef("Cel is required")) + return nil, failure.New(appError.ErrDSLSyntaxError, failure.Messagef("cel is required")) } if validation.Variables == nil { - return nil, failure.New(appError.ErrDSLServiceDSLSyntaxError, failure.Messagef("Variables is required")) + return nil, failure.New(appError.ErrDSLSyntaxError, failure.Messagef("variables is required")) } dsl.Validations[i] = dslPkg.Validation{ ID: validation.Id, @@ -47,10 +54,10 @@ func toDSL(req *pb.RegisterRequest) (*dslPkg.DSL, error) { } for j, variable := range validation.Variables { if variable.Name == "" { - return nil, failure.New(appError.ErrDSLServiceDSLSyntaxError, failure.Messagef("Variable Name is required")) + return nil, failure.New(appError.ErrDSLSyntaxError, failure.Messagef("variable name is required")) } if variable.Type == "" { - return nil, failure.New(appError.ErrDSLServiceDSLSyntaxError, failure.Messagef("Variable Type is required")) + return nil, failure.New(appError.ErrDSLSyntaxError, failure.Messagef("variable type is required")) } dsl.Validations[i].Variables[j] = dslPkg.Variable{ Name: variable.Name, diff --git a/go/pkg/services/dsl/v1/service.go b/go/pkg/services/dsl/v1/service.go index 7bdfa5b..1b37af7 100644 --- a/go/pkg/services/dsl/v1/service.go +++ b/go/pkg/services/dsl/v1/service.go @@ -2,6 +2,7 @@ package dslv1 import ( "context" + "log/slog" "github.com/shibukazu/open-ve/go/pkg/dsl/reader" "github.com/shibukazu/open-ve/go/pkg/slave" @@ -10,11 +11,12 @@ import ( type Service struct { pb.UnimplementedDSLServiceServer + logger *slog.Logger mode string dslReader *reader.DSLReader slaveRegistrar *slave.SlaveRegistrar } -func NewService(ctx context.Context, mode string, dslReader *reader.DSLReader, slaveRegistrar *slave.SlaveRegistrar) *Service { - return &Service{mode: mode, dslReader: dslReader, slaveRegistrar: slaveRegistrar} +func NewService(ctx context.Context, logger *slog.Logger, mode string, dslReader *reader.DSLReader, slaveRegistrar *slave.SlaveRegistrar) *Service { + return &Service{logger: logger, mode: mode, dslReader: dslReader, slaveRegistrar: slaveRegistrar} } diff --git a/go/pkg/services/slave/v1/service.go b/go/pkg/services/slave/v1/service.go index a2b4bd5..a755f75 100644 --- a/go/pkg/services/slave/v1/service.go +++ b/go/pkg/services/slave/v1/service.go @@ -2,6 +2,7 @@ package slavev1 import ( "context" + "log/slog" "github.com/shibukazu/open-ve/go/pkg/slave" pb "github.com/shibukazu/open-ve/go/proto/slave/v1" @@ -9,9 +10,10 @@ import ( type Service struct { pb.UnimplementedSlaveServiceServer + logger *slog.Logger slaveManager *slave.SlaveManager } -func NewService(ctx context.Context, slaveManager *slave.SlaveManager) *Service { - return &Service{slaveManager: slaveManager} +func NewService(ctx context.Context, logger *slog.Logger, slaveManager *slave.SlaveManager) *Service { + return &Service{logger: logger, slaveManager: slaveManager} } diff --git a/go/pkg/services/validate/v1/check.go b/go/pkg/services/validate/v1/check.go index 06ad89f..7f5493d 100644 --- a/go/pkg/services/validate/v1/check.go +++ b/go/pkg/services/validate/v1/check.go @@ -5,6 +5,7 @@ import ( "github.com/morikuni/failure/v2" "github.com/shibukazu/open-ve/go/pkg/appError" + "github.com/shibukazu/open-ve/go/pkg/logger" pb "github.com/shibukazu/open-ve/go/proto/validate/v1" "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/wrapperspb" @@ -16,10 +17,12 @@ func (s *Service) Check(ctx context.Context, req *pb.CheckRequest) (*pb.CheckRes for _, validation := range req.Validations { variables, err := convertAnyMapToInterfaceMap(validation.Variables) if err != nil { + logger.LogError(s.logger, err) return nil, appError.ToGRPCError(err) } is_valid, msg, err := s.validator.Validate(validation.Id, variables) if err != nil { + logger.LogError(s.logger, err) return nil, appError.ToGRPCError(err) } results = append(results, &pb.ValidationResult{Id: validation.Id, IsValid: is_valid, Message: msg}) @@ -39,79 +42,79 @@ func convertAnyMapToInterfaceMap(anyMap map[string]*anypb.Any) (map[string]inter case "type.googleapis.com/google.protobuf.StringValue": stringValue := &wrapperspb.StringValue{} if err := anyValue.UnmarshalTo(stringValue); err != nil { - return nil, failure.Translate(err, appError.ErrRequestParameterInvalid) + return nil, failure.Translate(err, appError.ErrRequestParameterInvalid, failure.Messagef("failed to unmarshal string value")) } val = stringValue.Value case "type.googleapis.com/google.protobuf.DoubleValue": doubleValue := &wrapperspb.DoubleValue{} if err := anyValue.UnmarshalTo(doubleValue); err != nil { - return nil, failure.Translate(err, appError.ErrRequestParameterInvalid) + return nil, failure.Translate(err, appError.ErrRequestParameterInvalid, failure.Messagef("failed to unmarshal double value")) } val = doubleValue.Value case "type.googleapis.com/google.protobuf.FloatValue": floatValue := &wrapperspb.FloatValue{} if err := anyValue.UnmarshalTo(floatValue); err != nil { - return nil, failure.Translate(err, appError.ErrRequestParameterInvalid) + return nil, failure.Translate(err, appError.ErrRequestParameterInvalid, failure.Messagef("failed to unmarshal float value")) } val = floatValue.Value case "type.googleapis.com/google.protobuf.Int32Value": intValue := &wrapperspb.Int32Value{} if err := anyValue.UnmarshalTo(intValue); err != nil { - return nil, failure.Translate(err, appError.ErrRequestParameterInvalid) + return nil, failure.Translate(err, appError.ErrRequestParameterInvalid, failure.Messagef("failed to unmarshal int32 value")) } val = intValue.Value case "type.googleapis.com/google.protobuf.Int64Value": intValue := &wrapperspb.Int64Value{} if err := anyValue.UnmarshalTo(intValue); err != nil { - return nil, failure.Translate(err, appError.ErrRequestParameterInvalid) + return nil, failure.Translate(err, appError.ErrRequestParameterInvalid, failure.Messagef("failed to unmarshal int64 value")) } val = intValue.Value case "type.googleapis.com/google.protobuf.UInt32Value": uintValue := &wrapperspb.UInt32Value{} if err := anyValue.UnmarshalTo(uintValue); err != nil { - return nil, failure.Translate(err, appError.ErrRequestParameterInvalid) + return nil, failure.Translate(err, appError.ErrRequestParameterInvalid, failure.Messagef("failed to unmarshal uint32 value")) } val = uintValue.Value case "type.googleapis.com/google.protobuf.UInt64Value": uintValue := &wrapperspb.UInt64Value{} if err := anyValue.UnmarshalTo(uintValue); err != nil { - return nil, failure.Translate(err, appError.ErrRequestParameterInvalid) + return nil, failure.Translate(err, appError.ErrRequestParameterInvalid, failure.Messagef("failed to unmarshal uint64 value")) } val = uintValue.Value case "type.googleapis.com/google.protobuf.SInt32Value": intValue := &wrapperspb.Int32Value{} if err := anyValue.UnmarshalTo(intValue); err != nil { - return nil, failure.Translate(err, appError.ErrRequestParameterInvalid) + return nil, failure.Translate(err, appError.ErrRequestParameterInvalid, failure.Messagef("failed to unmarshal sint32 value")) } val = intValue.Value case "type.googleapis.com/google.protobuf.SInt64Value": intValue := &wrapperspb.Int64Value{} if err := anyValue.UnmarshalTo(intValue); err != nil { - return nil, failure.Translate(err, appError.ErrRequestParameterInvalid) + return nil, failure.Translate(err, appError.ErrRequestParameterInvalid, failure.Messagef("failed to unmarshal sint64 value")) } val = intValue.Value case "type.googleapis.com/google.protobuf.Fixed32Value": uintValue := &wrapperspb.UInt32Value{} if err := anyValue.UnmarshalTo(uintValue); err != nil { - return nil, failure.Translate(err, appError.ErrRequestParameterInvalid) + return nil, failure.Translate(err, appError.ErrRequestParameterInvalid, failure.Messagef("failed to unmarshal fixed32 value")) } val = uintValue.Value case "type.googleapis.com/google.protobuf.Fixed64Value": uintValue := &wrapperspb.UInt64Value{} if err := anyValue.UnmarshalTo(uintValue); err != nil { - return nil, failure.Translate(err, appError.ErrRequestParameterInvalid) + return nil, failure.Translate(err, appError.ErrRequestParameterInvalid, failure.Messagef("failed to unmarshal fixed64 value")) } val = uintValue.Value case "type.googleapis.com/google.protobuf.BoolValue": boolValue := &wrapperspb.BoolValue{} if err := anyValue.UnmarshalTo(boolValue); err != nil { - return nil, failure.Translate(err, appError.ErrRequestParameterInvalid) + return nil, failure.Translate(err, appError.ErrRequestParameterInvalid, failure.Messagef("failed to unmarshal bool value")) } val = boolValue.Value case "type.googleapis.com/google.protobuf.BytesValue": bytesValue := &wrapperspb.BytesValue{} if err := anyValue.UnmarshalTo(bytesValue); err != nil { - return nil, failure.Translate(err, appError.ErrRequestParameterInvalid) + return nil, failure.Translate(err, appError.ErrRequestParameterInvalid, failure.Messagef("failed to unmarshal bytes value")) } val = bytesValue default: diff --git a/go/pkg/services/validate/v1/service.go b/go/pkg/services/validate/v1/service.go index 232598e..65921f2 100644 --- a/go/pkg/services/validate/v1/service.go +++ b/go/pkg/services/validate/v1/service.go @@ -2,6 +2,7 @@ package validatev1 import ( "context" + "log/slog" "github.com/shibukazu/open-ve/go/pkg/validator" pb "github.com/shibukazu/open-ve/go/proto/validate/v1" @@ -9,9 +10,10 @@ import ( type Service struct { pb.UnimplementedValidateServiceServer + logger *slog.Logger validator *validator.Validator } -func NewService(ctx context.Context,validator *validator.Validator) *Service { - return &Service{validator: validator} -} \ No newline at end of file +func NewService(ctx context.Context, logger *slog.Logger, validator *validator.Validator) *Service { + return &Service{logger: logger, validator: validator} +} diff --git a/go/pkg/slave/manager.go b/go/pkg/slave/manager.go index b6e68c9..a997f6f 100644 --- a/go/pkg/slave/manager.go +++ b/go/pkg/slave/manager.go @@ -43,5 +43,5 @@ func (m *SlaveManager) FindSlave(validationId string) (*Slave, error) { } } } - return nil, failure.New(fmt.Sprintf("slave node that can handle the validation ID (%s) is not found", validationId)) + return nil, failure.New(fmt.Sprintf("slave node that can handle validation id (%s) is not found", validationId)) } diff --git a/go/pkg/slave/registrar.go b/go/pkg/slave/registrar.go index 217716a..ce7c3d2 100644 --- a/go/pkg/slave/registrar.go +++ b/go/pkg/slave/registrar.go @@ -15,6 +15,7 @@ import ( "github.com/shibukazu/open-ve/go/pkg/appError" "github.com/shibukazu/open-ve/go/pkg/config" "github.com/shibukazu/open-ve/go/pkg/dsl/reader" + "github.com/shibukazu/open-ve/go/pkg/logger" ) type SlaveRegistrar struct { @@ -57,7 +58,10 @@ func NewSlaveRegistrar(id, slaveHTTPAddress string, slaveTLSEnabled bool, slaveA func (s *SlaveRegistrar) RegisterTimer(ctx context.Context, wg *sync.WaitGroup) { s.logger.Info("🟢 slave registration timer started") - s.Register(ctx) + err := s.Register(ctx) + if err != nil { + logger.LogError(s.logger, err) + } ticker := time.NewTicker(30 * time.Second) for { select { @@ -67,16 +71,18 @@ func (s *SlaveRegistrar) RegisterTimer(ctx context.Context, wg *sync.WaitGroup) wg.Done() return case <-ticker.C: - s.Register(ctx) + err := s.Register(ctx) + if err != nil { + logger.LogError(s.logger, err) + } } } } -func (s *SlaveRegistrar) Register(ctx context.Context) { +func (s *SlaveRegistrar) Register(ctx context.Context) error { dsl, err := s.dslReader.Read(ctx) if err != nil { - s.logger.Error(err.Error()) - return + return err } validationIds := make([]string, len(dsl.Validations)) for i, validation := range dsl.Validations { @@ -96,14 +102,12 @@ func (s *SlaveRegistrar) Register(ctx context.Context) { } body, err := json.Marshal(reqBody) if err != nil { - s.logger.Error(failure.Translate(err, appError.ErrSlaveRegistrationFailed, failure.Message("Failed to marshal request body")).Error()) - return + return failure.Translate(err, appError.ErrServerError, failure.Message("failed to marshal slave registration request")) } req, err := http.NewRequestWithContext(ctx, http.MethodPost, s.MasterHTTPAddress+"/v1/slave/register", bytes.NewReader(body)) if err != nil { - s.logger.Error(failure.Translate(err, appError.ErrSlaveRegistrationFailed, failure.Message("Failed to create request")).Error()) - return + return failure.Translate(err, appError.ErrServerError, failure.Message("failed to create slave registration request")) } req.Header.Set("Content-Type", "application/json") @@ -114,15 +118,15 @@ func (s *SlaveRegistrar) Register(ctx context.Context) { resp, err := s.httpClient.Do(req) if err != nil { - s.logger.Error(failure.Translate(err, appError.ErrSlaveRegistrationFailed, failure.Message("Failed to send request")).Error()) - return + return failure.Translate(err, appError.ErrServerError, failure.Message("failed to send slave registration request")) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - s.logger.Error(failure.New(appError.ErrSlaveRegistrationFailed, failure.Messagef("Failed to register to master: %d", resp.StatusCode)).Error()) - return + return failure.New(appError.ErrServerError, failure.Messagef("failed to register to master: %d", resp.StatusCode)) } else { s.logger.Info("📓 slave registration success") } + + return nil } diff --git a/go/pkg/store/memory.go b/go/pkg/store/memory.go index ccb5afc..dca4b50 100644 --- a/go/pkg/store/memory.go +++ b/go/pkg/store/memory.go @@ -38,7 +38,7 @@ func (s *MemoryStore) WriteSchema(dsl *dsl.DSL) error { enc := json.NewEncoder(&dslJson) enc.SetEscapeHTML(false) if err := enc.Encode(dsl); err != nil { - return failure.Translate(err, appError.ErrDSLSyntaxError) + return failure.Translate(err, appError.ErrDSLSyntaxError, failure.Messagef("failed to encode dsl to json")) } s.mu.Lock() s.memory[s.id+":schema"] = dslJson.Bytes() @@ -53,11 +53,11 @@ func (s *MemoryStore) ReadSchema() (*dsl.DSL, error) { dslJSON, ok := s.memory[s.id+":schema"] s.mu.RUnlock() if !ok { - return nil, failure.New(appError.ErrRedisOperationFailed) + return nil, failure.New(appError.ErrStoreOperationFailed, failure.Messagef("schema not found")) } if err := json.Unmarshal(dslJSON, dsl); err != nil { - return nil, failure.Translate(err, appError.ErrDSLSyntaxError) + return nil, failure.Translate(err, appError.ErrDSLSyntaxError, failure.Messagef("failed to decode dsl from json")) } return dsl, nil } @@ -65,7 +65,7 @@ func (s *MemoryStore) ReadSchema() (*dsl.DSL, error) { func (s *MemoryStore) WriteVariables(id string, variables []dsl.Variable) error { variablesJson, err := json.Marshal(variables) if err != nil { - return failure.Translate(err, appError.ErrDSLSyntaxError) + return failure.Translate(err, appError.ErrDSLSyntaxError, failure.Messagef("failed to encode variables to json")) } s.mu.Lock() s.memory[getVariablesID(s.id, id)] = variablesJson @@ -78,12 +78,12 @@ func (s *MemoryStore) ReadVariables(id string) ([]dsl.Variable, error) { variablesJSON, ok := s.memory[getVariablesID(s.id, id)] s.mu.RUnlock() if !ok { - return nil, failure.New(appError.ErrRedisOperationFailed) + return nil, failure.New(appError.ErrStoreOperationFailed, failure.Messagef("variables not found")) } var variables []dsl.Variable if err := json.Unmarshal(variablesJSON, &variables); err != nil { - return nil, failure.Translate(err, appError.ErrDSLSyntaxError) + return nil, failure.Translate(err, appError.ErrDSLSyntaxError, failure.Messagef("failed to decode variables from json")) } return variables, nil } @@ -107,7 +107,7 @@ func (s *MemoryStore) ReadAllEncodedAST(id string) ([][]byte, error) { jsonEncodedAllEncodedAST, ok := s.memory[getAstID(s.id, id)] s.mu.RUnlock() if !ok { - return nil, failure.New(appError.ErrRedisOperationFailed) + return nil, failure.New(appError.ErrStoreOperationFailed) } return jsonDecodeAllEncodedAST(jsonEncodedAllEncodedAST) } diff --git a/go/pkg/store/redis.go b/go/pkg/store/redis.go index f91e184..9c9b135 100644 --- a/go/pkg/store/redis.go +++ b/go/pkg/store/redis.go @@ -25,7 +25,7 @@ func (s *RedisStore) Reset() error { return nil } if err := s.redisClient.Del(keys...).Err(); err != nil { - return failure.Translate(err, appError.ErrRedisOperationFailed) + return failure.Translate(err, appError.ErrStoreOperationFailed, failure.Messagef("failed to reset redis store")) } return nil } @@ -35,10 +35,10 @@ func (s *RedisStore) WriteSchema(dsl *dsl.DSL) error { enc := json.NewEncoder(&dslJson) enc.SetEscapeHTML(false) if err := enc.Encode(dsl); err != nil { - return failure.Translate(err, appError.ErrDSLSyntaxError) + return failure.Translate(err, appError.ErrDSLSyntaxError, failure.Messagef("failed to encode dsl to json")) } if err := s.redisClient.Set(s.id+":schema", dslJson.String(), 0).Err(); err != nil { - return failure.Translate(err, appError.ErrRedisOperationFailed) + return failure.Translate(err, appError.ErrStoreOperationFailed, failure.Messagef("failed to save schema")) } return nil } @@ -47,11 +47,11 @@ func (s *RedisStore) ReadSchema() (*dsl.DSL, error) { dsl := &dsl.DSL{} dslJSON, err := s.redisClient.Get(s.id + ":schema").Bytes() if err != nil { - return nil, failure.Translate(err, appError.ErrRedisOperationFailed) + return nil, failure.Translate(err, appError.ErrStoreOperationFailed, failure.Messagef("failed to get schema")) } if err := json.Unmarshal(dslJSON, dsl); err != nil { - return nil, failure.Translate(err, appError.ErrDSLSyntaxError) + return nil, failure.Translate(err, appError.ErrDSLSyntaxError, failure.Messagef("failed to decode dsl from json")) } return dsl, nil } @@ -59,10 +59,10 @@ func (s *RedisStore) ReadSchema() (*dsl.DSL, error) { func (s *RedisStore) WriteVariables(id string, variables []dsl.Variable) error { variablesJson, err := json.Marshal(variables) if err != nil { - return failure.Translate(err, appError.ErrDSLSyntaxError) + return failure.Translate(err, appError.ErrDSLSyntaxError, failure.Messagef("failed to encode variables to json")) } if err := s.redisClient.Set(getVariablesID(s.id, id), variablesJson, 0).Err(); err != nil { - return failure.Translate(err, appError.ErrRedisOperationFailed) + return failure.Translate(err, appError.ErrStoreOperationFailed, failure.Messagef("failed to save variables")) } return nil } @@ -70,12 +70,12 @@ func (s *RedisStore) WriteVariables(id string, variables []dsl.Variable) error { func (s *RedisStore) ReadVariables(id string) ([]dsl.Variable, error) { variablesJson, err := s.redisClient.Get(getVariablesID(s.id, id)).Bytes() if err != nil { - return nil, failure.Translate(err, appError.ErrRedisOperationFailed) + return nil, failure.Translate(err, appError.ErrStoreOperationFailed, failure.Messagef("failed to get variables")) } var variables []dsl.Variable if err := json.Unmarshal(variablesJson, &variables); err != nil { - return nil, failure.Translate(err, appError.ErrDSLSyntaxError) + return nil, failure.Translate(err, appError.ErrDSLSyntaxError, failure.Messagef("failed to decode variables from json")) } return variables, nil } @@ -91,7 +91,7 @@ func (s *RedisStore) WriteAllEncodedAST(id string, allEncodedAST [][]byte) error } if err := s.redisClient.Set(getAstID(s.id, id), jsonEncodedAllEncodedAST, 0).Err(); err != nil { - return failure.Translate(err, appError.ErrRedisOperationFailed) + return failure.Translate(err, appError.ErrStoreOperationFailed, failure.Messagef("failed to save all encoded AST")) } return nil } @@ -99,7 +99,7 @@ func (s *RedisStore) WriteAllEncodedAST(id string, allEncodedAST [][]byte) error func (s *RedisStore) ReadAllEncodedAST(id string) ([][]byte, error) { jsonEncodedAllEncodedAST, err := s.redisClient.Get(getAstID(s.id, id)).Bytes() if err != nil { - return nil, failure.Translate(err, appError.ErrRedisOperationFailed) + return nil, failure.Translate(err, appError.ErrStoreOperationFailed, failure.Messagef("failed to get all encoded AST")) } allEncodedAST, err := jsonDecodeAllEncodedAST(jsonEncodedAllEncodedAST) if err != nil { diff --git a/go/pkg/store/util.go b/go/pkg/store/util.go index 68a67ad..e18c99c 100644 --- a/go/pkg/store/util.go +++ b/go/pkg/store/util.go @@ -20,7 +20,7 @@ func jsonEncodeAllEncodedAST(allEncodedAST [][]byte) ([]byte, error) { var buf bytes.Buffer enc := json.NewEncoder(&buf) if err := enc.Encode(allEncodedAST); err != nil { - return nil, failure.Translate(err, appError.ErrInternalError) + return nil, failure.Translate(err, appError.ErrDSLSyntaxError, failure.Messagef("Failed to encode AST: %v", err)) } return buf.Bytes(), nil } @@ -28,7 +28,7 @@ func jsonEncodeAllEncodedAST(allEncodedAST [][]byte) ([]byte, error) { func jsonDecodeAllEncodedAST(jsonEncodedAllEncodedAST []byte) ([][]byte, error) { var allEncodedAST [][]byte if err := json.Unmarshal(jsonEncodedAllEncodedAST, &allEncodedAST); err != nil { - return nil, failure.Translate(err, appError.ErrInternalError) + return nil, failure.Translate(err, appError.ErrDSLSyntaxError, failure.Messagef("Failed to decode encoded AST: %v", err)) } return allEncodedAST, nil } diff --git a/go/pkg/validator/validator.go b/go/pkg/validator/validator.go index c2e3e32..9d0bc80 100644 --- a/go/pkg/validator/validator.go +++ b/go/pkg/validator/validator.go @@ -8,7 +8,7 @@ import ( "github.com/google/cel-go/cel" "github.com/morikuni/failure/v2" "github.com/shibukazu/open-ve/go/pkg/appError" - "github.com/shibukazu/open-ve/go/pkg/dsl" + "github.com/shibukazu/open-ve/go/pkg/dsl/util" "github.com/shibukazu/open-ve/go/pkg/store" exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1" "google.golang.org/protobuf/proto" @@ -28,14 +28,14 @@ func (v *Validator) Validate(id string, variables map[string]interface{}) (bool, if err != nil { return false, "", err } - celVariables, err := dsl.ToCELVariables(dslVariables) + celVariables, err := util.DSLVariablesToCELVariables(dslVariables) if err != nil { return false, "", err } env, err := cel.NewEnv(celVariables...) if err != nil { - return false, "", failure.Translate(err, appError.ErrCELSyantaxError) + return false, "", failure.Translate(err, appError.ErrDSLSyntaxError, failure.Messagef("failed to create CEL environment: %v", err)) } allEncodedAST, err := v.store.ReadAllEncodedAST(id) @@ -51,26 +51,26 @@ func (v *Validator) Validate(id string, variables map[string]interface{}) (bool, go func(encodedAST []byte) { var expr exprpb.CheckedExpr if err = proto.Unmarshal(encodedAST, &expr); err != nil { - errorCh <- failure.Translate(err, appError.ErrDSLSyntaxError) + errorCh <- failure.Translate(err, appError.ErrDSLSyntaxError, failure.Messagef("failed to unmarshal encoded AST")) return } ast := cel.CheckedExprToAst(&expr) prg, err := env.Program(ast) if err != nil { - errorCh <- failure.Translate(err, appError.ErrCELSyantaxError) + errorCh <- failure.Translate(err, appError.ErrDSLSyntaxError, failure.Messagef("failed to create cel program")) return } res, _, err := prg.Eval(variables) if err != nil { - errorCh <- failure.Translate(err, appError.ErrCELSyantaxError) + errorCh <- failure.Translate(err, appError.ErrDSLSyntaxError, failure.Messagef("failed to evaluate cel program")) return } if !res.Value().(bool) { failedCEL, err := cel.AstToString(ast) if err != nil { - errorCh <- failure.Translate(err, appError.ErrCELSyantaxError) + errorCh <- failure.Translate(err, appError.ErrDSLSyntaxError, failure.Messagef("failed to convert AST to string")) return } failedCELCh <- failedCEL