-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathvalidation.go
360 lines (322 loc) · 9.22 KB
/
validation.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
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
package ocfl
import (
"errors"
"fmt"
"iter"
"log/slog"
"runtime"
"github.com/hashicorp/go-multierror"
"github.com/srerickson/ocfl-go/digest"
"github.com/srerickson/ocfl-go/validation"
)
// Validation represents multiple fatal errors and warning errors.
type Validation struct {
fatal *multierror.Error
warn *multierror.Error
}
// Add adds all fatal errors and warnings from another validation to v.
func (v *Validation) Add(v2 *Validation) {
if v2 == nil {
return
}
v.AddFatal(v2.Errors()...)
v.AddWarn(v2.WarnErrors()...)
}
// AddFatal adds fatal errors to the validation
func (v *Validation) AddFatal(errs ...error) {
v.fatal = multierror.Append(v.fatal, errs...)
}
// AddWarn adds warning errors to the validation
func (v *Validation) AddWarn(errs ...error) {
v.warn = multierror.Append(v.warn, errs...)
}
// Err returns an error wrapping all the validation's fatal errors, or nil if
// there are no fatal errors.
func (v *Validation) Err() error {
if v.fatal == nil {
return nil
}
return v.fatal.ErrorOrNil()
}
// Errors returns a slice of all the fatal errors.q
func (v *Validation) Errors() []error {
if v.fatal == nil {
return nil
}
return v.fatal.Errors
}
// WarnErr returns an error wrapping all the validation's warning errors, or nil
// if there are none.
func (v *Validation) WarnErr() error {
if v.warn == nil {
return nil
}
return v.warn.ErrorOrNil()
}
// WarnErrors returns a slice of all the warning errors.
func (v *Validation) WarnErrors() []error {
if v.warn == nil {
return nil
}
return v.warn.Errors
}
// ObjectValidation is used to configure and track results from an object validation process.
type ObjectValidation struct {
Validation
obj *Object
// set with option
objOptions []ObjectOption
logger *slog.Logger
skipDigests bool
concurrency int
files map[string]*validationFileInfo
algRegistry digest.AlgorithmRegistry
}
// newObjectValidation constructs a new *Validation with the given
// options
func newObjectValidation(fsys FS, dir string, opts ...ObjectValidationOption) *ObjectValidation {
v := &ObjectValidation{
algRegistry: digest.DefaultRegistry(),
}
for _, opt := range opts {
opt(v)
}
v.obj = newObject(fsys, dir, v.objOptions...)
return v
}
// Add adds and logs all fatal errors and warning from the validation
func (v *ObjectValidation) Add(v2 *Validation) {
if v2 == nil {
return
}
v.AddFatal(v2.Errors()...)
v.AddWarn(v2.WarnErrors()...)
}
// PrefixAdd adds and logs all fatal errors and warning from the valiation,
// prepending each error with the prefix.
func (v *ObjectValidation) PrefixAdd(prefix string, v2 *Validation) {
if v2 == nil {
return
}
for _, err := range v2.Errors() {
v.AddFatal(fmt.Errorf("%s: %w", prefix, err))
}
for _, err := range v2.WarnErrors() {
v.AddWarn(fmt.Errorf("%s: %w", prefix, err))
}
}
// AddFatal adds fatal errors to the validation
func (v *ObjectValidation) AddFatal(errs ...error) {
v.Validation.AddFatal(errs...)
if v.logger == nil {
return
}
for _, err := range errs {
var validErr *ValidationError
switch {
case errors.As(err, &validErr):
v.logger.Error(err.Error(), "ocfl_code", validErr.Code)
default:
v.logger.Error(err.Error())
}
}
}
// AddWarn adds warning errors to the object validation and logs the errors
// using the object validations logger, if set.
func (v *ObjectValidation) AddWarn(errs ...error) {
v.Validation.AddWarn(errs...)
if v.logger == nil {
return
}
for _, err := range errs {
var validErr *ValidationError
switch {
case errors.As(err, &validErr):
v.logger.Warn(err.Error(), "ocfl_code", validErr.Code)
default:
v.logger.Warn(err.Error())
}
}
}
// Logger returns the validation's logger, which is nil by default.
func (v *ObjectValidation) Logger() *slog.Logger {
return v.logger
}
// SkipDigests returns true if the validation is configured to skip digest
// checks. It is false by default.
func (v *ObjectValidation) SkipDigests() bool {
return v.skipDigests
}
// DigestConcurrency returns the configured number of go routines used to read
// and digest contents during validation. The default value is runtime.NumCPU().
func (v *ObjectValidation) DigestConcurrency() int {
if v.concurrency > 0 {
return v.concurrency
}
return runtime.NumCPU()
}
// ValidationAlgorithms returns the registry of digest algoriths
// the object validation is configured to use. The default value is
// digest.DefaultRegistry
func (v *ObjectValidation) ValidationAlgorithms() digest.AlgorithmRegistry {
return v.algRegistry
}
// addExistingContent sets the existence status for a content file in the
// validation state.
func (v *ObjectValidation) addExistingContent(name string) {
if v.files == nil {
v.files = map[string]*validationFileInfo{}
}
if v.files[name] == nil {
v.files[name] = &validationFileInfo{}
}
v.files[name].fileExists = true
}
// addInventory adds digests from the inventory's manifest and fixity entries to
// the object validation for later verification. An error is returned if any
// name/digests entries in the inventory conflict with previously added values.
// The returned error wraps a slice of *DigestError values. Errors *are not*
// automatically added to the validation's Fatal errors.
//
// If isRoot is true, v.object's is set to inv
func (v *ObjectValidation) addInventory(inv Inventory, isRoot bool) error {
if v.files == nil {
v.files = map[string]*validationFileInfo{}
}
primaryAlg := inv.DigestAlgorithm()
allErrors := &multierror.Error{}
inv.Manifest().EachPath(func(name string, primaryDigest string) bool {
allDigests := inv.GetFixity(primaryDigest)
allDigests[primaryAlg.ID()] = primaryDigest
existing := v.files[name]
if existing == nil {
v.files[name] = &validationFileInfo{
expectedDigests: allDigests,
}
return true
}
if existing.expectedDigests == nil {
existing.expectedDigests = allDigests
return true
}
if err := existing.expectedDigests.Add(allDigests); err != nil {
var digestError *digest.DigestError
if errors.As(err, &digestError) {
digestError.Path = name
}
allErrors = multierror.Append(allErrors, err)
}
return true
})
if err := allErrors.ErrorOrNil(); err != nil {
return err
}
if isRoot {
v.obj.inventory = inv
}
return nil
}
// existingContent digests returns an iterator that yields the names and digests
// of files that exist and were referenced in the inventory added to the
// valiation.
func (v *ObjectValidation) existingContentDigests(fsys FS, objPath string, alg digest.Algorithm) FileDigestsSeq {
return func(yield func(*FileDigests) bool) {
for name, entry := range v.files {
if entry.fileExists && len(entry.expectedDigests) > 0 {
fd := &FileDigests{
FileRef: FileRef{
FS: fsys,
BaseDir: objPath,
Path: name,
},
Algorithm: alg,
Digests: entry.expectedDigests,
}
if !yield(fd) {
return
}
}
}
}
}
func (v *ObjectValidation) fs() FS { return v.obj.fs }
func (v *ObjectValidation) path() string { return v.obj.path }
// missingContent returns an iterator the yields the names of files that appear
// in an inventory added to the validation but were not marked as existing.
func (v *ObjectValidation) missingContent() iter.Seq[string] {
return func(yield func(string) bool) {
for name, entry := range v.files {
if !entry.fileExists && len(entry.expectedDigests) > 0 {
if !yield(name) {
return
}
}
}
}
}
// unexpectedContent returns an iterator that yields the names of existing files
// that were not included in an inventory manifest.
func (v *ObjectValidation) unexpectedContent() iter.Seq[string] {
return func(yield func(string) bool) {
for name, entry := range v.files {
if entry.fileExists && len(entry.expectedDigests) == 0 {
if !yield(name) {
return
}
}
}
}
}
type ObjectValidationOption func(*ObjectValidation)
func ValidationSkipDigest() ObjectValidationOption {
return func(opts *ObjectValidation) {
opts.skipDigests = true
}
}
// ValidationLogger sets the *slog.Logger that should be used for logging
// validation errors and warnings.
func ValidationLogger(logger *slog.Logger) ObjectValidationOption {
return func(v *ObjectValidation) {
v.logger = logger
}
}
// ValidationDigestConcurrency is used to set the number of go routines used to
// read and digest contents during validation.
func ValidationDigestConcurrency(num int) ObjectValidationOption {
return func(v *ObjectValidation) {
v.concurrency = num
}
}
// ValidationAlgorithms sets registry of available digest algorithms for
// fixity validation.
func ValidationAlgorithms(reg digest.AlgorithmRegistry) ObjectValidationOption {
return func(v *ObjectValidation) {
v.algRegistry = reg
}
}
type validationFileInfo struct {
expectedDigests digest.Set
fileExists bool
}
// ValidationError is an error that includes a reference
// to a validation error code from the OCFL spec.
type ValidationError struct {
validation.ValidationCode
Err error
}
func (ver *ValidationError) Error() string {
return ver.Err.Error()
}
func (ver *ValidationError) Unwrap() error {
return ver.Err
}
// helper for constructing new validation code
func verr(err error, code *validation.ValidationCode) error {
if code == nil {
return err
}
return &ValidationError{
Err: err,
ValidationCode: *code,
}
}