hakurei/container/absolute.go
Ophestra 8543812e02
Some checks failed
Test / Create distribution (push) Successful in 33s
Test / Sandbox (push) Successful in 1m59s
Test / Hakurei (push) Failing after 2m56s
Test / Hpkg (push) Successful in 3m52s
Test / Sandbox (race detector) (push) Successful in 4m6s
Test / Hakurei (race detector) (push) Successful in 4m52s
Test / Flake checks (push) Has been skipped
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>
2025-08-09 17:50:03 +09:00

79 lines
1.8 KiB
Go

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
}
// MustAbsolute calls [NewAbsolute] and panics on error.
func MustAbsolute(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
}