-
Notifications
You must be signed in to change notification settings - Fork 8
/
filepath.go
142 lines (125 loc) · 4.87 KB
/
filepath.go
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
130
131
132
133
134
135
136
137
138
139
140
141
142
package filesystem
import (
"fmt"
"io/fs"
"path/filepath"
"strings"
"syscall"
validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/ARM-software/golang-utils/utils/commonerrors"
"github.com/ARM-software/golang-utils/utils/reflection"
)
// FilepathStem returns the final path component, without its suffix.
func FilepathStem(fp string) string {
return strings.TrimSuffix(filepath.Base(fp), filepath.Ext(fp))
}
// FileTreeDepth returns the depth of a file in a tree starting from root
func FileTreeDepth(fs FS, root, filePath string) (depth int64, err error) {
if reflection.IsEmpty(filePath) {
return
}
rel, err := fs.ConvertToRelativePath(root, filePath)
if err != nil {
return
}
diff := rel[0]
if reflection.IsEmpty(diff) {
return
}
diff = strings.ReplaceAll(diff, string(fs.PathSeparator()), "/")
depth = int64(len(strings.Split(diff, "/")) - 1)
return
}
// EndsWithPathSeparator states whether a path is ending with a path separator of not
func EndsWithPathSeparator(fs FS, filePath string) bool {
return strings.HasSuffix(filePath, "/") || strings.HasSuffix(filePath, string(fs.PathSeparator()))
}
// NewPathValidationRule returns a validation rule to use in configuration.
// The rule checks whether a string is a valid not empty path.
// `when` describes whether the rule is enforced or not
func NewPathValidationRule(filesystem FS, when bool) validation.Rule {
return &pathValidationRule{condition: when, filesystem: filesystem}
}
// NewOSPathValidationRule returns a validation rule to use in configuration.
// The rule checks whether a string is a valid path for the Operating System's filesystem.
// `when` describes whether the rule is enforced or not
func NewOSPathValidationRule(when bool) validation.Rule {
return NewPathValidationRule(GetGlobalFileSystem(), when)
}
type pathValidationRule struct {
condition bool
filesystem FS
}
func (r *pathValidationRule) Validate(value interface{}) error {
err := validation.Required.When(r.condition).Validate(value)
if err != nil {
return fmt.Errorf("%w: path [%v] is required: %v", commonerrors.ErrUndefined, value, err.Error())
}
if !r.condition {
return nil
}
pathString, err := validation.EnsureString(value)
if err != nil {
return fmt.Errorf("%w: path [%v] must be a string: %v", commonerrors.ErrInvalid, value, err.Error())
}
pathString = strings.TrimSpace(pathString)
// This check is here because it validates the path on any platform (it is a cross-platform check)
// Indeed if the path exists, then it can only be valid.
if r.filesystem.Exists(pathString) {
return nil
}
// Inspired from https://github.com/go-playground/validator/blob/84254aeb5a59e615ec0b66ab53b988bc0677f55e/baked_in.go#L1604 and https://stackoverflow.com/questions/35231846/golang-check-if-string-is-valid-path
if pathString == "" {
return fmt.Errorf("%w: the path [%v] is empty", commonerrors.ErrUndefined, value)
}
// This check is to catch errors on Linux. It does not work as well on Windows.
if _, err := r.filesystem.Stat(pathString); err != nil {
switch t := err.(type) {
case *fs.PathError:
if t.Err == syscall.EINVAL {
return fmt.Errorf("%w: the path [%v] has invalid characters: %v", commonerrors.ErrInvalid, value, err.Error())
}
default:
// make the linter happy
}
}
// The following case is not caught on Windows by the check above.
if strings.Contains(pathString, "\n") {
return fmt.Errorf("%w: the path [%v] has carriage returns characters", commonerrors.ErrInvalid, value)
}
// TODO add platform validation checks: e.g. https://learn.microsoft.com/en-gb/windows/win32/fileio/naming-a-file?redirectedfrom=MSDN on windows
return nil
}
// NewPathExistRule returns a validation rule to use in configuration.
// The rule checks whether a string is a valid not empty path and actually exists.
// `when` describes whether the rule is enforced or not.
func NewPathExistRule(filesystem FS, when bool) validation.Rule {
return &pathExistValidationRule{filesystem: filesystem, condition: when}
}
// NewOSPathExistRule returns a validation rule to use in configuration.
// The rule checks whether a string is a valid path for the Operating system's filesystem and actually exists.
// `when` describes whether the rule is enforced or not.
func NewOSPathExistRule(when bool) validation.Rule {
return NewPathExistRule(GetGlobalFileSystem(), when)
}
type pathExistValidationRule struct {
condition bool
filesystem FS
}
func (r *pathExistValidationRule) Validate(value interface{}) error {
err := NewPathValidationRule(r.filesystem, r.condition).Validate(value)
if err != nil {
return err
}
if !r.condition {
return nil
}
path, err := validation.EnsureString(value)
if err != nil {
return fmt.Errorf("%w: path [%v] must be a string: %v", commonerrors.ErrInvalid, value, err.Error())
}
if !r.filesystem.Exists(path) {
err = fmt.Errorf("%w: path [%v] does not exist", commonerrors.ErrNotFound, path)
}
return err
}