From 5f830a8cae685b63d70437186aaa7667a67b2bca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20R=2E=20de=20Miranda?= Date: Thu, 8 Jun 2023 15:42:34 -0300 Subject: [PATCH] Add validator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: André R. de Miranda --- cmd/rest/rest.go | 2 +- go.mod | 2 +- internal/gencode/gencode.go | 2 +- internal/gencode/gencode_test.go | 4 +- model/fhub.go | 144 +++++++++++++++++++++++++++++-- model/function.go | 12 +-- model/package.go | 52 ----------- model/unmarshal.go | 36 ++++---- model/validator.go | 65 ++++++++++++++ model/validator_test.go | 112 ++++++++++++++++++++++++ 10 files changed, 346 insertions(+), 85 deletions(-) delete mode 100644 model/package.go create mode 100644 model/validator.go create mode 100644 model/validator_test.go diff --git a/cmd/rest/rest.go b/cmd/rest/rest.go index 2007606..c179e7d 100644 --- a/cmd/rest/rest.go +++ b/cmd/rest/rest.go @@ -27,7 +27,7 @@ import ( func main() { err := cmd.Rest() if err != nil { - fmt.Println(err) + fmt.Print(err) os.Exit(1) } os.Exit(0) diff --git a/go.mod b/go.mod index f8588d4..062f1c3 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( cuelang.org/go v0.5.0 github.com/dave/jennifer v1.6.1 github.com/gin-gonic/gin v1.9.1 + github.com/go-playground/validator/v10 v10.14.0 github.com/stretchr/testify v1.8.4 github.com/urfave/cli/v2 v2.25.5 ) @@ -20,7 +21,6 @@ require ( github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.14.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/google/uuid v1.2.0 // indirect github.com/json-iterator/go v1.1.12 // indirect diff --git a/internal/gencode/gencode.go b/internal/gencode/gencode.go index 6e27791..333e0fc 100644 --- a/internal/gencode/gencode.go +++ b/internal/gencode/gencode.go @@ -55,7 +55,7 @@ func Exec(root string, rootOutput string) error { return nil } -func gen(fhub model.Fhub) ([]byte, error) { +func gen(fhub model.FHub) ([]byte, error) { f := jen.NewFile("main") f.PackagePrefix = "pkg" diff --git a/internal/gencode/gencode_test.go b/internal/gencode/gencode_test.go index 43214aa..e57fdea 100644 --- a/internal/gencode/gencode_test.go +++ b/internal/gencode/gencode_test.go @@ -11,7 +11,7 @@ import ( func Test_gen(t *testing.T) { t.Run("not use initialize", func(t *testing.T) { - fhub := model.Fhub{ + fhub := model.FHub{ Packages: map[string]model.Package{ "test": { Import: "fhub.dev/test", @@ -69,7 +69,7 @@ func (f *functions) function_test(input map[string]any) map[string]any { }) t.Run("use initialize", func(t *testing.T) { - fhub := model.Fhub{ + fhub := model.FHub{ Packages: map[string]model.Package{ "test": { Import: "fhub.dev/test", diff --git a/model/fhub.go b/model/fhub.go index bdc49f9..8931637 100644 --- a/model/fhub.go +++ b/model/fhub.go @@ -17,13 +17,145 @@ package model -type Fhub struct { - Name string - Version string - SpecVersion string +type FHub struct { + // Function namespace + Name string `validate:"required"` + // Function version + Version string `validate:"required"` + // FHub schema version + SpecVersion string `validate:"required"` Constants map[string]string Env []string Import []string - Packages map[string]Package - Functions map[string]Function + Packages map[string]Package `validate:"min=1,dive"` + Functions map[string]Function `validate:"min=1,dive"` +} + +func (in *FHub) DeepCopy() (out *FHub) { + out = new(FHub) + *out = *in + + out.Env = make([]string, len(in.Env)) + copy(out.Env, in.Env) + + out.Import = make([]string, len(in.Import)) + copy(out.Import, in.Env) + + out.Constants = make(map[string]string, len(in.Constants)) + for key, constant := range in.Constants { + out.Constants[key] = constant + } + + out.Functions = make(map[string]Function, len(in.Functions)) + for key, function := range in.Functions { + out.Functions[key] = function + } + + out.Packages = make(map[string]Package, len(in.Packages)) + for key, pkg := range in.Packages { + out.Packages[key] = pkg + } + + return +} + +type Package struct { + Import string `validate:"required"` + Launch string + Build Build + Serving Serving +} + +func (p *Package) HasLaunch() bool { + return p.Launch != "" +} + +func (in *Package) DeepCopy() (out *Package) { + *out = *in + return +} + +type Serving struct { + Http *Http + Grpc *Grpc +} + +func (in *Serving) DeepCopy() (out *Serving) { + out = new(Serving) + *out = *in + + if out.Http != nil { + in, out := &in.Http, &out.Http + *out = new(Http) + **out = **in + } + if out.Grpc != nil { + in, out := &in.Grpc, &out.Grpc + *out = new(Grpc) + **out = **in + } + return +} + +type Http struct { + Url string `validate:"required"` +} + +func (in *Http) DeepCopy() (out *Http) { + out = new(Http) + *out = *in + return +} + +type Grpc struct { + Proto string `validate:"required"` +} + +func (in *Grpc) DeepCopy() (out *Grpc) { + out = new(Grpc) + *out = *in + return +} + +type Build struct { + Local *Local `validate:"required_without=Container"` + Container *Container `validate:"required_without=Local"` +} + +func (in *Build) DeepCopy() (out *Build) { + out = new(Build) + *out = *in + if out.Local != nil { + in, out := &in.Local, &out.Local + *out = new(Local) + **out = **in + } + if out.Container != nil { + in, out := &in.Container, &out.Container + *out = new(Container) + **out = **in + } + return out +} + +type Local struct { + Source string `validate:"required"` +} + +func (in *Local) DeepCopy() (out *Local) { + out = new(Local) + *out = *in + return +} + +type Container struct { + Image string `validate:"required_without=ContainerFile"` + ContainerFile string `validate:"required_without=Image"` + Source string `validate:"required"` +} + +func (in *Container) DeepCopy() (out *Container) { + out = new(Container) + *out = *in + return } diff --git a/model/function.go b/model/function.go index 7cd59c4..a6c1993 100644 --- a/model/function.go +++ b/model/function.go @@ -30,13 +30,13 @@ type Function struct { inputValue cue.Value `fhub:"input" fhub-unmarshal:"true"` outputValue cue.Value `fhub:"output" fhub-unmarshal:"true"` - Package string - Launch string + Package string `validate:"required"` + Launch string `validate:"required"` - InputsLabel []string - InputsType []string - OutputsLabel []string - OutputsType []string + InputsLabel []string `validate:"min=1"` + InputsType []string `validate:"min=1"` + OutputsLabel []string `validate:"min=1"` + OutputsType []string `validate:"min=1"` } func (f *Function) Unmarshal(field string, value cue.Value) (err error) { diff --git a/model/package.go b/model/package.go deleted file mode 100644 index f64ea94..0000000 --- a/model/package.go +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2023 The fhub-runtime-go Authors -// This file is part of fhub-runtime-go. -// -// This file is part of fhub-runtime-go. -// fhub-runtime-go is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// fhub-runtime-go is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with fhub-runtime-go. If not, see . - -package model - -type Package struct { - Import string - Launch string - Serving Serving - Build Build -} - -func (p *Package) HasLaunch() bool { - return p.Launch != "" -} - -type Serving struct { - Http Http -} - -type Http struct { - Url string -} - -type Build struct { - Local Local - Container Container -} - -type Local struct { - Source string -} - -type Container struct { - Image string - ContainerFile string - Source string -} diff --git a/model/unmarshal.go b/model/unmarshal.go index ff47740..5b1a04a 100644 --- a/model/unmarshal.go +++ b/model/unmarshal.go @@ -30,48 +30,48 @@ import ( "cuelang.org/go/cue/cuecontext" ) -func UnmarshalFile(path string) (Fhub, error) { +func UnmarshalFile(path string) (FHub, error) { data, err := os.ReadFile(path) if err != nil { - return Fhub{}, err + return FHub{}, err } return UnmarshalBytes(data) } -func UnmarshalHttp(url string) (Fhub, error) { +func UnmarshalHttp(url string) (FHub, error) { resp, err := http.Get(url) if err != nil { - return Fhub{}, err + return FHub{}, err } body, err := io.ReadAll(resp.Body) if err != nil { - return Fhub{}, err + return FHub{}, err } return UnmarshalBytes(body) } -func UnmarshalBytes(data []byte) (Fhub, error) { +func UnmarshalString(data string) (FHub, error) { + return UnmarshalBytes([]byte(data)) +} + +func UnmarshalBytes(data []byte) (FHub, error) { ctx := cuecontext.New() value := ctx.CompileBytes(data) fhub, err := unmarshalStart(value) if err != nil { - return Fhub{}, err + return FHub{}, err } - return fhub, nil -} -func UnmarshalString(data string) (Fhub, error) { - ctx := cuecontext.New() - value := ctx.CompileString(data) - fhub, err := unmarshalStart(value) + err = Validator(fhub) if err != nil { - return Fhub{}, err + return FHub{}, err } + return fhub, nil } -func unmarshalStart(value cue.Value) (Fhub, error) { - fhub := Fhub{} +func unmarshalStart(value cue.Value) (FHub, error) { + fhub := FHub{} outValueOf := reflect.ValueOf(&fhub).Elem() err := unmarshalDiscoverType("fhub", outValueOf, value) @@ -98,9 +98,13 @@ func unmarshalDiscoverType(namespace string, outValueOf reflect.Value, value cue err = unmarshalMapIt(namespace, outValueOf, value) case reflect.Slice: err = unmarshalListIt(namespace, outValueOf, value) + case reflect.Ptr: + outValueOf.Set(reflect.New(outValueOf.Type().Elem())) + err = unmarshalDiscoverType(namespace, outValueOf.Elem(), value) default: panic(fmt.Sprintf("type %q not implemented from key", kind)) } + return err } diff --git a/model/validator.go b/model/validator.go new file mode 100644 index 0000000..a8f4370 --- /dev/null +++ b/model/validator.go @@ -0,0 +1,65 @@ +// Copyright 2023 The fhub-runtime-go Authors +// This file is part of fhub-runtime-go. +// +// This file is part of fhub-runtime-go. +// fhub-runtime-go is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// fhub-runtime-go is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with fhub-runtime-go. If not, see . + +package model + +import ( + "context" + + "github.com/go-playground/validator/v10" +) + +type ValidatorCtxValueKey string + +const ValidatorCtxValue ValidatorCtxValueKey = "value" + +func Validator(model FHub) error { + validate := validator.New() + + validate.RegisterStructValidationCtx(fhubStructLevel, FHub{}) + validate.RegisterStructValidationCtx(functionStructLevel, Function{}) + + ctx := context.Background() + ctx = context.WithValue(ctx, ValidatorCtxValue, model) + err := validate.StructCtx(ctx, model) + + return err +} + +func fhubStructLevel(ctx context.Context, sl validator.StructLevel) { + // fhub, ok := sl.Current().Interface().(FHub) + // if !ok { + // return + // } + +} + +func functionStructLevel(ctx context.Context, sl validator.StructLevel) { + function, ok := sl.Current().Interface().(Function) + if !ok { + return + } + + fhub, ok := ctx.Value(ValidatorCtxValue).(FHub) + if !ok { + return + } + + if _, ok := fhub.Packages[function.Package]; !ok { + sl.ReportError(sl.Current(), "package", "Package", "exists", "") + } +} diff --git a/model/validator_test.go b/model/validator_test.go new file mode 100644 index 0000000..6db9c27 --- /dev/null +++ b/model/validator_test.go @@ -0,0 +1,112 @@ +// Copyright 2023 The fhub-runtime-go Authors +// This file is part of fhub-runtime-go. +// +// This file is part of fhub-runtime-go. +// fhub-runtime-go is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// fhub-runtime-go is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with fhub-runtime-go. If not, see . + +package model + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +var structFhubDefault = FHub{ + Name: "namespace", + Version: "0.1", + SpecVersion: "0.1", + Packages: map[string]Package{ + "test": { + Import: "fhub.dev/test", + Build: Build{ + Local: &Local{ + Source: "./", + }, + }, + }, + }, + Functions: map[string]Function{ + "fuctionTest": { + Package: "test", + Launch: "Test", + InputsLabel: []string{"arg1"}, + InputsType: []string{"string"}, + OutputsLabel: []string{"out1"}, + OutputsType: []string{"string"}, + }, + }, +} + +type validatorTestCase struct { + Name string + Model FHub + Err string +} + +func TestValidator(t *testing.T) { + testCases := []validatorTestCase{{ + Name: "success", + Model: structFhubDefault, + Err: ``, + }, { + Name: "", + Model: func() FHub { + f := structFhubDefault + f.Name = "" + f.Version = "" + f.SpecVersion = "" + return f + }(), + Err: `Key: 'FHub.Name' Error:Field validation for 'Name' failed on the 'required' tag +Key: 'FHub.Version' Error:Field validation for 'Version' failed on the 'required' tag +Key: 'FHub.SpecVersion' Error:Field validation for 'SpecVersion' failed on the 'required' tag`, + }, { + Name: "package not exists", + Model: func() FHub { + f := structFhubDefault.DeepCopy() + ff := f.Functions["fuctionTest"] + ff.Package = "invalid" + f.Functions["fuctionTest"] = ff + + return *f + }(), + Err: `Key: 'FHub.Functions[fuctionTest].package' Error:Field validation for 'package' failed on the 'exists' tag`, + }, { + Name: "build required", + Model: func() FHub { + f := structFhubDefault.DeepCopy() + pkg := f.Packages["test"] + pkg.Build.Local = nil + f.Packages["test"] = pkg + return *f + }(), + Err: `Key: 'FHub.Packages[test].Build.Local' Error:Field validation for 'Local' failed on the 'required_without' tag +Key: 'FHub.Packages[test].Build.Container' Error:Field validation for 'Container' failed on the 'required_without' tag`, + }} + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + err := Validator(tc.Model) + if tc.Err != "" { + if assert.Error(t, err) { + assert.Equal(t, tc.Err, err.Error()) + } + } else { + assert.NoError(t, err) + } + }) + } + +}