feat(user/v1): support composite queries (#6361)

* feat(user/v1): support composite queries

* fix: added proper error handling for NotQuery

* Added error when there are too many levels of nesting

* Add localization keys for english

* Update internal/api/grpc/user/query.go
This commit is contained in:
Ahmed Fwela 2023-11-09 02:38:34 -08:00 committed by GitHub
parent e0a5f8661d
commit 3f22fb3a5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 557 additions and 12 deletions

View File

@ -24,7 +24,7 @@ import (
func ListUsersRequestToModel(req *mgmt_pb.ListUsersRequest) (*query.UserSearchQueries, error) { func ListUsersRequestToModel(req *mgmt_pb.ListUsersRequest) (*query.UserSearchQueries, error) {
offset, limit, asc := object.ListQueryToModel(req.Query) offset, limit, asc := object.ListQueryToModel(req.Query)
queries, err := user_grpc.UserQueriesToQuery(req.Queries) queries, err := user_grpc.UserQueriesToQuery(req.Queries, 0 /*start from level 0*/)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -7,10 +7,10 @@ import (
user_pb "github.com/zitadel/zitadel/pkg/grpc/user" user_pb "github.com/zitadel/zitadel/pkg/grpc/user"
) )
func UserQueriesToQuery(queries []*user_pb.SearchQuery) (_ []query.SearchQuery, err error) { func UserQueriesToQuery(queries []*user_pb.SearchQuery, level uint8) (_ []query.SearchQuery, err error) {
q := make([]query.SearchQuery, len(queries)) q := make([]query.SearchQuery, len(queries))
for i, query := range queries { for i, query := range queries {
q[i], err = UserQueryToQuery(query) q[i], err = UserQueryToQuery(query, level)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -18,7 +18,11 @@ func UserQueriesToQuery(queries []*user_pb.SearchQuery) (_ []query.SearchQuery,
return q, nil return q, nil
} }
func UserQueryToQuery(query *user_pb.SearchQuery) (query.SearchQuery, error) { func UserQueryToQuery(query *user_pb.SearchQuery, level uint8) (query.SearchQuery, error) {
if level > 20 {
// can't go deeper than 20 levels of nesting.
return nil, errors.ThrowInvalidArgument(nil, "USER-zsQ97", "Errors.User.TooManyNestingLevels")
}
switch q := query.Query.(type) { switch q := query.Query.(type) {
case *user_pb.SearchQuery_UserNameQuery: case *user_pb.SearchQuery_UserNameQuery:
return UserNameQueryToQuery(q.UserNameQuery) return UserNameQueryToQuery(q.UserNameQuery)
@ -42,6 +46,12 @@ func UserQueryToQuery(query *user_pb.SearchQuery) (query.SearchQuery, error) {
return ResourceOwnerQueryToQuery(q.ResourceOwner) return ResourceOwnerQueryToQuery(q.ResourceOwner)
case *user_pb.SearchQuery_InUserIdsQuery: case *user_pb.SearchQuery_InUserIdsQuery:
return InUserIdsQueryToQuery(q.InUserIdsQuery) return InUserIdsQueryToQuery(q.InUserIdsQuery)
case *user_pb.SearchQuery_OrQuery:
return OrQueryToQuery(q.OrQuery, level)
case *user_pb.SearchQuery_AndQuery:
return AndQueryToQuery(q.AndQuery, level)
case *user_pb.SearchQuery_NotQuery:
return NotQueryToQuery(q.NotQuery, level)
default: default:
return nil, errors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid") return nil, errors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid")
} }
@ -90,3 +100,24 @@ func ResourceOwnerQueryToQuery(q *user_pb.ResourceOwnerQuery) (query.SearchQuery
func InUserIdsQueryToQuery(q *user_pb.InUserIDQuery) (query.SearchQuery, error) { func InUserIdsQueryToQuery(q *user_pb.InUserIDQuery) (query.SearchQuery, error) {
return query.NewUserInUserIdsSearchQuery(q.UserIds) return query.NewUserInUserIdsSearchQuery(q.UserIds)
} }
func OrQueryToQuery(q *user_pb.OrQuery, level uint8) (query.SearchQuery, error) {
mappedQueries, err := UserQueriesToQuery(q.Queries, level+1)
if err != nil {
return nil, err
}
return query.NewUserOrSearchQuery(mappedQueries)
}
func AndQueryToQuery(q *user_pb.AndQuery, level uint8) (query.SearchQuery, error) {
mappedQueries, err := UserQueriesToQuery(q.Queries, level+1)
if err != nil {
return nil, err
}
return query.NewUserAndSearchQuery(mappedQueries)
}
func NotQueryToQuery(q *user_pb.NotQuery, level uint8) (query.SearchQuery, error) {
mappedQuery, err := UserQueryToQuery(q.Query, level+1)
if err != nil {
return nil, err
}
return query.NewUserNotSearchQuery(mappedQuery)
}

View File

@ -2,6 +2,7 @@ package query
import ( import (
"errors" "errors"
"fmt"
"reflect" "reflect"
"time" "time"
@ -92,27 +93,26 @@ func (q *IsNullQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
func (q *IsNullQuery) comp() sq.Sqlizer { func (q *IsNullQuery) comp() sq.Sqlizer {
return sq.Eq{q.Column.identifier(): nil} return sq.Eq{q.Column.identifier(): nil}
} }
func (q *IsNullQuery) Col() Column { func (q *IsNullQuery) Col() Column {
return q.Column return q.Column
} }
type orQuery struct { type OrQuery struct {
queries []SearchQuery queries []SearchQuery
} }
func newOrQuery(queries ...SearchQuery) (*orQuery, error) { func NewOrQuery(queries ...SearchQuery) (*OrQuery, error) {
if len(queries) == 0 { if len(queries) == 0 {
return nil, ErrMissingColumn return nil, ErrMissingColumn
} }
return &orQuery{queries: queries}, nil return &OrQuery{queries: queries}, nil
} }
func (q *orQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder { func (q *OrQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
return query.Where(q.comp()) return query.Where(q.comp())
} }
func (q *orQuery) comp() sq.Sqlizer { func (q *OrQuery) comp() sq.Sqlizer {
or := make(sq.Or, len(q.queries)) or := make(sq.Or, len(q.queries))
for i, query := range q.queries { for i, query := range q.queries {
or[i] = query.comp() or[i] = query.comp()
@ -120,7 +120,66 @@ func (q *orQuery) comp() sq.Sqlizer {
return or return or
} }
func (q *orQuery) Col() Column { type AndQuery struct {
queries []SearchQuery
}
func (q *AndQuery) Col() Column {
return Column{}
}
func NewAndQuery(queries ...SearchQuery) (*AndQuery, error) {
if len(queries) == 0 {
return nil, ErrMissingColumn
}
return &AndQuery{queries: queries}, nil
}
func (q *AndQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
return query.Where(q.comp())
}
func (q *AndQuery) comp() sq.Sqlizer {
and := make(sq.And, len(q.queries))
for i, query := range q.queries {
and[i] = query.comp()
}
return and
}
type NotQuery struct {
query SearchQuery
}
func (q *NotQuery) Col() Column {
return q.query.Col()
}
func NewNotQuery(query SearchQuery) (*NotQuery, error) {
if query == nil {
return nil, ErrMissingColumn
}
return &NotQuery{query: query}, nil
}
func (q *NotQuery) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
return query.Where(q.comp())
}
func (notQ NotQuery) ToSql() (sql string, args []interface{}, err error) {
querySql, queryArgs, queryErr := notQ.query.comp().ToSql()
// Handle the error from the query's ToSql() function.
if queryErr != nil {
return "", queryArgs, queryErr
}
// Construct the SQL statement.
sql = fmt.Sprintf("NOT (%s)", querySql)
return sql, queryArgs, nil
}
func (q *NotQuery) comp() sq.Sqlizer {
return q
}
func (q *OrQuery) Col() Column {
return Column{} return Column{}
} }

View File

@ -1383,6 +1383,415 @@ func TestNumberComparisonFromMethod(t *testing.T) {
} }
} }
func TestNewOrQuery(t *testing.T) {
type args struct {
queries []SearchQuery
}
singleCorrectQuery, _ := NewTextQuery(testCol, "hello", TextEquals)
tests := []struct {
name string
args args
want *OrQuery
wantErr func(error) bool
}{
{
name: "empty values",
args: args{
queries: []SearchQuery{},
},
wantErr: func(err error) bool {
return errors.Is(err, ErrMissingColumn)
},
},
{
name: "correct",
args: args{
queries: []SearchQuery{singleCorrectQuery},
},
want: &OrQuery{
queries: []SearchQuery{singleCorrectQuery},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := NewOrQuery(tt.args.queries...)
if err != nil && tt.wantErr == nil {
t.Errorf("NewOrQuery() no error expected got %v", err)
return
} else if tt.wantErr != nil && !tt.wantErr(err) {
t.Errorf("NewOrQuery() unexpeted error = %v", err)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewOrQuery() = %v, want %v", got, tt.want)
}
})
}
}
func TestOrQuery_comp(t *testing.T) {
q1, _ := NewTextQuery(testCol, "hello1", TextEquals)
q2, _ := NewTextQuery(testCol, "hello2", TextEquals)
q3, _ := NewTextQuery(testCol2, "world1", TextEquals)
q4, _ := NewTextQuery(testCol2, "world2", TextEquals)
orq, _ := NewOrQuery(q3, q4)
type fields struct {
queries []SearchQuery
}
type want struct {
query interface{}
isNil bool
}
tests := []struct {
name string
fields fields
want want
}{
{
name: "single input",
fields: fields{
queries: []SearchQuery{q1},
},
want: want{
query: sq.Or{sq.Eq{"test_table.test_col": "hello1"}},
},
},
{
name: "multi input",
fields: fields{
queries: []SearchQuery{q1, q2},
},
want: want{
query: sq.Or{sq.Eq{"test_table.test_col": "hello1"}, sq.Eq{"test_table.test_col": "hello2"}},
},
},
{
name: "nested inputs",
fields: fields{
queries: []SearchQuery{q1, orq},
},
want: want{
query: sq.Or{sq.Eq{"test_table.test_col": "hello1"}, sq.Or{sq.Eq{"test_table2.test_col2": "world1"}, sq.Eq{"test_table2.test_col2": "world2"}}},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &OrQuery{
queries: tt.fields.queries,
}
query := s.comp()
if query == nil && tt.want.isNil {
return
} else if tt.want.isNil && query != nil {
t.Error("query should not be nil")
}
if !reflect.DeepEqual(query, tt.want.query) {
t.Errorf("wrong query: want: %v, (%T), got: %v, (%T)", tt.want.query, tt.want.query, query, query)
}
})
}
}
func TestNewAndQuery(t *testing.T) {
type args struct {
queries []SearchQuery
}
singleCorrectQuery, _ := NewTextQuery(testCol, "hello", TextEquals)
tests := []struct {
name string
args args
want *AndQuery
wantErr func(error) bool
}{
{
name: "empty values",
args: args{
queries: []SearchQuery{},
},
wantErr: func(err error) bool {
return errors.Is(err, ErrMissingColumn)
},
},
{
name: "correct",
args: args{
queries: []SearchQuery{singleCorrectQuery},
},
want: &AndQuery{
queries: []SearchQuery{singleCorrectQuery},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := NewAndQuery(tt.args.queries...)
if err != nil && tt.wantErr == nil {
t.Errorf("NewAndQuery() no error expected got %v", err)
return
} else if tt.wantErr != nil && !tt.wantErr(err) {
t.Errorf("NewAndQuery() unexpeted error = %v", err)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewAndQuery() = %v, want %v", got, tt.want)
}
})
}
}
func TestAndQuery_comp(t *testing.T) {
q1, _ := NewTextQuery(testCol, "hello1", TextEquals)
q2, _ := NewTextQuery(testCol, "hello2", TextEquals)
q3, _ := NewTextQuery(testCol2, "world1", TextEquals)
q4, _ := NewTextQuery(testCol2, "world2", TextEquals)
andq, _ := NewAndQuery(q3, q4)
type fields struct {
queries []SearchQuery
}
type want struct {
query interface{}
isNil bool
}
tests := []struct {
name string
fields fields
want want
}{
{
name: "single input",
fields: fields{
queries: []SearchQuery{q1},
},
want: want{
query: sq.And{sq.Eq{"test_table.test_col": "hello1"}},
},
},
{
name: "multi input",
fields: fields{
queries: []SearchQuery{q1, q2},
},
want: want{
query: sq.And{sq.Eq{"test_table.test_col": "hello1"}, sq.Eq{"test_table.test_col": "hello2"}},
},
},
{
name: "nested inputs",
fields: fields{
queries: []SearchQuery{q1, andq},
},
want: want{
query: sq.And{sq.Eq{"test_table.test_col": "hello1"}, sq.And{sq.Eq{"test_table2.test_col2": "world1"}, sq.Eq{"test_table2.test_col2": "world2"}}},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &AndQuery{
queries: tt.fields.queries,
}
query := s.comp()
if query == nil && tt.want.isNil {
return
} else if tt.want.isNil && query != nil {
t.Error("query should not be nil")
}
if !reflect.DeepEqual(query, tt.want.query) {
t.Errorf("wrong query: want: %v, (%T), got: %v, (%T)", tt.want.query, tt.want.query, query, query)
}
})
}
}
func TestNewNotQuery(t *testing.T) {
type args struct {
query SearchQuery
}
singleCorrectQuery, _ := NewTextQuery(testCol, "hello", TextEquals)
tests := []struct {
name string
args args
want *NotQuery
wantErr func(error) bool
}{
{
name: "empty query",
args: args{
query: nil,
},
wantErr: func(err error) bool {
return errors.Is(err, ErrMissingColumn)
},
},
{
name: "correct",
args: args{
query: singleCorrectQuery,
},
want: &NotQuery{
query: singleCorrectQuery,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := NewNotQuery(tt.args.query)
if err != nil && tt.wantErr == nil {
t.Errorf("NewNotQuery() no error expected got %v", err)
return
} else if tt.wantErr != nil && !tt.wantErr(err) {
t.Errorf("NewNotQuery() unexpeted error = %v", err)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewNotQuery() = %v, want %v", got, tt.want)
}
})
}
}
func TestNotQuery_comp(t *testing.T) {
q1, _ := NewTextQuery(testCol, "hello1", TextEquals)
type fields struct {
query SearchQuery
}
type want struct {
query interface{}
isNil bool
}
tests := []struct {
name string
fields fields
want want
}{
{
name: "single input",
fields: fields{
query: q1,
},
want: want{
query: &NotQuery{query: q1},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &NotQuery{
query: tt.fields.query,
}
query := s.comp()
if query == nil && tt.want.isNil {
return
} else if tt.want.isNil && query != nil {
t.Error("query should not be nil")
}
if !reflect.DeepEqual(query, tt.want.query) {
t.Errorf("wrong query: want: %v, (%T), got: %v, (%T)", tt.want.query, tt.want.query, query, query)
}
})
}
}
func TestNotQuery_ToSql(t *testing.T) {
q, _ := NewTextQuery(testCol, "hello1", TextEquals)
type fields struct {
query SearchQuery
}
type want struct {
query string
}
tests := []struct {
name string
fields fields
want want
}{
{
name: "single input",
fields: fields{
query: q,
},
want: want{
query: "NOT (test_table.test_col = hello1)",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &NotQuery{
query: tt.fields.query,
}
queryStr, _, err := s.ToSql()
if err != nil {
t.Error("an error raised ")
}
if queryStr == tt.want.query {
t.Errorf("wrong query: want: %v, (%T), got: %v, (%T)", tt.want.query, tt.want.query, queryStr, queryStr)
}
})
}
}
func TestAndOrQueryCombo(t *testing.T) {
q1, _ := NewTextQuery(testCol, "hello1", TextEquals)
q2, _ := NewTextQuery(testCol, "hello2", TextEquals)
q3, _ := NewTextQuery(testCol2, "world1", TextEquals)
q4, _ := NewTextQuery(testCol2, "world2", TextEquals)
andq, _ := NewAndQuery(q3, q4)
orq, _ := NewOrQuery(q1, q2, andq)
type fields struct {
query SearchQuery
}
type want struct {
query interface{}
isNil bool
}
tests := []struct {
name string
fields fields
want want
}{
{
name: "OR containing AND query",
fields: fields{
query: orq,
},
want: want{
query: sq.Or{sq.Eq{"test_table.test_col": "hello1"}, sq.Eq{"test_table.test_col": "hello2"}, sq.And{sq.Eq{"test_table2.test_col2": "world1"}, sq.Eq{"test_table2.test_col2": "world2"}}},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := tt.fields.query
query := s.comp()
if query == nil && tt.want.isNil {
return
} else if tt.want.isNil && query != nil {
t.Error("query should not be nil")
}
if !reflect.DeepEqual(query, tt.want.query) {
t.Errorf("wrong query: want: %v, (%T), got: %v, (%T)", tt.want.query, tt.want.query, query, query)
}
})
}
}
func TestNewInTextQuery(t *testing.T) { func TestNewInTextQuery(t *testing.T) {
type args struct { type args struct {
column Column column Column

View File

@ -629,6 +629,15 @@ func (r *UserSearchQueries) AppendMyResourceOwnerQuery(orgID string) error {
return nil return nil
} }
func NewUserOrSearchQuery(values []SearchQuery) (SearchQuery, error) {
return NewOrQuery(values...)
}
func NewUserAndSearchQuery(values []SearchQuery) (SearchQuery, error) {
return NewAndQuery(values...)
}
func NewUserNotSearchQuery(value SearchQuery) (SearchQuery, error) {
return NewNotQuery(value)
}
func NewUserInUserIdsSearchQuery(values []string) (SearchQuery, error) { func NewUserInUserIdsSearchQuery(values []string) (SearchQuery, error) {
return NewInTextQuery(UserIDCol, values) return NewInTextQuery(UserIDCol, values)
} }

View File

@ -149,7 +149,7 @@ func NewUserGrantWithGrantedQuery(owner string) (SearchQuery, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return newOrQuery(orgQuery, projectQuery) return NewOrQuery(orgQuery, projectQuery)
} }
func NewUserGrantContainsRolesSearchQuery(roles ...string) (SearchQuery, error) { func NewUserGrantContainsRolesSearchQuery(roles ...string) (SearchQuery, error) {

View File

@ -53,6 +53,7 @@ Errors:
Notification: Notification:
NoDomain: Няма намерен домейн за съобщение NoDomain: Няма намерен домейн за съобщение
User: User:
TooManyNestingLevels: Too many query nesting levels (Max 20).
NotFound: Потребителят не може да бъде намерен NotFound: Потребителят не може да бъде намерен
AlreadyExists: Вече съществува потребител AlreadyExists: Вече съществува потребител
NotFoundOnOrg: Потребителят не може да бъде намерен в избраната организация NotFoundOnOrg: Потребителят не може да бъде намерен в избраната организация

View File

@ -51,6 +51,7 @@ Errors:
Notification: Notification:
NoDomain: Keine Domäne für Nachricht gefunden NoDomain: Keine Domäne für Nachricht gefunden
User: User:
TooManyNestingLevels: Too many query nesting levels (Max 20).
NotFound: Benutzer konnte nicht gefunden werden NotFound: Benutzer konnte nicht gefunden werden
AlreadyExists: Benutzer existiert bereits AlreadyExists: Benutzer existiert bereits
NotFoundOnOrg: Benutzer konnte in der gewünschten Organisation nicht gefunden werden NotFoundOnOrg: Benutzer konnte in der gewünschten Organisation nicht gefunden werden

View File

@ -51,6 +51,7 @@ Errors:
Notification: Notification:
NoDomain: No Domain found for message NoDomain: No Domain found for message
User: User:
TooManyNestingLevels: Too many query nesting levels (Max 20).
NotFound: User could not be found NotFound: User could not be found
AlreadyExists: User already exists AlreadyExists: User already exists
NotFoundOnOrg: User could not be found on chosen organization NotFoundOnOrg: User could not be found on chosen organization

View File

@ -51,6 +51,7 @@ Errors:
Notification: Notification:
NoDomain: No se encontró el dominio para el mensaje NoDomain: No se encontró el dominio para el mensaje
User: User:
TooManyNestingLevels: Too many query nesting levels (Max 20).
NotFound: El usuario no pudo encontrarse NotFound: El usuario no pudo encontrarse
AlreadyExists: El usuario ya existe AlreadyExists: El usuario ya existe
NotFoundOnOrg: El usuario no pudo encontrarse en la organización elegida NotFoundOnOrg: El usuario no pudo encontrarse en la organización elegida

View File

@ -51,6 +51,7 @@ Errors:
Notification: Notification:
NoDomain: Aucun domaine trouvé pour le message NoDomain: Aucun domaine trouvé pour le message
User: User:
TooManyNestingLevels: Too many query nesting levels (Max 20).
NotFound: L'utilisateur n'a pas été trouvé NotFound: L'utilisateur n'a pas été trouvé
AlreadyExists: L'utilisateur existe déjà AlreadyExists: L'utilisateur existe déjà
NotFoundOnOrg: L'utilisateur n'a pas été trouvé dans l'organisation choisie NotFoundOnOrg: L'utilisateur n'a pas été trouvé dans l'organisation choisie

View File

@ -51,6 +51,7 @@ Errors:
Notification: Notification:
NoDomain: Nessun dominio trovato per il messaggio NoDomain: Nessun dominio trovato per il messaggio
User: User:
TooManyNestingLevels: Too many query nesting levels (Max 20).
NotFound: L'utente non è stato trovato NotFound: L'utente non è stato trovato
AlreadyExists: L'utente già esistente AlreadyExists: L'utente già esistente
NotFoundOnOrg: L'utente non è stato trovato nell'organizzazione scelta NotFoundOnOrg: L'utente non è stato trovato nell'organizzazione scelta

View File

@ -51,6 +51,7 @@ Errors:
Notification: Notification:
NoDomain: メッセージのドメインが見つかりません NoDomain: メッセージのドメインが見つかりません
User: User:
TooManyNestingLevels: Too many query nesting levels (Max 20).
NotFound: ユーザーが見つかりません NotFound: ユーザーが見つかりません
AlreadyExists: 既に存在するユーザーです AlreadyExists: 既に存在するユーザーです
NotFoundOnOrg: ユーザーが選択した組織内で見つかりません NotFoundOnOrg: ユーザーが選択した組織内で見つかりません

View File

@ -51,6 +51,7 @@ Errors:
Notification: Notification:
NoDomain: Не е пронајден домен за пораката NoDomain: Не е пронајден домен за пораката
User: User:
TooManyNestingLevels: Too many query nesting levels (Max 20).
NotFound: Корисникот не е пронајден NotFound: Корисникот не е пронајден
AlreadyExists: Корисникот веќе постои AlreadyExists: Корисникот веќе постои
NotFoundOnOrg: Корисникот не е пронајден во избраната организација NotFoundOnOrg: Корисникот не е пронајден во избраната организација

View File

@ -51,6 +51,7 @@ Errors:
Notification: Notification:
NoDomain: Nie znaleziono domeny dla wiadomości NoDomain: Nie znaleziono domeny dla wiadomości
User: User:
TooManyNestingLevels: Too many query nesting levels (Max 20).
NotFound: Nie znaleziono użytkownika NotFound: Nie znaleziono użytkownika
AlreadyExists: Użytkownik już istnieje AlreadyExists: Użytkownik już istnieje
NotFoundOnOrg: Użytkownik nie został znaleziony w wybranej organizacji NotFoundOnOrg: Użytkownik nie został znaleziony w wybranej organizacji

View File

@ -51,6 +51,7 @@ Errors:
Notification: Notification:
NoDomain: Nenhum domínio encontrado para a mensagem NoDomain: Nenhum domínio encontrado para a mensagem
User: User:
TooManyNestingLevels: Too many query nesting levels (Max 20).
NotFound: Usuário não pôde ser encontrado NotFound: Usuário não pôde ser encontrado
AlreadyExists: Usuário já existe AlreadyExists: Usuário já existe
NotFoundOnOrg: Usuário não pôde ser encontrado na organização escolhida NotFoundOnOrg: Usuário não pôde ser encontrado na organização escolhida

View File

@ -51,6 +51,7 @@ Errors:
Notification: Notification:
NoDomain: 未找到对应的域名 NoDomain: 未找到对应的域名
User: User:
TooManyNestingLevels: Too many query nesting levels (Max 20).
NotFound: 找不到用户 NotFound: 找不到用户
AlreadyExists: 用户已存在 AlreadyExists: 用户已存在
NotFoundOnOrg: 在所选组织中找不到用户 NotFoundOnOrg: 在所选组织中找不到用户

View File

@ -185,9 +185,35 @@ message SearchQuery {
TypeQuery type_query = 8; TypeQuery type_query = 8;
LoginNameQuery login_name_query = 9; LoginNameQuery login_name_query = 9;
InUserIDQuery in_user_ids_query = 10; InUserIDQuery in_user_ids_query = 10;
OrQuery or_query = 11;
AndQuery and_query = 12;
NotQuery not_query = 13;
} }
} }
message OrQuery {
repeated SearchQuery queries = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "the sub queries to 'OR'"
}
];
}
message AndQuery {
repeated SearchQuery queries = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "the sub queries to 'AND'"
}
];
}
message NotQuery {
SearchQuery query = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "the sub query to negate (NOT)"
}
];
}
message InUserIDQuery { message InUserIDQuery {
repeated string user_ids = 1 [ repeated string user_ids = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {