From da8ff3c267e5ff2ac903646be41e70a5ffe066ec Mon Sep 17 00:00:00 2001 From: ashmrtn <3891298+ashmrtn@users.noreply.github.com> Date: Fri, 22 Jul 2022 10:25:55 -0700 Subject: [PATCH] Define struct and interface for corso paths (#339) * Define struct and interface for corso paths * Add wrapper for ExchangeMail as an example --- src/internal/path/exchange_path.go | 104 +++++++++++++++++++++++++++++ src/internal/path/path.go | 98 +++++++++++++++++++++++++++ 2 files changed, 202 insertions(+) create mode 100644 src/internal/path/exchange_path.go create mode 100644 src/internal/path/path.go diff --git a/src/internal/path/exchange_path.go b/src/internal/path/exchange_path.go new file mode 100644 index 000000000..70ee12c6c --- /dev/null +++ b/src/internal/path/exchange_path.go @@ -0,0 +1,104 @@ +package path + +import ( + "strings" + + "github.com/pkg/errors" +) + +const ( + emailCategory = "email" +) + +var _ Path = &ExchangeMail{} + +type ExchangeMail struct { + Base +} + +// NewExchangeEmailPath creates and returns a new ExchangeEmailPath struct after +// verifying the path is properly escaped and contains information for the +// required segments. +func NewExchangeMail( + tenant string, + user string, + folder []string, + item string, +) (*ExchangeMail, error) { + tmpFolder := strings.Join(folder, "") + if err := validateExchangeMailSegments(tenant, user, tmpFolder, item); err != nil { + return nil, err + } + + p := newPath([][]string{ + {tenant}, + {emailCategory}, + {user}, + folder, + {item}, + }) + + return &ExchangeMail{p}, nil +} + +func NewExchangeMailFromEscapedSegments(tenant, user, folder, item string) (*ExchangeMail, error) { + if err := validateExchangeMailSegments(tenant, user, folder, item); err != nil { + return nil, err + } + + p, err := newPathFromEscapedSegments([]string{tenant, emailCategory, user, folder, item}) + if err != nil { + return nil, err + } + + return &ExchangeMail{p}, nil +} + +func validateExchangeMailSegments(tenant, user, folder, item string) error { + if len(tenant) == 0 { + return errors.Wrap(errMissingSegment, "tenant") + } + + if len(user) == 0 { + return errors.Wrap(errMissingSegment, "user") + } + + if len(folder) == 0 { + return errors.Wrap(errMissingSegment, "mail folder") + } + + if len(item) == 0 { + return errors.Wrap(errMissingSegment, "mail item") + } + + return nil +} + +// Tenant returns the tenant ID for the referenced email resource. +func (emp ExchangeMail) Tenant() string { + return emp.segment(0) +} + +// Cateory returns an identifier noting this is a path for an email resource. +func (emp ExchangeMail) Category() string { + return emp.segment(1) +} + +// User returns the user ID for the referenced email resource. +func (emp ExchangeMail) User() string { + return emp.segment(2) +} + +// Folder returns the folder segment for the referenced email resource. +func (emp ExchangeMail) Folder() string { + return emp.segment(3) +} + +func (emp ExchangeMail) FolderElements() []string { + return emp.unescapedSegmentElements(3) +} + +// Mail returns the email ID for the referenced email resource. +func (emp ExchangeMail) Item() string { + return emp.segment(4) +} diff --git a/src/internal/path/path.go b/src/internal/path/path.go new file mode 100644 index 000000000..277b57c5b --- /dev/null +++ b/src/internal/path/path.go @@ -0,0 +1,98 @@ +// Package path provides a set of functions for wrangling paths from the outside +// world into paths that corso can understand. Paths use the standard Unix path +// separator character '/'. If for some reason an individual element in a raw +// path contains the '/' character, it should be escaped with '\'. If the path +// contains '\' it should be escaped by turning it into '\\'. +// +// Paths can be split into elements by splitting on '/' if the '/' is not +// escaped. Additionally, corso may operate on segments in a path. Segments are +// made up of one or more path elements. +// +// Examples of paths splitting by elements and canonicalization with escaping: +// 1. +// input path: `this/is/a/path` +// elements of path: `this`, `is`, `a`, `path` +// 2. +// input path: `this/is\/a/path` +// elements of path: `this`, `is/a`, `path` +// 3. +// input path: `this/is\\/a/path` +// elements of path: `this`, `is\`, `a`, `path` +// 4. +// input path: `this/is\\\/a/path` +// elements of path: `this`, `is\/a`, `path` +// 5. +// input path: `this/is//a/path` +// elements of path: `this`, `is`, `a`, `path` +// 6. +// input path: `this/is\//a/path` +// elements of path: `this`, `is/`, `a`, `path` +// 7. +// input path: `this/is/a/path/` +// elements of path: `this`, `is`, `a`, `path` +// 8. +// input path: `this/is/a/path\/` +// elements of path: `this`, `is`, `a`, `path/` + +package path + +import ( + "errors" +) + +var errMissingSegment = errors.New("missing required path segment") + +// TODO(ashmrtn): Getting the category should either be through type-switches or +// through a function, but if it's a function it should re-use existing enums +// for resource types. +// For now, adding generic functions to pull information from segments. +// Resources that don't have the requested information should return an empty +// string. +type Path interface { + String() string + Tenant() string + User() string + Folder() string + Item() string +} + +type Base struct { +} + +// newPath takes a path that is broken into segments and elements in the segment +// and returns a Base. Each element in the input is escaped. +func newPath(segments [][]string) Base { + return Base{} +} + +// NewPathFromEscapedSegments takes already escaped segments of a path, verifies +// the segments are escaped properly, and returns a new Base struct. If there is +// an unescaped trailing '/' it is removed. +func newPathFromEscapedSegments(segments []string) (Base, error) { + return Base{}, errors.New("not implemented") +} + +// String returns a string that contains all path segments joined +// together. Elements of the path that need escaping will be escaped. +func (p Base) String() string { + return "" +} + +// segment returns the nth segment of the path. Path segment indices are +// 0-based. +func (p Base) segment(n int) string { + return "" +} + +// unescapedSegmentElements returns the unescaped version of the elements that +// comprise the requested segment. +func (p Base) unescapedSegmentElements(n int) []string { + return nil +} + +// TransformedSegments returns a slice of the path segments where each segments +// has also been transformed such that it contains no characters outside the set +// of acceptable file system path characters. +func (p Base) TransformedSegments() []string { + return nil +}