Expose native filesystem sources through public loading API
This commit is contained in:
@@ -16,8 +16,8 @@ const (
|
||||
maxTotalBytes = 64 << 20
|
||||
)
|
||||
|
||||
// Source is an opaque configuration input. Construct sources with File, Bytes,
|
||||
// Reader, or NoSource; the zero value is invalid.
|
||||
// Source is an opaque configuration input. Construct sources with File,
|
||||
// Filesystem.File, Bytes, Reader, or NoSource; the zero value is invalid.
|
||||
type Source struct {
|
||||
kind sourceKind
|
||||
path string
|
||||
@@ -47,8 +47,52 @@ const (
|
||||
creationInvalidLabel
|
||||
creationInvalidReader
|
||||
creationTooLarge
|
||||
creationInvalidFilesystem
|
||||
creationInvalidFilesystemLabel
|
||||
creationInvalidFilesystemPath
|
||||
)
|
||||
|
||||
// Filesystem is a reusable opaque namespace for caller-owned fs.FS inputs.
|
||||
// Copies preserve the namespace identity used for explicit contexts.
|
||||
type Filesystem struct {
|
||||
namespace *nativeNamespace
|
||||
createErr sourceCreationError
|
||||
}
|
||||
|
||||
// NewFilesystem records a borrowed filesystem without opening it. Invalid
|
||||
// arguments are reported when a File source is consumed.
|
||||
func NewFilesystem(label string, filesystem fs.FS) Filesystem {
|
||||
result := Filesystem{}
|
||||
if !validOverrideLabel(label) {
|
||||
result.createErr = creationInvalidFilesystemLabel
|
||||
} else if nilFilesystem(filesystem) {
|
||||
result.createErr = creationInvalidFilesystem
|
||||
} else {
|
||||
result.namespace = newNativeNamespace(label, filesystem)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// File selects a non-root slash-relative file within this namespace.
|
||||
func (f Filesystem) File(name string) Source {
|
||||
if f.createErr != creationValid {
|
||||
return Source{kind: sourceFilesystem, createErr: f.createErr}
|
||||
}
|
||||
return filesystemFile(f.namespace, name)
|
||||
}
|
||||
|
||||
func nilFilesystem(filesystem fs.FS) bool {
|
||||
if filesystem == nil {
|
||||
return true
|
||||
}
|
||||
value := reflect.ValueOf(filesystem)
|
||||
switch value.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice:
|
||||
return value.IsNil()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type readerSource struct {
|
||||
input io.Reader
|
||||
used atomic.Bool
|
||||
@@ -63,8 +107,11 @@ func File(path string) Source {
|
||||
}
|
||||
|
||||
func filesystemFile(namespace *nativeNamespace, name string) Source {
|
||||
if namespace == nil || namespace.filesystem == nil || name == "." || !fs.ValidPath(name) {
|
||||
return Source{kind: sourceFilesystem, createErr: creationInvalidPath}
|
||||
if namespace == nil || nilFilesystem(namespace.filesystem) {
|
||||
return Source{kind: sourceFilesystem, createErr: creationInvalidFilesystem}
|
||||
}
|
||||
if name == "." || !fs.ValidPath(name) {
|
||||
return Source{kind: sourceFilesystem, createErr: creationInvalidFilesystemPath}
|
||||
}
|
||||
return Source{kind: sourceFilesystem, path: name, label: namespace.label + "/" + name, namespace: namespace}
|
||||
}
|
||||
@@ -132,7 +179,7 @@ func readSource(source Source, sourceID string, limits sourceLimits) (sourceRead
|
||||
if limit < 0 {
|
||||
limit = 0
|
||||
}
|
||||
info := &SourceInfo{ID: sourceID, Label: source.label, Layer: LayerBase}
|
||||
info := &SourceInfo{ID: sourceID, Label: source.label, Layer: LayerBase, Kind: sourceSourceKind(source.kind)}
|
||||
|
||||
var input io.Reader
|
||||
switch source.kind {
|
||||
@@ -159,6 +206,7 @@ func readSource(source Source, sourceID string, limits sourceLimits) (sourceRead
|
||||
if err != nil {
|
||||
return sourceRead{info: info}, []Diagnostic{classifySourceError(err, sourceID)}
|
||||
}
|
||||
info.Path = canonical
|
||||
data, failure := readRegularFile(canonical, access.stat, access.open, limit)
|
||||
if failure != nil {
|
||||
return sourceRead{info: info}, []Diagnostic{sourceReadDiagnostic(failure, sourceID, CodeSourceLimitExceeded, "source exceeds the configured byte limit")}
|
||||
@@ -199,11 +247,32 @@ func sourceConstructorDiagnostic(source Source) *Diagnostic {
|
||||
case creationTooLarge:
|
||||
code = CodeSourceLimitExceeded
|
||||
message = "source exceeds the configured byte limit"
|
||||
case creationInvalidFilesystem:
|
||||
message = "filesystem must not be nil"
|
||||
case creationInvalidFilesystemLabel:
|
||||
message = "filesystem label must be nonempty and safe to display"
|
||||
case creationInvalidFilesystemPath:
|
||||
message = "filesystem source path must be a non-root slash-relative valid path"
|
||||
}
|
||||
diagnostic := sourceDiagnostic(code, "", message)
|
||||
return &diagnostic
|
||||
}
|
||||
|
||||
func sourceSourceKind(kind sourceKind) SourceKind {
|
||||
switch kind {
|
||||
case sourceFile:
|
||||
return SourceKindFile
|
||||
case sourceFilesystem:
|
||||
return SourceKindFilesystem
|
||||
case sourceBytes:
|
||||
return SourceKindBytes
|
||||
case sourceReader:
|
||||
return SourceKindReader
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func classifySourceError(err error, sourceID string) Diagnostic {
|
||||
switch {
|
||||
case errors.Is(err, fs.ErrNotExist):
|
||||
|
||||
Reference in New Issue
Block a user