diff --git a/triple/node/node.go b/triple/node/node.go index dc68f32f..e96a7338 100644 --- a/triple/node/node.go +++ b/triple/node/node.go @@ -36,8 +36,11 @@ func (t *Type) String() string { // Covariant checks for given two types A and B, A covariant B if B _is a_ A. // In other word, A _covariant_ B if B is a prefix of A. func (t *Type) Covariant(ot *Type) bool { - // TODO: is /ab covariant of /a ? - return strings.HasPrefix(t.String(), ot.String()) + if !strings.HasPrefix(t.String(), ot.String()) { + return false + } + // /type/foo is covarian of /type, but /typefoo is not covatiant of /type. + return len(t.String()) == len(ot.String()) || t.String()[len(ot.String())] == '/' } // ID represents a node ID. diff --git a/triple/node/node_test.go b/triple/node/node_test.go index 347db100..37246476 100644 --- a/triple/node/node_test.go +++ b/triple/node/node_test.go @@ -65,12 +65,19 @@ func TestNewTypeString(t *testing.T) { if err != nil { t.Errorf("node.NewType(\"/some/type/a\") should never fail with error %v", err) } + tAB, err := NewType("/some/type/ab") + if err != nil { + t.Errorf("node.NewType(\"/some/type/ab\") should never fail with error %v", err) + } if tA.Covariant(tB) { t.Errorf("node.Covariant: %q should not be market as covariant of %q", tA, tB) } if !tB.Covariant(tA) { t.Errorf("node.Covariant: %q should not be market as covariant of %q", tB, tA) } + if tAB.Covariant(tB) { + t.Errorf("node.Covariant: %q should not be market as covariant of %q", tAB, tB) + } } func TestNewNodeFromString(t *testing.T) { diff --git a/triple/predicate/predicate.go b/triple/predicate/predicate.go index 22fc389d..8f44c9fa 100644 --- a/triple/predicate/predicate.go +++ b/triple/predicate/predicate.go @@ -18,6 +18,7 @@ package predicate import ( "encoding/base64" "fmt" + "strconv" "strings" "time" ) @@ -80,7 +81,11 @@ func Parse(s string) (*Predicate, error) { if idx < 0 { return nil, fmt.Errorf("predicate.Parse could not find anchor definition in %s", raw) } - id, ta := raw[1:idx], raw[idx+3:len(raw)-1] + id, ta := raw[0:idx+1], raw[idx+3:len(raw)-1] + id, err := strconv.Unquote(id) + if err != nil { + return nil, fmt.Errorf("predicate.Parse can't unquote id in %s: %v", raw, err) + } // TODO: if id has \" inside, it should be unquoted. if ta == "" { return &Predicate{ diff --git a/triple/predicate/predicate_test.go b/triple/predicate/predicate_test.go index 920b650a..72539205 100644 --- a/triple/predicate/predicate_test.go +++ b/triple/predicate/predicate_test.go @@ -106,7 +106,7 @@ func TestParse(t *testing.T) { pretty := fmt.Sprintf("\"bar\"@[%s]", date) got, err := Parse(pretty) if err != nil { - t.Errorf("predicate.Parse could not create a predicate for %s with error %v", pretty, err) + t.Fatalf("predicate.Parse could not create a predicate for %s with error %v", pretty, err) } if got.Type() != Temporal { t.Errorf("predicate.Parse should have returned a temporal predicate, instead returned %s", got) @@ -121,9 +121,29 @@ func TestParse(t *testing.T) { imm, err := Parse("\"foo\"@[]") if err != nil { - t.Errorf("predicate.Parse failed to immutable predicate \"foo\"@[] with error %v", err) + t.Errorf("predicate.Parse failed to parse immutable predicate \"foo\"@[] with error %v", err) } if imm.Type() != Immutable || imm.ID() != "foo" { - t.Errorf("predicate.Parse failed to immutable predicate \"foo\"@[]; got %v instead", imm) + t.Errorf("predicate.Parse failed to parse immutable predicate \"foo\"@[]; got %v instead", imm) + } +} + +func TestQuotedID(t *testing.T) { + const id = "ba\"r" + const pretty = "\"ba\\\"r\"@[]" + immut, err := NewImmutable(id) + if err != nil { + t.Fatal(err) + } + if got, want := immut.String(), pretty; got != want { + t.Fatalf("predicate.String() = %v; want %v", got, want) + } + + immut, err = Parse(pretty) + if err != nil { + t.Fatalf("predicate.Parse failed to parse immutable predicate \"foo\"@[] with error %v", err) + } + if immut.Type() != Immutable || immut.ID() != id { + t.Errorf("predicate.Parse failed to parse immutable predicate %v; got %v instead", pretty, immut) } }