corso/src/pkg/path/resource_path.go
ryanfkeepers 7c242819bc Add 'halves()' to path interface
Adds a func to the path interface which breaks up the path
into its two distinct halves: the prefix builder, and the folder
and item elements.  Likely to be useful in the future where
prefixes will be of arbitrary length, and downstream consumers
are primarly focused on the non-prefix portion of the path.
2023-08-11 11:14:27 -06:00

173 lines
4.7 KiB
Go

package path
import (
"github.com/alcionai/clues"
)
// dataLayerResourcePath allows callers to extract information from a
// resource-specific path. This struct is unexported so that callers are
// forced to use the pre-defined constructors, making it impossible to create a
// dataLayerResourcePath with invalid service/category combinations.
//
// All dataLayerResourcePaths start with the same prefix:
// <tenant ID>/<service>/<resource owner ID>/<category>
// which allows extracting high-level information from the path. The path
// elements after this prefix represent zero or more folders and, if the path
// refers to a file or item, an item ID. A valid dataLayerResourcePath must have
// at least one folder or an item so that the resulting path has at least one
// element after the prefix.
type dataLayerResourcePath struct {
Builder
category CategoryType
serviceResources []ServiceResource
hasItem bool
}
// performs no validation, assumes the caller has validated the inputs.
func newDataLayerResourcePath(
pb Builder,
tenant string,
srs []ServiceResource,
cat CategoryType,
isItem bool,
) dataLayerResourcePath {
pfx := append([]string{tenant}, ServiceResourcesToElements(srs)...)
pfx = append(pfx, cat.String())
return dataLayerResourcePath{
Builder: *pb.withPrefix(pfx...),
serviceResources: srs,
category: cat,
hasItem: isItem,
}
}
// Tenant returns the tenant ID embedded in the dataLayerResourcePath.
func (rp dataLayerResourcePath) Tenant() string {
return rp.Builder.elements[0]
}
func (rp dataLayerResourcePath) ServiceResources() []ServiceResource {
return rp.serviceResources
}
// Category returns the CategoryType embedded in the dataLayerResourcePath.
func (rp dataLayerResourcePath) Category() CategoryType {
return rp.category
}
// ResourceOwner returns the user ID or group ID embedded in the
// dataLayerResourcePath.
func (rp dataLayerResourcePath) ResourceOwner() string {
return rp.Builder.elements[2]
}
func (rp dataLayerResourcePath) lastFolderIdx() int {
endIdx := len(rp.Builder.elements)
if rp.hasItem {
endIdx--
}
return endIdx
}
// Folder returns the folder segment embedded in the dataLayerResourcePath.
func (rp dataLayerResourcePath) Folder(escape bool) string {
endIdx := rp.lastFolderIdx()
if endIdx == 4 {
return ""
}
fs := rp.Folders()
if !escape {
return join(fs)
}
// builder.String() will escape all individual elements.
return Builder{}.Append(fs...).String()
}
// Folders returns the individual folder elements embedded in the
// dataLayerResourcePath.
func (rp dataLayerResourcePath) Folders() Elements {
endIdx := rp.lastFolderIdx()
if endIdx == 4 {
return nil
}
return append([]string{}, rp.elements[4:endIdx]...)
}
// Item returns the item embedded in the dataLayerResourcePath if the path
// refers to an item.
func (rp dataLayerResourcePath) Item() string {
if rp.hasItem {
return rp.Builder.elements[len(rp.Builder.elements)-1]
}
return ""
}
// Dir removes the last element from the path. If this would remove a
// value that is part of the standard prefix structure, an error is returned.
func (rp dataLayerResourcePath) Dir() (Path, error) {
// Dir is not allowed to slice off any prefix values.
// The prefix len is determined by the length of the number of
// service+resource tuples, plus 2 (tenant and category).
if len(rp.elements) <= 2+(2*len(rp.serviceResources)) {
return nil, clues.New("unable to shorten path").With("path", rp)
}
return &dataLayerResourcePath{
Builder: *rp.Builder.Dir(),
serviceResources: rp.serviceResources,
category: rp.category,
hasItem: false,
}, nil
}
func (rp dataLayerResourcePath) Append(
isItem bool,
elems ...string,
) (Path, error) {
if rp.hasItem {
return nil, clues.New("appending to an item path")
}
return &dataLayerResourcePath{
Builder: *rp.Builder.Append(elems...),
serviceResources: rp.serviceResources,
category: rp.category,
hasItem: isItem,
}, nil
}
func (rp dataLayerResourcePath) AppendItem(item string) (Path, error) {
return rp.Append(true, item)
}
func (rp dataLayerResourcePath) ToBuilder() *Builder {
// Safe to directly return the Builder because Builders are immutable.
return &rp.Builder
}
func (rp *dataLayerResourcePath) UpdateParent(prev, cur Path) bool {
return rp.Builder.UpdateParent(prev.ToBuilder(), cur.ToBuilder())
}
func (rp *dataLayerResourcePath) Halves() (*Builder, Elements) {
pfx, sfx := &Builder{}, Elements{}
b := rp.Builder
if len(b.elements) > 0 {
lenPfx := 2 + (len(rp.serviceResources) * 2)
pfx = &Builder{elements: append(Elements{}, b.elements[:lenPfx]...)}
sfx = append(Elements{}, b.elements[lenPfx-1:]...)
}
return pfx, sfx
}