Skip to content

Commit

Permalink
add support for relation types, add some ID char checks and tests
Browse files Browse the repository at this point in the history
Signed-off-by: Doug Davis <[email protected]>
  • Loading branch information
duglin committed Dec 31, 2024
1 parent 228b7e9 commit 40d13de
Show file tree
Hide file tree
Showing 9 changed files with 588 additions and 220 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,4 @@ TODOs:
- remove entities from cache upon delete
- test creating a resource + lots of versions w/o ?defaultversionid-should fail
- PUT ../f1 POST .../f1/versions POT .../versions
- Split the model.verify stuff so it doesn't verify the data unless asked to
97 changes: 6 additions & 91 deletions cmds/server/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,7 @@ func LoadDirsSample(reg *registry.Registry) *registry.Registry {
ErrFatalf(err)

_, err = reg.Model.AddAttrArray("arrmap",
registry.NewItemMap(
registry.NewItemType(registry.STRING)))
registry.NewItemMap(registry.NewItemType(registry.STRING)))
ErrFatalf(err)

ErrFatalf(reg.SetSave("bool1", true))
Expand Down Expand Up @@ -281,6 +280,9 @@ func LoadDirsSample(reg *registry.Registry) *registry.Registry {
_, err = rm.AddAttr("*", registry.STRING)
ErrFatalf(err)

_, err = reg.Model.AddAttrRelation("resptr", "/dirs/files/versions?")
ErrFatalf(err)

ErrFatalf(reg.Model.Verify())

g, err := reg.AddGroup("dirs", "dir1")
Expand All @@ -295,6 +297,7 @@ func LoadDirsSample(reg *registry.Registry) *registry.Registry {
ErrFatalf(r.SetSaveMeta("labels.none", ""))
ErrFatalf(r.SetSaveMeta("rext", "a string"))
ErrFatalf(r.SetSaveDefault("vext", "a ver string"))
ErrFatalf(reg.SetSave("resptr", "/dirs/dir1/files/f1/versions/v1"))

_, err = g.AddResource("datas", "d1", "v1")

Expand Down Expand Up @@ -328,60 +331,6 @@ func LoadEndpointsSample(reg *registry.Registry) *registry.Registry {
err = reg.LoadModelFromFile(fn)
ErrFatalf(err)

/*
ep, err := reg.Model.AddGroupModel("endpoints", "endpoint")
ErrFatalf(err)
attr, err := ep.AddAttr("usage", registry.STRING)
ErrFatalf(err)
// TODO make these required
// attr.ClientRequired = true
// attr.ServerRequired = true
_, err = ep.AddAttr("origin", registry.URI)
ErrFatalf(err)
_, err = ep.AddAttr("channel", registry.STRING)
ErrFatalf(err)
attr, err = ep.AddAttrObj("deprecated")
ErrFatalf(err)
_, err = attr.AddAttr("effective", registry.TIMESTAMP)
ErrFatalf(err)
_, err = attr.AddAttr("removal", registry.TIMESTAMP)
ErrFatalf(err)
_, err = attr.AddAttr("alternative", registry.URL)
ErrFatalf(err)
_, err = attr.AddAttr("docs", registry.URL)
ErrFatalf(err)
config, err := attr.AddAttrObj("config")
ErrFatalf(err)
_, err = config.AddAttr("protocol", registry.STRING)
ErrFatalf(err)
obj, err := config.AddAttrMap("endpoints", registry.NewItemObject())
ErrFatalf(err)
obj.Item.SetItem(registry.NewItem())
_, err = obj.Item.Item.AddAttr("*", registry.ANY)
ErrFatalf(err)
auth, err := config.AddAttrObj("authorization")
ErrFatalf(err)
attr, err = auth.AddAttr("type", registry.STRING)
ErrFatalf(err)
attr, err = auth.AddAttr("resourceurl", registry.STRING)
ErrFatalf(err)
attr, err = auth.AddAttr("authorityurl", registry.STRING)
ErrFatalf(err)
attr, err = auth.AddAttrArray("grant_types", registry.NewItemType(registry.STRING))
ErrFatalf(err)
_, err = config.AddAttr("strict", registry.BOOLEAN)
ErrFatalf(err)
_, err = config.AddAttrMap("options", registry.NewItemType(registry.ANY))
ErrFatalf(err)
_, err = ep.AddResourceModel("definitions", "definition", 2, true, true, true)
ErrFatalf(err)
*/

// End of model

g, err := reg.AddGroupWithObject("endpoints", "e1", registry.Object{
Expand Down Expand Up @@ -451,41 +400,7 @@ func LoadMessagesSample(reg *registry.Registry) *registry.Registry {
err = reg.LoadModelFromFile(fn)
ErrFatalf(err)

/*
msgs, _ := reg.Model.AddGroupModel("messagegroups", "messagegroup")
msgs.AddAttr("binding", registry.STRING)
msg, _ := msgs.AddResourceModel("messages", "message", 1, true, true, false)
// Modify core attribute
attr, _ := msg.AddAttr("format", registry.STRING)
attr.ClientRequired = true
attr.ServerRequired = true
msg.AddAttr("basedefinitionurl", registry.URL)
meta, _ := msg.AddAttrObj("metadata")
meta.AddAttr("required", registry.BOOLEAN)
meta.AddAttr("description", registry.STRING)
meta.AddAttr("value", registry.ANY)
meta.AddAttr("type", registry.STRING)
meta.AddAttr("specurl", registry.URL)
obj := registry.NewItemObject()
meta.AddAttrMap("attributes", obj)
obj.AddAttr("type", registry.STRING)
obj.AddAttr("value", registry.ANY)
obj.AddAttr("required", registry.BOOLEAN)
meta.AddAttr("binding", registry.STRING)
meta.AddAttrMap("message", registry.NewItemType(registry.ANY))
meta.AddAttr("schemaformat", registry.STRING)
meta.AddAttr("schema", registry.ANY)
meta.AddAttr("schemaurl", registry.URL)
// End of model
*/
// End of model

ErrFatalf(reg.Model.Verify())
reg.Commit()
Expand Down
1 change: 1 addition & 0 deletions registry/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const DECIMAL = "decimal"
const INTEGER = "integer"
const MAP = "map"
const OBJECT = "object"
const RELATION = "relation"
const STRING = "string"
const TIMESTAMP = "timestamp"
const UINTEGER = "uinteger"
Expand Down
128 changes: 128 additions & 0 deletions registry/entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -856,6 +856,10 @@ var OrderedSpecProps = []*Attribute{
singular)
}

if !IsValidID(newID.(string)) {
return fmt.Errorf(`%q isn't a valid ID`, newID)
}

if oldID != "" && !IsNil(oldID) && newID != oldID {
return fmt.Errorf(`The %q attribute must be set to `+
`%q, not %q`, singular, oldID, newID)
Expand Down Expand Up @@ -921,6 +925,10 @@ var OrderedSpecProps = []*Attribute{
return fmt.Errorf(`"versionid" can't be an empty string`)
}

if !IsValidID(newID.(string)) {
return fmt.Errorf(`%q isn't a valid ID`, newID)
}

if oldID != "" && !IsNil(oldID) && newID != oldID {
return fmt.Errorf(`The "versionid" attribute must be `+
`set to %q, not %q`, oldID, newID)
Expand Down Expand Up @@ -2338,6 +2346,16 @@ func (e *Entity) ValidateScalar(val any, attr *Attribute, path *PropPath) error
if i < 0 {
return fmt.Errorf("Attribute %q must be a uinteger", path.UI())
}
case RELATION:
if valKind != reflect.String {
return fmt.Errorf("Attribute %q must be a relation", path.UI())
}
str := val.(string)

err := e.MatchRelation(str, attr.Target)
if err != nil {
return fmt.Errorf("Attribute %q %s", path.UI(), err.Error())
}
case STRING:
if valKind != reflect.String {
return fmt.Errorf("Attribute %q must be a string", path.UI())
Expand Down Expand Up @@ -2369,6 +2387,8 @@ func (e *Entity) ValidateScalar(val any, attr *Attribute, path *PropPath) error
return fmt.Errorf("Attribute %q is a malformed timestamp",
path.UI())
}
default:
panic(fmt.Sprintf("Unknown type: %v", attr.Type))
}

// don't "return nil" above, we may need to check enum values
Expand Down Expand Up @@ -2430,3 +2450,111 @@ func PrepUpdateEntity(e *Entity) error {

return nil
}

// If no match then return an error saying why
func (e *Entity) MatchRelation(str string, relation string) error {
targetParts := strings.Split(relation, "/")
if len(str) == 0 {
return fmt.Errorf("must be an xid, not empty")
}
if str[0] != '/' {
return fmt.Errorf("must be an xid, and start with /")
}
strParts := strings.Split(str, "/")
if len(strParts) < 2 {
return fmt.Errorf("must be a valid xid")
}
if len(strParts[0]) > 0 {
return fmt.Errorf("must be an xid, and start with /")
}
if relation == "/" {
if str != "/" {
return fmt.Errorf("must match %q target", relation)
}
return nil
}
if targetParts[1] != strParts[1] { // works for "" too
return fmt.Errorf("must match %q target", relation)
}

gm := e.Registry.Model.Groups[targetParts[1]]
if gm == nil {
return fmt.Errorf("uses an unknown group %q", targetParts[1])
}
if len(strParts) < 3 || len(strParts[2]) == 0 {
return fmt.Errorf("must match %q target, missing \"%sid\"",
relation, gm.Singular)
}
if !IsValidID(strParts[2]) {
return fmt.Errorf("must match %q target, %q isn't a valid ID",
relation, strParts[2])
}

if len(targetParts) == 2 { // ""/GROUPS
if len(strParts) == 3 {
return nil
}
return fmt.Errorf("must match %q target, extra stuff after %q",
relation, strParts[2])
}

// len targetParts >= 3
if len(strParts) < 4 { // /GROUPS/gID/RESOURCES
return fmt.Errorf("must match %q target, missing %q",
relation, targetParts[2])
}

if targetParts[2] != strParts[3] {
return fmt.Errorf("must match %q target, missing %q",
relation, targetParts[2])
}

rm := gm.Resources[targetParts[2]]
if rm == nil {
return fmt.Errorf("uses an unknown resource %q", targetParts[2])
}

if len(strParts) < 5 || len(strParts[4]) == 0 {
return fmt.Errorf("must match %q target, missing \"%sid\"",
relation, rm.Singular)
}
if !IsValidID(strParts[4]) {
return fmt.Errorf("must match %q target, %q isn't a valid ID",
relation, strParts[4])
}
if len(targetParts) == 3 {
if len(strParts) == 5 {
return nil
}
return fmt.Errorf("must match %q target, extra stuff after %q",
relation, strParts[4])

}

if targetParts[3] == "versions?" {
if len(strParts) == 5 {
// /GROUPS/RESOURCES/version? vs /GROUPS/gID/RESOURCES/rID
return nil
}
}

if len(strParts) < 6 || strParts[5] != "versions" {
return fmt.Errorf("must match %q target, missing \"versions\"",
relation)
}

if len(strParts) < 7 || len(strParts[6]) == 0 {
return fmt.Errorf("must match %q target, missing a \"versionid\"",
relation)
}
if !IsValidID(strParts[6]) {
return fmt.Errorf("must match %q target, %q isn't a valid ID",
relation, strParts[6])
}

if len(strParts) > 7 {
return fmt.Errorf("must match %q target, too long", relation)
}

return nil
}
Loading

0 comments on commit 40d13de

Please sign in to comment.