From 01e55d1cd163da277b031f102aa062f7245b81ab Mon Sep 17 00:00:00 2001 From: agungdwiprasetyo Date: Wed, 19 Jun 2024 10:19:27 +0700 Subject: [PATCH] enhance schema source --- candihelper/file_loader.go | 30 ++++++++- codebase/app/cron_worker/cron_worker.go | 1 + .../app/graphql_server/graphql_handler.go | 11 ++-- codebase/app/graphql_server/graphql_server.go | 1 - codebase/app/graphql_server/option.go | 8 +++ validator/json_schema.go | 34 +++++------ validator/json_schema_storage.go | 61 +++++++++++++++++++ validator/struct.go | 29 ++++----- validator/validator.go | 20 +++--- 9 files changed, 143 insertions(+), 52 deletions(-) diff --git a/candihelper/file_loader.go b/candihelper/file_loader.go index 69585809..37d937a4 100644 --- a/candihelper/file_loader.go +++ b/candihelper/file_loader.go @@ -2,6 +2,7 @@ package candihelper import ( "bytes" + "io/fs" "os" "path/filepath" "strings" @@ -9,7 +10,8 @@ import ( // LoadAllFile from path func LoadAllFile(path, formatFile string) []byte { - var buff bytes.Buffer + buff := bytes.NewBuffer(make([]byte, 128)) + buff.Reset() filepath.Walk(path, func(p string, info os.FileInfo, err error) error { if err != nil { panic(err) @@ -31,3 +33,29 @@ func LoadAllFile(path, formatFile string) []byte { return buff.Bytes() } + +// LoadAllFileFromFS helper for load all file from file system +func LoadAllFileFromFS(fileSystem fs.FS, sourcePath, formatFile string) []byte { + buff := bytes.NewBuffer(make([]byte, 128)) + buff.Reset() + fs.WalkDir(fileSystem, sourcePath, func(path string, info fs.DirEntry, err error) error { + if err != nil { + panic(err) + } + if info.IsDir() { + return nil + } + + fileName := info.Name() + if strings.HasSuffix(fileName, formatFile) { + s, err := fs.ReadFile(fileSystem, path) + if err != nil { + panic(err) + } + buff.Write(s) + } + return nil + }) + + return buff.Bytes() +} diff --git a/codebase/app/cron_worker/cron_worker.go b/codebase/app/cron_worker/cron_worker.go index fd99660d..5c829117 100644 --- a/codebase/app/cron_worker/cron_worker.go +++ b/codebase/app/cron_worker/cron_worker.go @@ -185,6 +185,7 @@ func (c *cronWorker) processJob(job *Job) { trace.Finish(tracer.FinishWithError(err)) }() + trace.SetTag("cron_expr", job.Interval) trace.SetTag("job_name", job.HandlerName) trace.Log("job_param", job.Params) diff --git a/codebase/app/graphql_server/graphql_handler.go b/codebase/app/graphql_server/graphql_handler.go index e93c5890..a6f660fd 100644 --- a/codebase/app/graphql_server/graphql_handler.go +++ b/codebase/app/graphql_server/graphql_handler.go @@ -31,7 +31,9 @@ type Handler interface { // ConstructHandlerFromService for create public graphql handler (maybe inject to rest handler) func ConstructHandlerFromService(service factory.ServiceFactory, opt Option) Handler { - gqlSchema := candihelper.LoadAllFile(os.Getenv(candihelper.WORKDIR)+"api/graphql", ".graphql") + if len(opt.schemaSource) == 0 { + opt.schemaSource = candihelper.LoadAllFile(os.Getenv(candihelper.WORKDIR)+"api/graphql", ".graphql") + } var resolver rootResolver if opt.rootResolver == nil { @@ -54,7 +56,7 @@ func ConstructHandlerFromService(service factory.ServiceFactory, opt Option) Han subscriptionResolverValues[rootName] = subscription if schema := resolverModule.Schema(); schema != "" { - gqlSchema = append(gqlSchema, schema+"\n"...) + opt.schemaSource = append(opt.schemaSource, schema+"\n"...) } } } @@ -64,7 +66,7 @@ func ConstructHandlerFromService(service factory.ServiceFactory, opt Option) Han rootSubscription: constructStruct(subscriptionResolverFields, subscriptionResolverValues), } } else { - gqlSchema = append(gqlSchema, opt.rootResolver.Schema()+"\n"...) + opt.schemaSource = append(opt.schemaSource, opt.rootResolver.Schema()+"\n"...) resolver = rootResolver{ rootQuery: opt.rootResolver.Query(), rootMutation: opt.rootResolver.Mutation(), @@ -99,7 +101,7 @@ func ConstructHandlerFromService(service factory.ServiceFactory, opt Option) Han logger.LogYellow(fmt.Sprintf("[GraphQL] voyager\t\t\t: http://127.0.0.1:%d%s/voyager", opt.httpPort, opt.RootPath)) return &handlerImpl{ - schema: graphql.MustParseSchema((string(gqlSchema)), &resolver, schemaOpts...), + schema: graphql.MustParseSchema(string(opt.schemaSource), &resolver, schemaOpts...), option: opt, } } @@ -109,6 +111,7 @@ type handlerImpl struct { option Option } +// NewHandler init new graphql http handler func NewHandler(schema *graphql.Schema, opt Option) Handler { return &handlerImpl{ schema: schema, diff --git a/codebase/app/graphql_server/graphql_server.go b/codebase/app/graphql_server/graphql_server.go index 70d62ff9..d40b9627 100644 --- a/codebase/app/graphql_server/graphql_server.go +++ b/codebase/app/graphql_server/graphql_server.go @@ -23,7 +23,6 @@ type graphqlServer struct { // NewServer create new GraphQL server func NewServer(service factory.ServiceFactory, opts ...OptionFunc) factory.AppServerFactory { - httpEngine := new(http.Server) server := &graphqlServer{ httpEngine: httpEngine, diff --git a/codebase/app/graphql_server/option.go b/codebase/app/graphql_server/option.go index c212ad42..06958539 100644 --- a/codebase/app/graphql_server/option.go +++ b/codebase/app/graphql_server/option.go @@ -25,6 +25,7 @@ type ( rootResolver interfaces.GraphQLHandler directiveFuncs map[string]types.DirectiveFunc tlsConfig *tls.Config + schemaSource []byte } // OptionFunc type @@ -115,3 +116,10 @@ func SetTLSConfig(tlsConfig *tls.Config) OptionFunc { o.tlsConfig = tlsConfig } } + +// SetSchemaSource option func +func SetSchemaSource(schema []byte) OptionFunc { + return func(o *Option) { + o.schemaSource = schema + } +} diff --git a/validator/json_schema.go b/validator/json_schema.go index aebf121b..e8c80636 100644 --- a/validator/json_schema.go +++ b/validator/json_schema.go @@ -10,40 +10,35 @@ import ( "github.com/golangid/gojsonschema" ) -// JSONSchemaValidator abstraction -type JSONSchemaValidator interface { - ValidateDocument(schemaID string, documentSource interface{}) error -} - // JSONSchemaValidatorOptionFunc type -type JSONSchemaValidatorOptionFunc func(*jsonSchemaValidator) +type JSONSchemaValidatorOptionFunc func(*JSONSchemaValidator) // SetSchemaStorageJSONSchemaValidatorOption option func func SetSchemaStorageJSONSchemaValidatorOption(s Storage) JSONSchemaValidatorOptionFunc { - return func(v *jsonSchemaValidator) { - v.schemaStorage = s + return func(v *JSONSchemaValidator) { + v.SchemaStorage = s } } // AddHideErrorListTypeJSONSchemaValidatorOption option func func AddHideErrorListTypeJSONSchemaValidatorOption(descType ...string) JSONSchemaValidatorOptionFunc { - return func(v *jsonSchemaValidator) { + return func(v *JSONSchemaValidator) { for _, e := range descType { v.notShowErrorListType[e] = struct{}{} } } } -// jsonSchemaValidator validator -type jsonSchemaValidator struct { - schemaStorage Storage +// JSONSchemaValidator validator +type JSONSchemaValidator struct { + SchemaStorage Storage notShowErrorListType map[string]struct{} } // NewJSONSchemaValidator constructor -func NewJSONSchemaValidator(opts ...JSONSchemaValidatorOptionFunc) JSONSchemaValidator { - v := &jsonSchemaValidator{ - schemaStorage: NewInMemStorage(os.Getenv(candihelper.WORKDIR) + "api/jsonschema"), +func NewJSONSchemaValidator(opts ...JSONSchemaValidatorOptionFunc) *JSONSchemaValidator { + v := &JSONSchemaValidator{ + SchemaStorage: NewInMemStorage(os.Getenv(candihelper.WORKDIR) + "api/jsonschema"), notShowErrorListType: map[string]struct{}{ "condition_else": {}, "condition_then": {}, }, @@ -57,11 +52,12 @@ func NewJSONSchemaValidator(opts ...JSONSchemaValidatorOptionFunc) JSONSchemaVal } // ValidateDocument based on schema id -func (v *jsonSchemaValidator) ValidateDocument(schemaSource string, documentSource interface{}) error { - s, err := v.schemaStorage.Get(schemaSource) - if err == nil { - schemaSource = strings.ReplaceAll(s, "{{WORKDIR}}", os.Getenv(candihelper.WORKDIR)) +func (v *JSONSchemaValidator) ValidateDocument(schemaSource string, documentSource interface{}) error { + s, err := v.SchemaStorage.Get(schemaSource) + if err != nil { + return err } + schemaSource = strings.ReplaceAll(s, "{{WORKDIR}}", os.Getenv(candihelper.WORKDIR)) schema, err := gojsonschema.NewSchema(gojsonschema.NewStringLoader(schemaSource)) if err != nil { diff --git a/validator/json_schema_storage.go b/validator/json_schema_storage.go index e445a5a6..3c57184f 100644 --- a/validator/json_schema_storage.go +++ b/validator/json_schema_storage.go @@ -4,6 +4,8 @@ import ( "database/sql" "encoding/json" "fmt" + "io/fs" + "log" "os" "path/filepath" "strings" @@ -24,6 +26,11 @@ type ( storage map[string]string } + fsStorage struct { + sourceMap map[string]string + storage fs.FS + } + sqlStorage struct { db *sql.DB } @@ -131,3 +138,57 @@ func (i *inMemStorage) Store(schemaID string, schema string) error { i.storage[schemaID] = schema return nil } + +// NewFileSystemStorage constructor +func NewFileSystemStorage(fileSystem fs.FS, rootPath string) Storage { + storage := &fsStorage{ + sourceMap: make(map[string]string), + storage: fileSystem, + } + + fs.WalkDir(fileSystem, rootPath, func(path string, info fs.DirEntry, err error) error { + if err != nil { + log.Panicf("json schema: %s", err.Error()) + } + if info.IsDir() { + return nil + } + + fileName := info.Name() + if strings.HasSuffix(fileName, ".json") { + s, err := fs.ReadFile(fileSystem, path) + if err != nil { + return fmt.Errorf("%s: %v", fileName, err) + } + + var data map[string]interface{} + if err := json.Unmarshal(s, &data); err != nil { + return fmt.Errorf("%s: %v", fileName, err) + } + + id, ok := data["$id"].(string) + if !ok { + id = strings.Trim(strings.TrimSuffix(strings.TrimPrefix(path, rootPath), ".json"), "/") + } + storage.sourceMap[id] = path + } + return nil + }) + + fmt.Println(storage.sourceMap) + return storage +} + +func (i *fsStorage) Get(schemaID string) (string, error) { + schemaPath, ok := i.sourceMap[schemaID] + if !ok { + return "", fmt.Errorf("schema '%s' not found", schemaID) + } + + s, err := fs.ReadFile(i.storage, schemaPath) + return string(s), err +} + +func (i *fsStorage) Store(schemaID string, schema string) error { + return nil +} diff --git a/validator/struct.go b/validator/struct.go index 87e25aeb..82517cc4 100644 --- a/validator/struct.go +++ b/validator/struct.go @@ -8,51 +8,46 @@ import ( "github.com/golangid/candi/candihelper" ) -// StructValidator abstraction -type StructValidator interface { - ValidateStruct(data interface{}) error -} - // StructValidatorOptionFunc type -type StructValidatorOptionFunc func(*structValidator) +type StructValidatorOptionFunc func(*StructValidator) // SetCoreStructValidatorOption option func func SetCoreStructValidatorOption(additionalConfigFunc ...func(*validatorengine.Validate)) StructValidatorOptionFunc { - return func(v *structValidator) { + return func(v *StructValidator) { ve := validatorengine.New() for _, additionalFunc := range additionalConfigFunc { additionalFunc(ve) } - v.validator = ve + v.Validator = ve } } -// structValidator struct -type structValidator struct { - validator *validatorengine.Validate +// StructValidator struct +type StructValidator struct { + Validator *validatorengine.Validate } // NewStructValidator using go library // https://github.com/go-playground/validator (all struct tags will be here) // https://godoc.org/github.com/go-playground/validator (documentation using it) // NewStructValidator function -func NewStructValidator(opts ...StructValidatorOptionFunc) StructValidator { +func NewStructValidator(opts ...StructValidatorOptionFunc) *StructValidator { // set struct validator - sv := &structValidator{} + sv := &StructValidator{} for _, opt := range opts { opt(sv) } - if sv.validator == nil { - sv.validator = validatorengine.New() + if sv.Validator == nil { + sv.Validator = validatorengine.New() } return sv } // ValidateStruct function -func (v *structValidator) ValidateStruct(data interface{}) error { - if err := v.validator.Struct(data); err != nil { +func (v *StructValidator) ValidateStruct(data interface{}) error { + if err := v.Validator.Struct(data); err != nil { switch errs := err.(type) { case validatorengine.ValidationErrors: multiError := candihelper.NewMultiError() diff --git a/validator/validator.go b/validator/validator.go index 68748e22..aa07bc7e 100644 --- a/validator/validator.go +++ b/validator/validator.go @@ -4,31 +4,31 @@ package validator type OptionFunc func(*Validator) // SetJSONSchemaValidator option func -func SetJSONSchemaValidator(jsonSchema JSONSchemaValidator) OptionFunc { +func SetJSONSchemaValidator(jsonSchema *JSONSchemaValidator) OptionFunc { return func(v *Validator) { - v.jsonSchema = jsonSchema + v.JSONSchema = jsonSchema } } // SetStructValidator option func -func SetStructValidator(structValidator StructValidator) OptionFunc { +func SetStructValidator(structValidator *StructValidator) OptionFunc { return func(v *Validator) { - v.structValidator = structValidator + v.StructValidator = structValidator } } // Validator instance type Validator struct { - jsonSchema JSONSchemaValidator - structValidator StructValidator + JSONSchema *JSONSchemaValidator + StructValidator *StructValidator } // NewValidator constructor, using jsonschema & struct validator (github.com/go-playground/validator), // jsonschema source file load from "api/jsonschema" directory func NewValidator(opts ...OptionFunc) *Validator { v := &Validator{ - jsonSchema: NewJSONSchemaValidator(), - structValidator: NewStructValidator(), + JSONSchema: NewJSONSchemaValidator(), + StructValidator: NewStructValidator(), } for _, opt := range opts { @@ -39,10 +39,10 @@ func NewValidator(opts ...OptionFunc) *Validator { // ValidateDocument method using jsonschema with input is json source func (v *Validator) ValidateDocument(reference string, document interface{}) error { - return v.jsonSchema.ValidateDocument(reference, document) + return v.JSONSchema.ValidateDocument(reference, document) } // ValidateStruct method, rules from struct tag using github.com/go-playground/validator func (v *Validator) ValidateStruct(data interface{}) error { - return v.structValidator.ValidateStruct(data) + return v.StructValidator.ValidateStruct(data) }