aboutsummaryrefslogtreecommitdiffhomepage
path: root/container/check/absolute.go
blob: 26f0def6617cec39d4e8648db858f8d555609dc3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// Package check provides types yielding values checked to meet a condition.
package check

import (
	"encoding/json"
	"errors"
	"fmt"
	"path"
	"slices"
	"strings"
	"syscall"
	"unique"
)

// AbsoluteError is returned by [NewAbs] 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 unique.Handle[string] }

// ok returns whether [Absolute] is not the zero value.
func (a *Absolute) ok() bool { return a != nil && *a != (Absolute{}) }

// unsafeAbs returns [check.Absolute] on any string value.
func unsafeAbs(pathname string) *Absolute {
	return &Absolute{unique.Make(pathname)}
}

// String returns the checked pathname.
func (a *Absolute) String() string {
	if !a.ok() {
		panic("attempted use of zero Absolute")
	}
	return a.pathname.Value()
}

// Handle returns the underlying [unique.Handle].
func (a *Absolute) Handle() unique.Handle[string] {
	return a.pathname
}

// Is efficiently compares the underlying pathname.
func (a *Absolute) Is(v *Absolute) bool {
	if a == nil && v == nil {
		return true
	}
	return a.ok() && v.ok() && a.pathname == v.pathname
}

// NewAbs checks pathname and returns a new [Absolute] if pathname is absolute.
func NewAbs(pathname string) (*Absolute, error) {
	if !path.IsAbs(pathname) {
		return nil, &AbsoluteError{pathname}
	}
	return unsafeAbs(pathname), nil
}

// MustAbs calls [NewAbs] and panics on error.
func MustAbs(pathname string) *Absolute {
	if a, err := NewAbs(pathname); err != nil {
		panic(err)
	} else {
		return a
	}
}

// Append calls [path.Join] with [Absolute] as the first element.
func (a *Absolute) Append(elem ...string) *Absolute {
	return unsafeAbs(path.Join(append([]string{a.String()}, elem...)...))
}

// Dir calls [path.Dir] with [Absolute] as its argument.
func (a *Absolute) Dir() *Absolute { return unsafeAbs(path.Dir(a.String())) }

// GobEncode returns the checked pathname.
func (a *Absolute) GobEncode() ([]byte, error) {
	return []byte(a.String()), nil
}

// GobDecode stores data if it represents an absolute pathname.
func (a *Absolute) GobDecode(data []byte) error {
	pathname := string(data)
	if !path.IsAbs(pathname) {
		return &AbsoluteError{pathname}
	}
	a.pathname = unique.Make(pathname)
	return nil
}

// MarshalJSON returns a JSON representation of the checked pathname.
func (a *Absolute) MarshalJSON() ([]byte, error) {
	return json.Marshal(a.String())
}

// UnmarshalJSON stores data if it represents an absolute pathname.
func (a *Absolute) UnmarshalJSON(data []byte) error {
	var pathname string
	if err := json.Unmarshal(data, &pathname); err != nil {
		return err
	}
	if !path.IsAbs(pathname) {
		return &AbsoluteError{pathname}
	}
	a.pathname = unique.Make(pathname)
	return nil
}

// SortAbs calls [slices.SortFunc] for a slice of [Absolute].
func SortAbs(x []*Absolute) {
	slices.SortFunc(x, func(a, b *Absolute) int {
		return strings.Compare(a.String(), b.String())
	})
}

// CompactAbs calls [slices.CompactFunc] for a slice of [Absolute].
func CompactAbs(s []*Absolute) []*Absolute {
	return slices.CompactFunc(s, func(a *Absolute, b *Absolute) bool {
		return a.Is(b)
	})
}