diff --git a/pkg/action/action.go b/pkg/action/action.go new file mode 100644 index 0000000000000000000000000000000000000000..4233d986f5cf0ecd199f64c42b5f6453e0533fce --- /dev/null +++ b/pkg/action/action.go @@ -0,0 +1,40 @@ +package action + +import ( + "net/url" + "strings" +) + +// URL структура для хранения данных о переданном действии. +type URL struct { + *url.URL +} + +// NewURL возвращает структуру ActionURL +func NewURL(action string) (*URL, error) { + u, err := url.Parse(action) + if err != nil { + return nil, err + } + return &URL{URL: u}, nil +} + +func (u *URL) actionParts() (string, string) { + if u.URL != nil && u.URL.Scheme == "grpc" { + splitPath := strings.Split(strings.TrimLeft(u.Path, "/"), "/") + if len(splitPath) >= 2 { + return splitPath[0], splitPath[1] + } + } + return "", "" +} + +func (u *URL) Action() string { + _, action := u.actionParts() + return action +} + +func (u *URL) Extension() string { + ext, _ := u.actionParts() + return ext +} diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go new file mode 100644 index 0000000000000000000000000000000000000000..2988744f4b88b734c4085b5aa20ceb95fe0657da --- /dev/null +++ b/pkg/action/action_test.go @@ -0,0 +1,135 @@ +package action + +import ( + "fmt" + "net/url" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestActionURL_New(t *testing.T) { + tests := []struct { + name string + action string + want *URL + url string + wantErr assert.ErrorAssertionFunc + }{ + { + name: "Without action", + want: &URL{ + URL: &url.URL{}, + }, + wantErr: assert.NoError, + }, + { + name: "Without deprecated action call", + action: "build-site", + want: &URL{ + URL: &url.URL{ + Path: "build-site", + }, + }, + url: "build-site", + wantErr: assert.NoError, + }, + { + name: "With grpc action", + action: "grpc:///perxisweb/build-site", + want: &URL{ + URL: &url.URL{ + Scheme: "grpc", + Path: "/perxisweb/build-site", + }, + }, + url: "grpc:///perxisweb/build-site", + wantErr: assert.NoError, + }, + { + name: "With ui action", + action: "ui:///space/env/coll", + want: &URL{ + URL: &url.URL{ + Scheme: "ui", + Path: "/space/env/coll", + }, + }, + url: "ui:///space/env/coll", + wantErr: assert.NoError, + }, + { + name: "With http action", + action: "https://perx.ru", + want: &URL{ + URL: &url.URL{ + Scheme: "https", + Host: "perx.ru", + }, + }, + url: "https://perx.ru", + wantErr: assert.NoError, + }, + { + name: "With error in parse", + action: "grpc://user:abc{DEf1=ghi@example.com:5432/db?sslmode=require", + want: nil, + wantErr: assert.Error, + }, + { + name: "With no action id", + action: "grpc:///perxisweb", + want: &URL{ + URL: &url.URL{ + Scheme: "grpc", + Path: "/perxisweb", + }, + }, + wantErr: assert.NoError, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := NewURL(tt.action) + if !tt.wantErr(t, err, fmt.Sprintf("NewURL(%v)", tt.action)) { + return + } + assert.Equalf(t, tt.want, got, "NewURL(%v)", tt.action) + }) + } +} + +func TestActionURL_String(t *testing.T) { + tests := []struct { + name string + url string + want string + }{ + { + name: "GRPC action", + url: "grpc:///perxisweb/build-site", + }, + { + name: "UI action #1", + url: "ui:///space/env/coll", + }, + { + name: "UI action deprecated call #2", + url: "space/env/coll", + }, + { + name: "Https action", + url: "https://perx.ru", + }, + { + name: "With action deprecated call", + url: "extension-id", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p, _ := NewURL(tt.url) + assert.Equalf(t, tt.url, p.String(), "String()") + }) + } +} diff --git a/pkg/expr/mongo.go b/pkg/expr/mongo.go index 70bd6f05e6f3e5c422c88e193a7e1621eaa2f9e6..989454178f640144791a9e6fd84d39317fd1e283 100644 --- a/pkg/expr/mongo.go +++ b/pkg/expr/mongo.go @@ -57,16 +57,11 @@ func convertToMongo(ctx context.Context, tree *parser.Tree, env map[string]inter } c := &compiler{tree: tree, env: env, config: config, identifierRenameFn: identifierRenameFn} - v := c.compile(tree.Node) - switch e := v.(type) { - case bson.M: - b = e - case string: - b = bson.M{"$text": bson.M{"$search": e}} - default: - err = fmt.Errorf("invalid expression") + v, ok := c.compile(tree.Node).(bson.M) + if !ok || v == nil { + return nil, fmt.Errorf("invalid expression") } - return + return v, nil } type compiler struct { diff --git a/pkg/expr/mongo_test.go b/pkg/expr/mongo_test.go index 46f92b551086ae7ad1d5336f35c98d3154b4253d..75ec627c124cc0a24768d3cec30a4eac0ae34c15 100644 --- a/pkg/expr/mongo_test.go +++ b/pkg/expr/mongo_test.go @@ -50,10 +50,15 @@ func TestConvertToMongo(t *testing.T) { {"time", fmt.Sprintf("d > Time.Time('%s')", now.Format(time.RFC3339)), nil, bson.M{"d": bson.M{"$gt": tm}}, false}, {"in", "In(s, [1,2,3])", nil, bson.M{"s": bson.M{"$in": []interface{}{1, 2, 3}}}, false}, {"in", "In(s, 1)", nil, bson.M{"s": bson.M{"$in": []interface{}{1}}}, false}, + {"text search or id", "id", nil, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gotB, err := ConvertToMongo(ctx, tt.eval, tt.env, nil) + if tt.wantErr { + require.Error(t, err) + return + } require.NoError(t, err) assert.Equal(t, tt.wantB, gotB) }) @@ -72,7 +77,7 @@ func BenchmarkConvertToMongo(b *testing.B) { //fmt.Println(len(exp)) for i := 0; i < b.N; i++ { - ConvertToMongo(ctx, exp, nil, nil, expr.Patch(&testVisitor{})) + _, _ = ConvertToMongo(ctx, exp, nil, nil, expr.Patch(&testVisitor{})) } } diff --git a/pkg/extension/server.go b/pkg/extension/server.go index f683d0914d897de21dc052f93734c6604e69ced9..f9b603f50ffb6109f89551a7e3b92b5c355761d4 100644 --- a/pkg/extension/server.go +++ b/pkg/extension/server.go @@ -3,6 +3,7 @@ package extension import ( "context" + "git.perx.ru/perxis/perxis-go/pkg/action" "git.perx.ru/perxis/perxis-go/pkg/errors" pb "git.perx.ru/perxis/perxis-go/proto/extensions" ) @@ -80,8 +81,19 @@ func (srv *Server) Update(ctx context.Context, request *UpdateRequest) (*UpdateR } func (srv *Server) Action(ctx context.Context, in *pb.ActionRequest) (*pb.ActionResponse, error) { + actionURL, err := action.NewURL(in.Action) + if err != nil { + return nil, err + } + ext := actionURL.Extension() + if ext == "" { + ext = in.Extension + } + if ext == "" { + return nil, errors.New("extension ID required") + } - svc, ok := srv.services[in.Extension] + svc, ok := srv.services[ext] if !ok { return nil, ErrUnknownExtension } diff --git a/pkg/extension/server_test.go b/pkg/extension/server_test.go index 5400c8c4786cbe09bf2c56fdfb0611d65048d31d..bee68b1a8e3f8ca90beae259ceda2a933e3f6b3e 100644 --- a/pkg/extension/server_test.go +++ b/pkg/extension/server_test.go @@ -2,11 +2,13 @@ package extension import ( "context" + "fmt" "reflect" "strings" "testing" "git.perx.ru/perxis/perxis-go/pkg/errors" + "github.com/stretchr/testify/assert" ) func TestGetResults(t *testing.T) { @@ -102,3 +104,102 @@ func (t testServerExtension) Uninstall(ctx context.Context, in *UninstallRequest func (t testServerExtension) Action(ctx context.Context, in *ActionRequest) (*ActionResponse, error) { return &ActionResponse{}, t.err } + +func TestServer_Action(t *testing.T) { + getDummyExtension := func(name string, wantErr ...bool) Extension { + ext := &testServerExtension{name: name} + + if len(wantErr) > 0 { + ext.err = errors.WithDetail(errors.New("some err"), "Ошибка") + } + + return ext + } + + var tests = []struct { + name string + services map[string]Extension + in *ActionRequest + want *ActionResponse + wantErr string + }{ + { + name: "GRPC", + services: map[string]Extension{"test-extension": getDummyExtension("test-extension")}, + in: &ActionRequest{ + Action: "grpc:///test-extension/test-action", + SpaceId: "sp", + EnvId: "env", + }, + want: &ActionResponse{State: ResponseDone}, + }, + { + name: "invalid schema", + services: map[string]Extension{"test-extension": getDummyExtension("test-extension")}, + in: &ActionRequest{ + Action: "some:///space/env/coll", + SpaceId: "sp", + EnvId: "env", + }, + want: &ActionResponse{State: ResponseDone}, + wantErr: "extension ID required", + }, + { + name: "Deprecated call", + services: map[string]Extension{"test-extension": getDummyExtension("test-extension")}, + in: &ActionRequest{ + Action: "test-action", + SpaceId: "sp", + EnvId: "env", + Extension: "test-extension", + }, + want: &ActionResponse{State: ResponseDone}, + }, + { + name: "unknown extension", + services: map[string]Extension{"test-extension": getDummyExtension("test-extension")}, + in: &ActionRequest{ + Action: "grpc:///test-extension-2/test-action", + SpaceId: "sp", + EnvId: "env", + }, + want: nil, + wantErr: ErrUnknownExtension.Error(), + }, + { + name: "Deprecated call, without extension", + services: map[string]Extension{"test-extension": getDummyExtension("test-extension")}, + in: &ActionRequest{ + Action: "test-action", + SpaceId: "sp", + EnvId: "env", + }, + want: nil, + wantErr: "extension ID required", + }, + { + name: "Deprecated call, without action and extension)", + services: map[string]Extension{"test-extension": getDummyExtension("test-extension")}, + in: &ActionRequest{ + SpaceId: "sp", + EnvId: "env", + }, + want: nil, + wantErr: "extension ID required", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + srv := &Server{ + services: tt.services, + } + got, err := srv.Action(context.Background(), tt.in) + if tt.wantErr != "" { + assert.EqualErrorf(t, err, tt.wantErr, fmt.Sprintf("Action(%v)", tt.in)) + return + } + assert.Equalf(t, tt.want, got, "Action(%v)", tt.in) + }) + } +} diff --git a/pkg/extension/service/extension.go b/pkg/extension/service/extension.go index 1ee716d9c1d8066b2e1c5a6adbd082382ae30dc0..6d9608ab6877acdc94bfcad0bd62e5fa21eef181 100644 --- a/pkg/extension/service/extension.go +++ b/pkg/extension/service/extension.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "git.perx.ru/perxis/perxis-go/pkg/action" "git.perx.ru/perxis/perxis-go/pkg/clients" "git.perx.ru/perxis/perxis-go/pkg/content" "git.perx.ru/perxis/perxis-go/pkg/errors" @@ -155,7 +156,18 @@ func (s *Extension) Uninstall(ctx context.Context, in *extension.UninstallReques } func (s *Extension) Action(ctx context.Context, in *extension.ActionRequest) (*extension.ActionResponse, error) { - ok, err := extension.CheckInstalled(ctx, s.Content, in.SpaceId, in.EnvId, in.Extension) + actionURL, err := action.NewURL(in.Action) + if err != nil { + return nil, err + } + ext := actionURL.Extension() + if ext == "" { + ext = in.Extension + } + if ext == "" { + return nil, errors.New("extension ID required") + } + ok, err := extension.CheckInstalled(ctx, s.Content, in.SpaceId, in.EnvId, ext) if err != nil { return nil, errors.Wrap(err, "check extension installed") } diff --git a/pkg/items/service.go b/pkg/items/service.go index 6a550dd0ad642f2dd1e02f7966b5963bf2147979..1f4f2867ebfc78ef5bc32bc68c4cd9371acd84bb 100644 --- a/pkg/items/service.go +++ b/pkg/items/service.go @@ -52,8 +52,9 @@ type PreSaver interface { } type Filter struct { - ID []string - Data []*filter.Filter + ID []string + Data []*filter.Filter + // DEPRECATED Use Q instead Search string // Поиск, одновременно поддерживается только один запрос Q []string } diff --git a/pkg/schema/validate/string.go b/pkg/schema/validate/string.go index f05ca540034e3cfab7e57c1aa324bcb343b97431..33fa453d0c707b893803f6e43484e50f232e8506 100644 --- a/pkg/schema/validate/string.go +++ b/pkg/schema/validate/string.go @@ -18,6 +18,9 @@ func MaxLength(max int) Validator { } func (t maxLength) Validate(_ context.Context, field *field.Field, value interface{}) error { + if value == nil { + return nil + } if s, ok := value.(string); ok { n := utf8.RuneCountInString(s) if t > 0 && n > int(t) { @@ -36,6 +39,9 @@ func MinLength(max int) Validator { } func (t minLength) Validate(_ context.Context, field *field.Field, value interface{}) error { + if value == nil { + return nil + } if s, ok := value.(string); ok { n := utf8.RuneCountInString(s) if n < int(t) { diff --git a/pkg/schema/validate/string_test.go b/pkg/schema/validate/string_test.go index b67e8c8d4f5056c61d5483a536701777d716bead..9d48cf4d75c5c7bba2763c125bc1b598e78b23ac 100644 --- a/pkg/schema/validate/string_test.go +++ b/pkg/schema/validate/string_test.go @@ -92,9 +92,15 @@ func TestString(t *testing.T) { wantErr bool }{ {"Length Max", field.String().AddOptions(MaxLength(5)), "1234567", true}, + {"Length Max with <nil>", field.String().AddOptions(MaxLength(5)), nil, false}, + {"Length Max and Required with <nil>", field.String().AddOptions(MaxLength(5), Required()), nil, true}, {"Length Min", field.String().AddOptions(MinLength(10)), "1234", true}, + {"Length Min with <nil>", field.String().AddOptions(MinLength(10)), nil, false}, + {"Length Min and Required with <nil>", field.String().AddOptions(MinLength(10), Required()), nil, true}, {"Length MinMax", field.String().AddOptions(MaxLength(6), MinLength(2)), "1234567", true}, {"Length MinMax", field.String().AddOptions(MaxLength(10), MinLength(7)), "123456", true}, + {"Length MinMax with <nil>", field.String().AddOptions(MaxLength(10), MinLength(7)), nil, false}, + {"Length MinMax and Required with <nil>", field.String().AddOptions(MaxLength(10), MinLength(7), Required()), nil, true}, {"Enum miss", field.String().AddOptions(Enum(EnumOpt{Name: "N 1", Value: "n1"}, EnumOpt{Name: "N 2", Value: "n2"})), "n3", true}, {"Enum match", field.String().AddOptions(Enum(EnumOpt{Name: "N 1", Value: "n1"}, EnumOpt{Name: "N 2", Value: "n2"})), "n2", false}, {"Invalid Schema Options", field.String().AddOptions(Schema()), invalidOptionsSchema, true}, @@ -129,8 +135,6 @@ func TestStringValidate(t *testing.T) { }{ {"String Length Max", field.String().AddOptions(MaxLength(1)), "1", false, ""}, {"String Length Min", field.String().AddOptions(MinLength(1)), "1", false, ""}, - {"Nil Length Max", field.String().AddOptions(MaxLength(1)), nil, true, "validation error: incorrect type: \"invalid\", expected \"string\""}, - {"Nil Length Min", field.String().AddOptions(MinLength(1)), nil, true, "validation error: incorrect type: \"invalid\", expected \"string\""}, {"Int Length Max", field.String().AddOptions(MaxLength(1)), 1, true, "validation error: incorrect type: \"int\", expected \"string\""}, {"Int Length Min", field.String().AddOptions(MinLength(1)), 1, true, "validation error: incorrect type: \"int\", expected \"string\""}, {"Float Length Max", field.String().AddOptions(MaxLength(1)), 1.0, true, "validation error: incorrect type: \"float64\", expected \"string\""},