diff options
| author | Ophestra <cat@gensokyo.uk> | 2025-08-09 17:50:03 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2025-08-09 18:53:46 +0900 |
| commit | ef54b2cd086ee8a3186c9646711d70e226815973 (patch) | |
| tree | 818b14bf9e10d1f11ed826fa0f84f135736605c4 /container/absolute.go | |
| parent | 82608164f66dda428189bdc3610f027820408b49 (diff) | |
container/absolute: early absolute pathname check
This is less error-prone, and allows pathname to be checked once.
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'container/absolute.go')
| -rw-r--r-- | container/absolute.go | 78 |
1 files changed, 78 insertions, 0 deletions
diff --git a/container/absolute.go b/container/absolute.go new file mode 100644 index 00000000..cafb1804 --- /dev/null +++ b/container/absolute.go @@ -0,0 +1,78 @@ +package container + +import ( + "encoding/json" + "errors" + "fmt" + "path" + "syscall" +) + +// AbsoluteError is returned by [NewAbsolute] and holds the invalid pathname. +type AbsoluteError struct { + Pathname string +} + +func (e *AbsoluteError) Error() string { return fmt.Sprintf("path %q is not absolute", e.Pathname) } +func (e *AbsoluteError) Is(target error) bool { + var ce *AbsoluteError + if !errors.As(target, &ce) { + return errors.Is(target, syscall.EINVAL) + } + return *e == *ce +} + +// Absolute holds a pathname checked to be absolute. +type Absolute struct { + pathname string +} + +// isAbs wraps [path.IsAbs] in case additional checks are added in the future. +func isAbs(pathname string) bool { return path.IsAbs(pathname) } + +func (a *Absolute) String() string { + if a.pathname == zeroString { + panic("attempted use of zero Absolute") + } + return a.pathname +} + +// NewAbsolute checks pathname and returns a new [Absolute] if pathname is absolute. +func NewAbsolute(pathname string) (*Absolute, error) { + if !isAbs(pathname) { + return nil, &AbsoluteError{pathname} + } + return &Absolute{pathname}, nil +} + +// MustAbs calls [NewAbsolute] and panics on error. +func MustAbs(pathname string) *Absolute { + if a, err := NewAbsolute(pathname); err != nil { + panic(err.Error()) + } else { + return a + } +} + +func (a *Absolute) GobEncode() ([]byte, error) { return []byte(a.String()), nil } +func (a *Absolute) GobDecode(data []byte) error { + pathname := string(data) + if !isAbs(pathname) { + return &AbsoluteError{pathname} + } + a.pathname = pathname + return nil +} + +func (a *Absolute) MarshalJSON() ([]byte, error) { return json.Marshal(a.String()) } +func (a *Absolute) UnmarshalJSON(data []byte) error { + var pathname string + if err := json.Unmarshal(data, &pathname); err != nil { + return err + } + if !isAbs(pathname) { + return &AbsoluteError{pathname} + } + a.pathname = pathname + return nil +} |
