Files
soft_quay/core/catalog/loader.go
T

145 lines
3.4 KiB
Go
Raw Normal View History

2026-07-16 16:13:18 +08:00
package catalog
import (
"context"
"errors"
"fmt"
)
var ErrNoValidCatalog = errors.New("no valid catalog available")
// Fetcher obtains a signed Catalog document from a remote or test source.
type Fetcher interface {
Fetch(context.Context) ([]byte, error)
}
// FetchFunc adapts a function to Fetcher.
type FetchFunc func(context.Context) ([]byte, error)
func (function FetchFunc) Fetch(ctx context.Context) ([]byte, error) {
return function(ctx)
}
// Cache stores the last verified signed document.
type Cache interface {
Load() ([]byte, error)
Store([]byte) error
}
2026-07-16 16:52:46 +08:00
// DocumentValidator rejects signed documents that this client cannot consume.
// Validators run before a remote document can replace the last valid cache.
type DocumentValidator interface {
Validate(VerifiedDocument) error
}
// DocumentValidatorFunc adapts a function to DocumentValidator.
type DocumentValidatorFunc func(VerifiedDocument) error
func (function DocumentValidatorFunc) Validate(document VerifiedDocument) error {
return function(document)
}
2026-07-16 16:13:18 +08:00
// LoadSource describes where a verified result came from.
type LoadSource string
const (
SourceRemote LoadSource = "remote"
SourceCache LoadSource = "cache"
)
// LoadResult returns verified bytes and a non-fatal refresh/cache warning.
type LoadResult struct {
Document VerifiedDocument
Source LoadSource
Warning error
}
// LoadError preserves both the refresh and cache failure.
type LoadError struct {
Refresh error
Cache error
}
func (err *LoadError) Error() string {
return fmt.Sprintf("%s: refresh=%v; cache=%v", ErrNoValidCatalog, err.Refresh, err.Cache)
}
func (err *LoadError) Unwrap() error {
return ErrNoValidCatalog
}
// Loader verifies remote data before storing it and re-verifies cache fallback.
type Loader struct {
2026-07-16 16:52:46 +08:00
verifier Verifier
fetcher Fetcher
cache Cache
validators []DocumentValidator
2026-07-16 16:13:18 +08:00
}
2026-07-16 16:52:46 +08:00
func NewLoader(
verifier Verifier,
fetcher Fetcher,
cache Cache,
validators ...DocumentValidator,
) *Loader {
2026-07-16 16:13:18 +08:00
return &Loader{
2026-07-16 16:52:46 +08:00
verifier: verifier,
fetcher: fetcher,
cache: cache,
validators: append([]DocumentValidator(nil), validators...),
2026-07-16 16:13:18 +08:00
}
}
// Load prefers a verified remote document and falls back to verified cache.
func (loader *Loader) Load(ctx context.Context) (LoadResult, error) {
remoteBytes, refreshErr := loader.fetcher.Fetch(ctx)
if refreshErr == nil {
verified, verifyErr := loader.verifier.Verify(remoteBytes)
if verifyErr == nil {
2026-07-16 16:52:46 +08:00
verifyErr = loader.validate(verified)
if verifyErr == nil {
storeErr := loader.cache.Store(verified.Bytes)
return LoadResult{
Document: verified,
Source: SourceRemote,
Warning: storeErr,
}, nil
}
2026-07-16 16:13:18 +08:00
}
refreshErr = verifyErr
}
cachedBytes, cacheErr := loader.cache.Load()
if cacheErr == nil {
var verified VerifiedDocument
verified, cacheErr = loader.verifier.Verify(cachedBytes)
if cacheErr == nil {
2026-07-16 16:52:46 +08:00
cacheErr = loader.validate(verified)
if cacheErr == nil {
return LoadResult{
Document: verified,
Source: SourceCache,
Warning: refreshErr,
}, nil
}
2026-07-16 16:13:18 +08:00
}
}
return LoadResult{}, &LoadError{
Refresh: refreshErr,
Cache: cacheErr,
}
}
2026-07-16 16:52:46 +08:00
func (loader *Loader) validate(document VerifiedDocument) error {
for _, validator := range loader.validators {
if validator == nil {
continue
}
if err := validator.Validate(document); err != nil {
return err
}
}
return nil
}