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/entity"
|
||||||
"local/dndex/storage/operator"
|
"local/dndex/storage/operator"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -45,40 +46,54 @@ func TestBoltDBCount(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, filter := range map[string]struct {
|
for name, filter := range map[string]struct {
|
||||||
filter interface{}
|
filter interface{}
|
||||||
match bool
|
matchOne bool
|
||||||
|
matchMany bool
|
||||||
}{
|
}{
|
||||||
"one.Query": {
|
"one.Query": {
|
||||||
filter: ones[0].Query(),
|
filter: ones[0].Query(),
|
||||||
match: true,
|
matchOne: true,
|
||||||
},
|
},
|
||||||
"title:title": {
|
"title:title": {
|
||||||
filter: map[string]interface{}{entity.Title: ones[1].Title},
|
filter: map[string]interface{}{entity.Title: ones[1].Title},
|
||||||
match: true,
|
matchOne: true,
|
||||||
},
|
},
|
||||||
"title:title, text:text": {
|
"title:title, text:text": {
|
||||||
filter: map[string]interface{}{entity.Title: ones[2].Title, entity.Text: ones[2].Text},
|
filter: map[string]interface{}{entity.Title: ones[2].Title, entity.Text: ones[2].Text},
|
||||||
match: true,
|
matchOne: true,
|
||||||
},
|
},
|
||||||
"title:title, text:gibberish": {
|
"title:title, text:gibberish": {
|
||||||
filter: map[string]interface{}{entity.Title: ones[3].Title, entity.Text: ones[2].Text},
|
filter: map[string]interface{}{entity.Title: ones[3].Title, entity.Text: ones[2].Text},
|
||||||
match: false,
|
|
||||||
},
|
},
|
||||||
"name:$in[gibberish]": {
|
"name:$in[gibberish]": {
|
||||||
filter: operator.NewFilterIn(entity.Name, []string{ones[0].Name + ones[1].Name}),
|
filter: operator.NewFilterIn(entity.Name, []string{ones[0].Name + ones[1].Name}),
|
||||||
match: false,
|
|
||||||
},
|
},
|
||||||
"name:$in[name]": {
|
"name:$in[name]": {
|
||||||
filter: operator.NewFilterIn(entity.Name, []string{ones[0].Name}),
|
filter: operator.NewFilterIn(entity.Name, []string{ones[0].Name}),
|
||||||
match: true,
|
matchOne: true,
|
||||||
},
|
},
|
||||||
"name:$regex[gibberish]": {
|
"name:$regex[gibberish]": {
|
||||||
filter: operator.Regex{Key: entity.Name, Value: ones[3].Name + ones[4].Name},
|
filter: operator.Regex{Key: entity.Name, Value: ones[3].Name + ones[4].Name},
|
||||||
match: false,
|
|
||||||
},
|
},
|
||||||
"name:$regex[name]": {
|
"name:$regex[name]": {
|
||||||
filter: operator.Regex{Key: entity.Name, Value: ones[3].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
|
f := filter
|
||||||
|
|
@ -87,10 +102,12 @@ func TestBoltDBCount(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if f.match && n != 1 {
|
if f.matchOne && n != 1 {
|
||||||
t.Fatalf("%v results for %+v, want match=%v", n, f, f.match)
|
t.Fatalf("%v results for %+v, want matchOne=%v", n, f, f.matchOne)
|
||||||
} else if !f.match && n != 0 {
|
} else if f.matchMany && n < 2 {
|
||||||
t.Fatalf("%v results for %+v, want match=%v", n, f, f.match)
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"local/dndex/config"
|
"local/dndex/config"
|
||||||
"local/dndex/storage/driver"
|
"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) {
|
func (g Graph) ListCaseInsensitive(ctx context.Context, namespace string, from ...string) ([]entity.One, error) {
|
||||||
if len(from) == 0 {
|
filter := operator.CaseInsensitives{Key: entity.Name, Values: from}
|
||||||
return g.List(ctx, namespace)
|
|
||||||
}
|
|
||||||
pattern := strings.Join(from, "|")
|
|
||||||
pattern = "^(?i)(" + pattern + ")"
|
|
||||||
filter := operator.Regex{Key: entity.Name, Value: pattern}
|
|
||||||
return g.find(ctx, namespace, filter)
|
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 {
|
if ones, err := g.ListCaseInsensitive(ctx, namespace, one.Name); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if len(ones) > 0 {
|
} 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)
|
return g.driver.Insert(ctx, namespace, one)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ func TestIntegration(t *testing.T) {
|
||||||
randomOne(),
|
randomOne(),
|
||||||
}
|
}
|
||||||
ones[0].Connections = map[string]entity.One{ones[2].Name: entity.One{Name: ones[2].Name, Relationship: ":("}}
|
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() {
|
cleanFill := func() {
|
||||||
clean()
|
clean()
|
||||||
for i := range ones {
|
for i := range ones {
|
||||||
|
|
@ -255,7 +256,7 @@ func TestIntegration(t *testing.T) {
|
||||||
t.Fatal(err)
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -282,7 +283,7 @@ func TestIntegration(t *testing.T) {
|
||||||
t.Fatal(err)
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package operator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
)
|
)
|
||||||
|
|
@ -15,6 +16,36 @@ func (re Regex) MarshalBSON() ([]byte, error) {
|
||||||
return filterMarshal("$regex", re.Key, re.Value)
|
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 {
|
type FilterIn struct {
|
||||||
Key string
|
Key string
|
||||||
Values []interface{}
|
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{})
|
return json.NewEncoder(w).Encode(entity.One{})
|
||||||
}
|
}
|
||||||
if len(ones) > 1 {
|
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]
|
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()})
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue