-
Notifications
You must be signed in to change notification settings - Fork 351
/
parser.go
215 lines (192 loc) · 5.16 KB
/
parser.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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
package uri
import (
"errors"
"fmt"
"net/url"
"strings"
"github.com/treeverse/lakefs/pkg/validator"
)
const (
LakeFSSchema = "lakefs"
LakeFSSchemaSeparator = "://"
PathSeparator = "/"
)
var (
ErrMalformedURI = errors.New("malformed lakefs URI")
ErrInvalidRepoURI = errors.New("not a valid repo URI")
ErrInvalidRefURI = errors.New("not a valid ref URI")
ErrInvalidBranchURI = errors.New("not a valid branch URI")
ErrInvalidPathURI = errors.New("not a valid path URI")
)
type URI struct {
// Repository is the name of the repository being addressed
Repository string
// Ref represents the reference in the repository (commit, tag, branch, etc.)
Ref string
// Path is a path to an object (or prefix of such) in lakeFS. It *could* be null since there's a difference between
// an empty path ("lakefs://repo/branch/", and no path at all e.g. "lakefs://repo/branch").
// Since path is the only URI part that is allowed to be empty, it is represented as a pointer.
Path *string
}
func isValidRepository(u *URI) error {
switch {
case len(u.Repository) == 0:
return fmt.Errorf("missing repository part: %w", ErrInvalidRepoURI)
case !validator.ReValidRepositoryID.MatchString(u.Repository):
return fmt.Errorf("contains invalid repository name (repo=%s): %w", u.Repository, ErrInvalidRepoURI)
default:
return nil
}
}
func (u *URI) ValidateRepository() error {
err := isValidRepository(u)
switch {
case err != nil:
return err
case len(u.Ref) != 0:
return fmt.Errorf("repository URI includes a ref part (ref=%s): %w", u.Ref, ErrInvalidRepoURI)
case u.Path != nil:
return fmt.Errorf("repository URI includes path part (path=%s): %w", u.GetPath(), ErrInvalidRepoURI)
default:
return nil
}
}
func isValidRef(u *URI) error {
switch {
case len(u.Ref) == 0:
return fmt.Errorf("missing reference part: %w", ErrInvalidRefURI)
case !validator.ReValidRef.MatchString(u.Ref):
return fmt.Errorf("contains invalid reference name: %w", ErrInvalidRefURI)
default:
return nil
}
}
func (u *URI) ValidateRef() error {
if err := isValidRepository(u); err != nil {
return err
}
err := isValidRef(u)
switch {
case err != nil:
return err
case len(u.GetPath()) > 0:
return fmt.Errorf("ref URI includes a path part (path=%s): %w", u.GetPath(), ErrInvalidRefURI)
default:
return nil
}
}
func (u *URI) ValidateBranch() error {
path := u.GetPath()
if err := isValidRepository(u); err != nil {
return err
}
err := isValidRef(u)
switch {
case err != nil:
return err
case len(path) > 0 && path != PathSeparator: // Ignore path separator in path part for branch URIs
return fmt.Errorf("branch URI includes a path part: %w", ErrInvalidBranchURI)
case !validator.ReValidBranchID.MatchString(u.Ref):
return fmt.Errorf("contains invalid branch name: %w", ErrInvalidBranchURI)
default:
return nil
}
}
func (u *URI) ValidateFullyQualified() error {
if err := isValidRepository(u); err != nil {
return err
}
err := isValidRef(u)
switch {
case err != nil:
return err
case u.Path == nil:
return fmt.Errorf("missing path part: %w", ErrInvalidPathURI)
default:
return nil
}
}
func (u *URI) GetPath() string {
if u.Path == nil {
return ""
}
return *u.Path
}
// WithRef returns a new URI from u replacing the Reference part with the given ref
func (u *URI) WithRef(ref string) *URI {
return &URI{
Repository: u.Repository,
Ref: ref,
Path: u.Path,
}
}
func (u *URI) String() string {
var buf strings.Builder
buf.WriteString(LakeFSSchema)
buf.WriteString(LakeFSSchemaSeparator)
buf.WriteString(u.Repository)
if len(u.Ref) == 0 {
return buf.String()
}
buf.WriteString(PathSeparator)
buf.WriteString(u.Ref)
if u.Path == nil {
return buf.String()
}
buf.WriteString(PathSeparator)
buf.WriteString(*u.Path)
return buf.String()
}
// ParseWithBaseURI parse URI uses base URI as prefix when set and input doesn't start with lakeFS protocol
func ParseWithBaseURI(s string, baseURI string) (*URI, error) {
if len(baseURI) > 0 && !strings.HasPrefix(s, LakeFSSchema+LakeFSSchemaSeparator) {
s = baseURI + s
}
return Parse(s)
}
func Parse(s string) (*URI, error) {
u, err := url.Parse(s)
if err != nil || u.Scheme != LakeFSSchema || u.User != nil {
return nil, ErrMalformedURI
}
repository := u.Hostname()
if len(repository) == 0 {
return nil, ErrMalformedURI
}
var ref string
var path *string
if len(u.Path) > 0 {
if !strings.HasPrefix(u.Path, PathSeparator) {
return nil, ErrMalformedURI
}
const refAndPathParts = 2
levels := strings.SplitN(u.Path[1:], PathSeparator, refAndPathParts)
if len(levels) == refAndPathParts {
ref = levels[0]
path = &levels[1]
} else if len(levels) == 1 {
ref = levels[0]
}
}
return &URI{
Repository: repository,
Ref: ref,
Path: path,
}, nil
}
func Equals(a, b *URI) bool {
return a.Repository == b.Repository &&
a.Ref == b.Ref &&
// either both contain no path, or both do, and that path is equal
((a.Path == nil && b.Path == nil) || (a.Path != nil && b.Path != nil && *a.Path == *b.Path))
}
func IsValid(str string) bool {
_, err := Parse(str)
return err == nil
}
func Must(u *URI, e error) *URI {
if e != nil {
panic(e)
}
return u
}