api.spaceplanner.app

Spaceplanner API
git clone git://jacobedwards.org/api.spaceplanner.app
Log | Files | Refs

commit 13ee18be0f4174a354658d0d936ee74ab16e390f
parent 38e62fcf004fd95853159418bd83892bbe9d27f2
Author: Jacob R. Edwards <jacob@jacobedwards.org>
Date:   Sun,  8 Sep 2024 15:06:06 -0700

Switch to system-wide unique string IDs

It was making things difficult to have IDs that were only unique
to each table, so now IDs are strings of the sequence number with
a type prepended to them. The database representation is the same,
they're simply converted from the string representation going in
and from the sequence number when coming out.

Also, the ref structs and functions were removed/re-labeled to id
functions.

Diffstat:
Minternal/backend/floorplan_data.go | 429++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
1 file changed, 261 insertions(+), 168 deletions(-)

diff --git a/internal/backend/floorplan_data.go b/internal/backend/floorplan_data.go @@ -12,14 +12,14 @@ import ( import "log" type FloorplanData struct { - Points map[int64]Point `json:"points"` - Pointmaps map[int64]PointMap `json:"pointmaps"` + Points map[ObjectID]Point `json:"points"` + Pointmaps map[ObjectID]PointMap `json:"pointmaps"` // I would like to have this in a FurnitureData struct, // but don't want to rework the client to accept that // at the moment. - Furniture map[int64]Furniture `json:"furniture"` - FurnitureMaps map[int64]FurnitureMap `json:"furniture_maps"` + Furniture map[ObjectID]Furniture `json:"furniture"` + FurnitureMaps map[ObjectID]FurnitureMap `json:"furniture_maps"` } /* @@ -29,26 +29,26 @@ type FloorplanData struct { * individual objects with JSON Patch. */ type Point struct { - id int64 `json:"id"` - OldID *int64 `json:"old_id,omitempty"` + id ObjectID `json:"id"` + OldID *ObjectID `json:"old_id,omitempty"` X int `json:"x" binding:"required"` Y int `json:"y" binding:"required"` } type PointMap struct { - id int64 - OldID *int64 `json:"old_id,omitempty"` + id ObjectID + OldID *ObjectID `json:"old_id,omitempty"` Type string `json:"type" binding:"required"` - A int64 `json:"a" binding:"required"` - B int64 `json:"b" binding:"required"` + A ObjectID `json:"a" binding:"required"` + B ObjectID `json:"b" binding:"required"` } // NOTE: I'd like to allow every value to be omitted so // long as it doesn't violate database rules, but without // setting to null. type Furniture struct { - id int64 - OldID *int64 `json:"old_id,omitempty"` + id ObjectID + OldID *ObjectID `json:"old_id,omitempty"` Type string `json:"type" binding:"required"` Name *string `json:"name"` Width int `json:"width" binding:"required"` @@ -56,31 +56,25 @@ type Furniture struct { } type FurnitureMap struct { - id int64 - OldID *int64 `json:"old_id,omitempty"` - FurnitureID int64 `json:"furniture_id" binding:"required"` + id ObjectID + OldID *ObjectID `json:"old_id,omitempty"` + FurnitureID ObjectID `json:"furniture_id" binding:"required"` Layout string `json:"layout" binding:"required"` X int `json:"x" binding:"required"` Y int `json:"y" binding:"required"` Angle int `json:"angle" binding:"required"` } -type rowReference struct { - table string - id int64 -} - type PatchError struct { - ref *rowReference - rawref string - msg string - extra error + ID *ObjectID + Context string + Reason error } type DBObject interface { - Ref() rowReference - updateRefs(rowReference, map[rowReference]rowReference) DBObject - SetOldID(int64) DBObject + ID() ObjectID + updateIDs(ObjectID, map[ObjectID]ObjectID) DBObject + SetOldID(ObjectID) DBObject /* * NOTE: these all take user and floorplan arguments solely for authorization, but @@ -91,6 +85,11 @@ type DBObject interface { Delete(e *Env, tx *sql.Tx, user, floorplan string) (DBObject, error) } +type ObjectID struct { + Type string + Seq int64 +} + type FurnitureType struct { Varieties map[string]FurnitureVariety `json:"varieties,omit_empty"` } @@ -104,6 +103,13 @@ type Mappable interface { Key() any } +var ( + IDTypePoint = "pnt" + IDTypePointMap = "pntmap" + IDTypeFurniture = "fur" + IDTypeFurnitureMap = "furmap" +) + func (e *Env) FurnitureTypes(tx *sql.Tx) (map[string]FurnitureType, error) { types, err := e.CacheTxStmt(tx, "furn_types", `SELECT name from spaceplanner.furniture_types`) @@ -189,7 +195,7 @@ func (e *Env) GetFloorplanData(tx *sql.Tx, user string, floorplan string) (Floor return data, nil } -func (e *Env) getFloorplanPoints(tx *sql.Tx, user string, floorplan string) (map[int64]Point, error) { +func (e *Env) getFloorplanPoints(tx *sql.Tx, user string, floorplan string) (map[ObjectID]Point, error) { stmt, err := e.CacheTxStmt(tx, "get_points", `SELECT id, x, y FROM spaceplanner.floorplan_points WHERE floorplan = spaceplanner.floorplan_id($1, $2)`) @@ -210,7 +216,7 @@ func (e *Env) getFloorplanPoints(tx *sql.Tx, user string, floorplan string) (map return mapArray(points, mapPoint) } -func (e *Env) getFloorplanPointMaps(tx *sql.Tx, user string, floorplan string) (map[int64]PointMap, error) { +func (e *Env) getFloorplanPointMaps(tx *sql.Tx, user string, floorplan string) (map[ObjectID]PointMap, error) { stmt, err := e.CacheTxStmt(tx, "get_pointmaps", `SELECT id, type, a, b FROM spaceplanner.floorplan_pointmaps WHERE floorplan = spaceplanner.floorplan_id($1, $2)`) @@ -230,7 +236,7 @@ func (e *Env) getFloorplanPointMaps(tx *sql.Tx, user string, floorplan string) ( return mapArray(pointmaps, mapPointMap) } -func (e *Env) getFloorplanFurnitureDefs(tx *sql.Tx, user string, floorplan string) (map[int64]Furniture, error) { +func (e *Env) getFloorplanFurnitureDefs(tx *sql.Tx, user string, floorplan string) (map[ObjectID]Furniture, error) { defsStmt, err := e.CacheTxStmt(tx, "furniture_defs", `SELECT id, type, name, width, depth FROM spaceplanner.furniture @@ -254,7 +260,7 @@ func (e *Env) getFloorplanFurnitureDefs(tx *sql.Tx, user string, floorplan strin } -func (e *Env) getFloorplanFurnitureMaps(tx *sql.Tx, user string, floorplan string) (map[int64]FurnitureMap, error) { +func (e *Env) getFloorplanFurnitureMaps(tx *sql.Tx, user string, floorplan string) (map[ObjectID]FurnitureMap, error) { mapsStmt, err := e.CacheTxStmt(tx, "furniture_maps", `SELECT id, furniture_id, layout, x, y, angle FROM spaceplanner.furniture_maps @@ -300,7 +306,7 @@ func (e *Env) ReplaceFloorplanData(tx *sql.Tx, user string, floorplan string, da for id, point := range data.Points { patch := Patch{ Op: "new", - Path: newRef("points", id).String(), + Path: id.Path(), Value: point, } patches = append(patches, patch) @@ -308,7 +314,7 @@ func (e *Env) ReplaceFloorplanData(tx *sql.Tx, user string, floorplan string, da for id, pointmap := range data.Pointmaps { patch := Patch{ Op: "new", - Path: newRef("pointmaps", id).String(), + Path: id.Path(), Value: pointmap, } patches = append(patches, patch) @@ -350,93 +356,92 @@ func (e *Env) PatchFloorplanData(tx *sql.Tx, user string, floorplan string, patc mytx = true } - newRefs := make(map[rowReference]rowReference) + newIDs := make(map[ObjectID]ObjectID) data := FloorplanData{} - data.Points = make(map[int64]Point) - data.Pointmaps = make(map[int64]PointMap) - data.Furniture = make(map[int64]Furniture) - data.FurnitureMaps = make(map[int64]FurnitureMap) + data.Points = make(map[ObjectID]Point) + data.Pointmaps = make(map[ObjectID]PointMap) + data.Furniture = make(map[ObjectID]Furniture) + data.FurnitureMaps = make(map[ObjectID]FurnitureMap) // Allowed operations are new, replace, and delete // (new is an extention that ensures path's don't exist before creation) for _, patch := range patches { - ref, err := parseRowReference(patch.Path) + id, err := parsePath(patch.Path) if err != nil { - return data, PatchError{}.New(nil, &patch.Path, "Invalid path", err) + return data, patchError(nil, patch.Path, err) } if (patch.Op == "new" || patch.Op == "replace") && patch.Value == nil { - return data, ref.Error("Requires value", nil) + return data, id.Error("Requires value", nil) } - if (ref.table == "points") { - point, err := applyPatch[Point](e, tx, user, floorplan, &patch, ref, newRefs) + if (id.Type == IDTypePoint) { + point, err := applyPatch[Point](e, tx, user, floorplan, &patch, id, newIDs) if err != nil { - return data, ref.Error("", err) + return data, id.Error("", err) } if patch.Op != "delete" { data.Points[point.id] = point } - } else if (ref.table == "pointmaps") { - pointmap, err := applyPatch[PointMap](e, tx, user, floorplan, &patch, ref, newRefs) + } else if (id.Type == IDTypePointMap) { + pointmap, err := applyPatch[PointMap](e, tx, user, floorplan, &patch, id, newIDs) if err != nil { - return data, ref.Error("", err) + return data, id.Error("", err) } if patch.Op != "delete" { data.Pointmaps[pointmap.id] = pointmap } - } else if (ref.table == "furniture") { - def, err := applyPatch[Furniture](e, tx, user, floorplan, &patch, ref, newRefs) + } else if (id.Type == IDTypeFurniture) { + def, err := applyPatch[Furniture](e, tx, user, floorplan, &patch, id, newIDs) if err != nil { - return data, ref.Error("", err) + return data, id.Error("", err) } if patch.Op != "delete" { data.Furniture[def.id] = def } - } else if (ref.table == "furniture_maps") { - fm, err := applyPatch[FurnitureMap](e, tx, user, floorplan, &patch, ref, newRefs) + } else if (id.Type == IDTypeFurnitureMap) { + fm, err := applyPatch[FurnitureMap](e, tx, user, floorplan, &patch, id, newIDs) if err != nil { - return data, ref.Error("", err) + return data, id.Error("", err) } if patch.Op != "delete" { data.FurnitureMaps[fm.id] = fm } } else { - return data, ref.Error("Path does not exist", nil) + return data, id.Error("Path does not exist", nil) } } if (mytx) { err = tx.Commit() if err != nil { - return data, PatchError{}.New(nil, nil, "Unable to commit patch", nil) + return data, patchError(nil, "Unable to commit patch", err) } } return data, nil } -func applyPatch[T DBObject](e *Env, tx *sql.Tx, user, floorplan string, patch *Patch, ref rowReference, - newRefs map[rowReference]rowReference) (T, error) { - inputRef := ref - - ref, exists := newRefs[inputRef] +func applyPatch[T DBObject](e *Env, tx *sql.Tx, user, floorplan string, patch *Patch, id ObjectID, + newIDs map[ObjectID]ObjectID) (T, error) { + inputID := id + id, exists := newIDs[inputID] if !exists { - ref = inputRef + id = inputID } var thing T if err := remarshal(patch.Value, &thing); err != nil { - return thing, inputRef.Error("Invalid object", err) + return thing, inputID.Error("Invalid object", err) } - // Could do with adding a ref.table check + // Could do with adding a id.Type check // Tried to figure out the correct solution to this, but // can't figure it out right now. I'll come back to it later // but for now a few type assertions - thing = thing.updateRefs(ref, newRefs).(T) + thing = thing.updateIDs(id, newIDs).(T) log.Print(patch, thing) var err error @@ -449,21 +454,21 @@ func applyPatch[T DBObject](e *Env, tx *sql.Tx, user, floorplan string, patch *P case "remove": dbo, err = thing.Delete(e, tx, user, floorplan) default: - return thing, inputRef.Error("Unsupported operation", nil) + return thing, inputID.Error("Unsupported operation", nil) } if err != nil { - return thing, inputRef.Error("Unable to perform " + patch.Op + " operation", err) + return thing, inputID.Error("Unable to perform " + patch.Op + " operation", err) } thing = dbo.(T) - ref = thing.Ref() - if inputRef == ref { + id = thing.ID() + if inputID == id { return thing, nil } - newRefs[inputRef] = ref - return thing.SetOldID(inputRef.id).(T), nil + newIDs[inputID] = id + return thing.SetOldID(inputID).(T), nil } func remarshal[T any](value interface{}, result *T) (error) { @@ -496,14 +501,14 @@ func (p Point) Update(e *Env, tx *sql.Tx, user string, floorplan string) (DBObje return Point{}, err } - return scanPoint(stmt.QueryRow(user, floorplan, p.id, p.X, p.Y)) + return scanPoint(stmt.QueryRow(user, floorplan, p.id.Seq, p.X, p.Y)) } func (p Point) Delete(e *Env, tx *sql.Tx, user string, floorplan string) (DBObject, error) { return e.DeletePoint(tx, user, floorplan, p.id) } -func (e *Env) DeletePoint(tx *sql.Tx, user string, floorplan string, id int64) (Point, error) { +func (e *Env) DeletePoint(tx *sql.Tx, user string, floorplan string, id ObjectID) (Point, error) { stmt, err := e.CacheTxStmt(tx, "dele_point", `DELETE FROM spaceplanner.floorplan_points WHERE floorplan = floorplan_id($1, $2) AND id = $3 RETURNING id, x, y`) @@ -511,7 +516,7 @@ func (e *Env) DeletePoint(tx *sql.Tx, user string, floorplan string, id int64) ( return Point{}, err } - return scanPoint(stmt.QueryRow(user, floorplan, id)) + return scanPoint(stmt.QueryRow(user, floorplan, id.Seq)) } func (pm PointMap) Create(e *Env, tx *sql.Tx, user string, floorplan string) (DBObject, error) { @@ -535,14 +540,14 @@ func (pm PointMap) Update(e *Env, tx *sql.Tx, user string, floorplan string) (DB return PointMap{}, err } - return scanPointMap(stmt.QueryRow(user, floorplan, pm.id, pm.Type, pm.A, pm.B)) + return scanPointMap(stmt.QueryRow(user, floorplan, pm.id.Seq, pm.Type, pm.A.Seq, pm.B.Seq)) } func (pm PointMap) Delete(e *Env, tx *sql.Tx, user, floorplan string) (DBObject, error) { return e.DeletePointMap(tx, user, floorplan, pm.id) } -func (e *Env) DeletePointMap(tx *sql.Tx, user string, floorplan string, id int64) (PointMap, error) { +func (e *Env) DeletePointMap(tx *sql.Tx, user string, floorplan string, id ObjectID) (PointMap, error) { stmt, err := e.CacheTxStmt(tx, "dele_pointmap", `DELETE FROM spaceplanner.floorplan_pointmaps WHERE floorplan = floorplan_id($1, $2) AND id = $3 @@ -551,7 +556,7 @@ func (e *Env) DeletePointMap(tx *sql.Tx, user string, floorplan string, id int64 return PointMap{}, err } - return scanPointMap(stmt.QueryRow(user, floorplan, id)) + return scanPointMap(stmt.QueryRow(user, floorplan, id.Seq)) } func (f Furniture) Create(e *Env, tx *sql.Tx, user, floorplan string) (DBObject, error) { @@ -575,7 +580,7 @@ func (f Furniture) Update(e *Env, tx *sql.Tx, user, floorplan string) (DBObject, return f, err } - return scanFurniture(update.QueryRow(user, floorplan, f.id, f.Type, f.Name, f.Width, f.Depth)) + return scanFurniture(update.QueryRow(user, floorplan, f.id.Seq, f.Type, f.Name, f.Width, f.Depth)) } func (f Furniture) Delete(e *Env, tx *sql.Tx, user, floorplan string) (DBObject, error) { @@ -587,7 +592,7 @@ func (f Furniture) Delete(e *Env, tx *sql.Tx, user, floorplan string) (DBObject, return f, err } - return scanFurniture(del.QueryRow(user, floorplan, f.id)) + return scanFurniture(del.QueryRow(user, floorplan, f.id.Seq)) } func (f FurnitureMap) Create(e *Env, tx *sql.Tx, user, floorplan string) (DBObject, error) { @@ -611,7 +616,7 @@ func (fm FurnitureMap) Update(e *Env, tx *sql.Tx, user, floorplan string) (DBObj return fm, err } - return scanFurnitureMap(update.QueryRow(user, floorplan, fm.id, fm.Layout, fm.X, fm.Y, fm.Angle)) + return scanFurnitureMap(update.QueryRow(user, floorplan, fm.id.Seq, fm.FurnitureID.Seq, fm.Layout, fm.X, fm.Y, fm.Angle)) } func (f FurnitureMap) Delete(e *Env, tx *sql.Tx, user, floorplan string) (DBObject, error) { @@ -623,149 +628,210 @@ func (f FurnitureMap) Delete(e *Env, tx *sql.Tx, user, floorplan string) (DBObje return f, err } - log.Printf("Delete map %d from %s/%s", f.id, user,floorplan) - return scanFurnitureMap(del.QueryRow(user, floorplan, f.id)) + return scanFurnitureMap(del.QueryRow(user, floorplan, f.id.Seq)) } -func (f Furniture) Ref() rowReference { - return newRef("furniture", f.id) +func (f Furniture) ID() ObjectID { + return f.id } -func (f Furniture) updateRefs(ref rowReference, newRefs map[rowReference]rowReference) DBObject { - if n, exists := newRefs[ref]; exists { - f.id = n.id +func (f Furniture) updateIDs(id ObjectID, newIDs map[ObjectID]ObjectID) DBObject { + if n, exists := newIDs[id]; exists { + f.id = n } else { - f.id = ref.id + f.id = id } return f } -func (f Furniture) SetOldID(id int64) DBObject { +func (f Furniture) SetOldID(id ObjectID) DBObject { if id != f.id { f.OldID = &id } return f } -func (fm FurnitureMap) Ref() rowReference { - return newRef("furniture_maps", fm.id) +func (fm FurnitureMap) ID() ObjectID { + return fm.id } -func (fm FurnitureMap) updateRefs(ref rowReference, newRefs map[rowReference]rowReference) DBObject { - if n, exists := newRefs[ref]; exists { - fm.id = n.id +func (fm FurnitureMap) updateIDs(id ObjectID, newIDs map[ObjectID]ObjectID) DBObject { + if n, exists := newIDs[id]; exists { + fm.id = n } else { - fm.id = ref.id + fm.id = id } - if n, exists := newRefs[newRef("furniture", fm.FurnitureID)]; exists { - log.Printf("Furniture Map Furniture ID remapped from %d to %d", fm.id, n.id) - fm.FurnitureID = n.id + if n, exists := newIDs[fm.FurnitureID]; exists { + fm.FurnitureID = n } return fm } -func (fm FurnitureMap) SetOldID(id int64) DBObject { +func (fm FurnitureMap) SetOldID(id ObjectID) DBObject { if id != fm.id { fm.OldID = &id } return fm } -func (p Point) Ref() rowReference { - return newRef("points", p.id) +func (p Point) ID() ObjectID { + return p.id } -func (p Point) updateRefs(ref rowReference, newRefs map[rowReference]rowReference) DBObject { - if n, exists := newRefs[ref]; exists { - p.id = n.id +func (p Point) updateIDs(id ObjectID, newIDs map[ObjectID]ObjectID) DBObject { + if n, exists := newIDs[id]; exists { + p.id = n } else { - p.id = ref.id + p.id = id } return p } -func (p Point) SetOldID(id int64) DBObject { +func (p Point) SetOldID(id ObjectID) DBObject { if id != p.id { p.OldID = &id } return p } -func (m PointMap) Ref() rowReference { - return newRef("pointmaps", m.id) +func (m PointMap) ID() ObjectID { + return m.id } -func (m PointMap) updateRefs(ref rowReference, newRefs map[rowReference]rowReference) DBObject { - if n, exists := newRefs[ref]; exists { - m.id = n.id +func (m PointMap) updateIDs(id ObjectID, newIDs map[ObjectID]ObjectID) DBObject { + if n, exists := newIDs[id]; exists { + m.id = n } else { - m.id = ref.id + m.id = id } - if n, exists := newRefs[newRef("points", m.A)]; exists { - m.A = n.id + if n, exists := newIDs[m.A]; exists { + m.A = n } - if n, exists := newRefs[newRef("points", m.B)]; exists { - m.B = n.id + if n, exists := newIDs[m.B]; exists { + m.B = n } return m } -func (m PointMap) SetOldID(id int64) DBObject { +func (m PointMap) SetOldID(id ObjectID) DBObject { if id != m.id { m.OldID = &id } return m } -func newRef(table string, id int64) rowReference { - return rowReference{table: table, id: id} -} - -func parseRowReference(ref string) (rowReference, error) { - // Ref should look like this: /type/id so three - // segments (including before initial slash) - parts := strings.Split(ref, "/") - if len(parts) < 3 { - return rowReference{}, errors.New(ref + ": Requires table") - } +/* + * Parse patch path's of the form + * /table/id + */ +func parsePath(p string) (ObjectID, error) { + parts := strings.Split(p, "/") if parts[0] != "" { - return rowReference{}, errors.New(ref + ": Must start with /") + return ObjectID{}, errors.New(p + ": Requires absolute path (start with /)") + } + if len(parts) < 3 { + return ObjectID{}, errors.New(p + ": Object does not exist") } - id, err := strconv.ParseInt(parts[len(parts) - 1], 10, 64) + id, err := parseID(parts[len(parts) - 1]) if err != nil { - return rowReference{}, errors.New(ref + ": Invalid id") + return ObjectID{}, err } + parts = parts[1:len(parts) - 1] - return newRef(strings.Join(parts, "/"), id), nil + typ, err := pathIDType(strings.Join(parts, "/")) + if err != nil { + return ObjectID{}, err + } + + if typ != id.Type { + return ObjectID{}, errors.New("Invalid ID for that path") + } + return id, nil +} + +func parseID(s string) (ObjectID, error) { + a := strings.Split(s, "_") + if len(a) != 2 { + return ObjectID{}, errors.New(s + ": Invalid ID (too many underscores)") + } + + seq, err := strconv.ParseInt(a[1], 10, 64) + if err != nil { + return ObjectID{}, errors.New(s + ": Invalid ID (invalid sequence number)") + } + + if (a[0] != IDTypePoint && a[0] != IDTypePointMap && + a[0] != IDTypeFurniture && a[0] != IDTypeFurnitureMap) { + return ObjectID{}, errors.New("Invalid ID type") + } + return makeID(a[0], seq), nil +} + +func pathIDType(p string) (string, error) { + switch p { + case "points": + return IDTypePoint, nil + case "pointmaps": + return IDTypePointMap, nil + case "furniture": + return IDTypeFurniture, nil + case "furniture_maps": + return IDTypeFurnitureMap, nil + } + return "", errors.New(p + ": Invalid path prefix") } func scanPoint(s Scanner) (Point, error) { var p Point + var id int64 - err := s.Scan(&p.id, &p.X, &p.Y) - return p, err + err := s.Scan(&id, &p.X, &p.Y) + if err != nil { + return p, err + } + p.id = makeID(IDTypePoint, id) + return p, nil } func scanPointMap(s Scanner) (PointMap, error) { var pm PointMap + var id, a, b int64 - err := s.Scan(&pm.id, &pm.Type, &pm.A, &pm.B) - return pm, err + err := s.Scan(&id, &pm.Type, &a, &b) + if err != nil { + return pm, err + } + pm.id = makeID(IDTypePointMap, id) + pm.A = makeID(IDTypePoint, a) + pm.B = makeID(IDTypePoint, b) + return pm, nil } func scanFurniture(row Scanner) (Furniture, error) { var f Furniture + var id int64 - err := row.Scan(&f.id, &f.Type, &f.Name, &f.Width, &f.Depth) - return f, err + err := row.Scan(&id, &f.Type, &f.Name, &f.Width, &f.Depth) + if err != nil { + return f, err + } + f.id = makeID(IDTypeFurniture, id) + return f, nil } func scanFurnitureMap(row Scanner) (FurnitureMap, error) { var m FurnitureMap + var id int64 + var fid int64 - err := row.Scan(&m.id, &m.FurnitureID, &m.Layout, &m.X, &m.Y, &m.Angle) - return m, err + err := row.Scan(&id, &fid, &m.Layout, &m.X, &m.Y, &m.Angle) + if err != nil { + return m, err + } + m.id = makeID(IDTypeFurnitureMap, id) + m.FurnitureID = makeID(IDTypeFurniture, fid) + return m, nil } func mapArray[K comparable, V any](array []V, mapper func(V) (K, error)) (map[K]V, error) { @@ -781,58 +847,85 @@ func mapArray[K comparable, V any](array []V, mapper func(V) (K, error)) (map[K] return m, nil } -func mapPoint(p Point) (int64, error) { +func mapPoint(p Point) (ObjectID, error) { return p.id, nil } -func mapPointMap(pm PointMap) (int64, error) { +func mapPointMap(pm PointMap) (ObjectID, error) { return pm.id, nil } -func mapFurniture(f Furniture) (int64, error) { +func mapFurniture(f Furniture) (ObjectID, error) { return f.id, nil } -func mapFurnitureMap(m FurnitureMap) (int64, error) { +func mapFurnitureMap(m FurnitureMap) (ObjectID, error) { return m.id, nil } -func (ref rowReference) String() string { - return fmt.Sprintf("/%s/%d", ref.table, ref.id) +func makeID(typ string, seq int64) ObjectID { + return ObjectID{ + Type: typ, + Seq: seq, + } } -func (ref rowReference) Error(msg string, extra error) PatchError { - return PatchError{}.New(&ref, nil, msg, extra) +func (o ObjectID) String() string { + return fmt.Sprintf("%s_%d", o.Type, o.Seq) } -func (e PatchError) New(ref *rowReference, rawref *string, msg string, extra error) PatchError { - if ref != nil { - copy := *ref - e.ref = &copy +func (o ObjectID) Path() string { + return fmt.Sprintf("/%s/%d", o.Type, o.Seq) +} + +func (id ObjectID) MarshalJSON() ([]byte, error) { + return json.Marshal(id.String()) +} + +func (id *ObjectID) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err } - if rawref != nil { - e.rawref = *rawref + d, err := parseID(s) + *id = d + return err +} + +func (id ObjectID) MarshalText() ([]byte, error) { + return []byte(id.String()), nil +} + +func (id *ObjectID) UnmarshalText(b []byte) error { + d, err := parseID(string(b)) + *id = d + return err +} + +func (id ObjectID) Error(context string, reason error) PatchError { + return patchError(&id, context, reason) +} + +func patchError(id *ObjectID, context string, reason error) PatchError { + if id != nil { + // Copy + d := *id + id = &d } - e.msg = msg - e.extra = extra - return e; + return PatchError{ID: id, Context: context, Reason: reason} } func (e PatchError) Error() string { - var err string + var s string - // Assume msg or extra will be defined (they should be) - if e.ref != nil { - err = fmt.Sprintf("/%s/%d: ", e.ref.table, e.ref.id) - } else if len(e.rawref) > 0 { - err = fmt.Sprintf("%s: ", e.rawref) + if e.ID != nil { + s = fmt.Sprintf("%s", e.ID) } - - if len(e.msg) > 0 { - err = fmt.Sprintf("%s: %s", err, e.msg) + if len(e.Context) > 0 { + s = fmt.Sprintf("%s: %s", s, e.Context) } - if e.extra != nil { - err = fmt.Sprintf("%s: %s", err, e.extra.Error()) + if e.Reason != nil { + s = fmt.Sprintf("%s: %s", s, e.Reason.Error()) } - return err + return s }