Make names case insensitive for API
parent
34075cf19a
commit
ee4f23a9f4
|
|
@ -1 +1 @@
|
|||
Subproject commit c98901f929a0c46f6bbf72d8e251c6ad485d81b5
|
||||
Subproject commit 6749d9fda2d1022ab24f36dde889dc51629df983
|
||||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"local/dndex/storage/entity"
|
||||
"local/dndex/storage/operator"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
|
@ -46,39 +47,53 @@ func TestBoltDBCount(t *testing.T) {
|
|||
|
||||
for name, filter := range map[string]struct {
|
||||
filter interface{}
|
||||
match bool
|
||||
matchOne bool
|
||||
matchMany bool
|
||||
}{
|
||||
"one.Query": {
|
||||
filter: ones[0].Query(),
|
||||
match: true,
|
||||
matchOne: true,
|
||||
},
|
||||
"title:title": {
|
||||
filter: map[string]interface{}{entity.Title: ones[1].Title},
|
||||
match: true,
|
||||
matchOne: true,
|
||||
},
|
||||
"title:title, text:text": {
|
||||
filter: map[string]interface{}{entity.Title: ones[2].Title, entity.Text: ones[2].Text},
|
||||
match: true,
|
||||
matchOne: true,
|
||||
},
|
||||
"title:title, text:gibberish": {
|
||||
filter: map[string]interface{}{entity.Title: ones[3].Title, entity.Text: ones[2].Text},
|
||||
match: false,
|
||||
},
|
||||
"name:$in[gibberish]": {
|
||||
filter: operator.NewFilterIn(entity.Name, []string{ones[0].Name + ones[1].Name}),
|
||||
match: false,
|
||||
},
|
||||
"name:$in[name]": {
|
||||
filter: operator.NewFilterIn(entity.Name, []string{ones[0].Name}),
|
||||
match: true,
|
||||
matchOne: true,
|
||||
},
|
||||
"name:$regex[gibberish]": {
|
||||
filter: operator.Regex{Key: entity.Name, Value: ones[3].Name + ones[4].Name},
|
||||
match: false,
|
||||
},
|
||||
"name:$regex[name]": {
|
||||
filter: operator.Regex{Key: entity.Name, Value: ones[3].Name},
|
||||
match: true,
|
||||
matchOne: true,
|
||||
},
|
||||
"name:caseInsensitive[]": {
|
||||
filter: operator.CaseInsensitive{Key: entity.Name, Value: ""},
|
||||
matchMany: true,
|
||||
},
|
||||
"name:caseInsensitive[NAME]": {
|
||||
filter: operator.CaseInsensitive{Key: entity.Name, Value: strings.ToUpper(ones[3].Name)},
|
||||
matchOne: true,
|
||||
},
|
||||
"name:caseInsensitives[[]{}]": {
|
||||
filter: operator.CaseInsensitives{Key: entity.Name, Values: []string{}},
|
||||
matchMany: true,
|
||||
},
|
||||
"name:caseInsensitives[[]{NAME}]": {
|
||||
filter: operator.CaseInsensitives{Key: entity.Name, Values: []string{strings.ToUpper(ones[3].Name)}},
|
||||
matchOne: true,
|
||||
},
|
||||
} {
|
||||
f := filter
|
||||
|
|
@ -87,10 +102,12 @@ func TestBoltDBCount(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if f.match && n != 1 {
|
||||
t.Fatalf("%v results for %+v, want match=%v", n, f, f.match)
|
||||
} else if !f.match && n != 0 {
|
||||
t.Fatalf("%v results for %+v, want match=%v", n, f, f.match)
|
||||
if f.matchOne && n != 1 {
|
||||
t.Fatalf("%v results for %+v, want matchOne=%v", n, f, f.matchOne)
|
||||
} else if f.matchMany && n < 2 {
|
||||
t.Fatalf("%v results for %+v, want matchMany=%v", n, f, f.matchMany)
|
||||
} else if !f.matchOne && !f.matchMany && n != 0 {
|
||||
t.Fatalf("%v results for %+v, want match=%v", n, f, f.matchOne || f.matchMany)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package storage
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"local/dndex/config"
|
||||
"local/dndex/storage/driver"
|
||||
|
|
@ -36,12 +35,7 @@ func (g Graph) Search(ctx context.Context, namespace string, nameContains string
|
|||
}
|
||||
|
||||
func (g Graph) ListCaseInsensitive(ctx context.Context, namespace string, from ...string) ([]entity.One, error) {
|
||||
if len(from) == 0 {
|
||||
return g.List(ctx, namespace)
|
||||
}
|
||||
pattern := strings.Join(from, "|")
|
||||
pattern = "^(?i)(" + pattern + ")"
|
||||
filter := operator.Regex{Key: entity.Name, Value: pattern}
|
||||
filter := operator.CaseInsensitives{Key: entity.Name, Values: from}
|
||||
return g.find(ctx, namespace, filter)
|
||||
}
|
||||
|
||||
|
|
@ -74,7 +68,7 @@ func (g Graph) Insert(ctx context.Context, namespace string, one entity.One) err
|
|||
if ones, err := g.ListCaseInsensitive(ctx, namespace, one.Name); err != nil {
|
||||
return err
|
||||
} else if len(ones) > 0 {
|
||||
return errors.New("collision on primary key when case insensitive")
|
||||
return fmt.Errorf("collision on primary key when case insensitive: cannot create %q because %+v exists", one.Name, ones)
|
||||
}
|
||||
return g.driver.Insert(ctx, namespace, one)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ func TestIntegration(t *testing.T) {
|
|||
randomOne(),
|
||||
}
|
||||
ones[0].Connections = map[string]entity.One{ones[2].Name: entity.One{Name: ones[2].Name, Relationship: ":("}}
|
||||
ones[1].Name = ones[0].Name[1 : len(ones[0].Name)-1]
|
||||
cleanFill := func() {
|
||||
clean()
|
||||
for i := range ones {
|
||||
|
|
@ -255,7 +256,7 @@ func TestIntegration(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = graph.Delete(ctx, "col", operator.Regex{Key: entity.Name, Value: "^(?i)" + one.Name})
|
||||
err = graph.Delete(ctx, "col", operator.CaseInsensitive{Key: entity.Name, Value: one.Name})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -282,7 +283,7 @@ func TestIntegration(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = graph.Delete(ctx, "col", operator.Regex{Key: entity.Name, Value: "^(?i)" + one.Name})
|
||||
err = graph.Delete(ctx, "col", operator.CaseInsensitive{Key: entity.Name, Value: one.Name})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package operator
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
|
@ -15,6 +16,36 @@ func (re Regex) MarshalBSON() ([]byte, error) {
|
|||
return filterMarshal("$regex", re.Key, re.Value)
|
||||
}
|
||||
|
||||
type CaseInsensitives struct {
|
||||
Key string
|
||||
Values []string
|
||||
}
|
||||
|
||||
func (cis CaseInsensitives) MarshalBSON() ([]byte, error) {
|
||||
values := cis.Values
|
||||
if len(cis.Values) == 0 {
|
||||
values = []string{".*"}
|
||||
}
|
||||
ci := CaseInsensitive{
|
||||
Key: cis.Key,
|
||||
Value: fmt.Sprintf("^(%s)$", strings.Join(values, "|")),
|
||||
}
|
||||
return bson.Marshal(ci)
|
||||
}
|
||||
|
||||
type CaseInsensitive struct {
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
func (ci CaseInsensitive) MarshalBSON() ([]byte, error) {
|
||||
value := ci.Value
|
||||
if value == "" {
|
||||
value = "^$"
|
||||
}
|
||||
return bson.Marshal(Regex{Key: ci.Key, Value: "(?i)" + ci.Value})
|
||||
}
|
||||
|
||||
type FilterIn struct {
|
||||
Key string
|
||||
Values []interface{}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ func whoGet(namespace string, g storage.Graph, w http.ResponseWriter, r *http.Re
|
|||
return json.NewEncoder(w).Encode(entity.One{})
|
||||
}
|
||||
if len(ones) > 1 {
|
||||
return errors.New("more than one result found matching " + id)
|
||||
return fmt.Errorf("more than one result found matching %q: %+v", id, ones)
|
||||
}
|
||||
one := ones[0]
|
||||
|
||||
|
|
@ -151,7 +151,7 @@ func whoDelete(namespace string, g storage.Graph, w http.ResponseWriter, r *http
|
|||
return json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
if err := g.Delete(r.Context(), namespace, entity.One{Name: id}); err != nil {
|
||||
if err := g.Delete(r.Context(), namespace, operator.CaseInsensitive{Key: entity.Name, Value: id}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue