Help us shape the future of Go! Take the 2026 Go Developer Survey

resource

package
v1.30.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 18, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

README

Caller-scoped resource roots

resource.NewProvider supplies ordinary AFS files under a caller-defined scheme. Each authority names one explicitly configured, exact root. It never interprets an authority as a hostname to discover or fetch. Application metadata, skill frontmatter, MCP schemas and list/get protocol methods belong to their callers.

//go:embed skills
var skills embed.FS

backend := afs.New() // caller owns credentials, native providers and CloseAll
provider, err := resource.NewProvider(resource.Config{
    Scheme: "skill",
    Service: backend,
    Roots: map[string]resource.Root{
        "datly-reader": {FS: skills, Path: "skills/datly-reader"},
        "local":        {URL: "file://localhost/absolute/existing/directory"},
        "draft":        {URL: "mem://localhost/my-application/skills"},
    },
})
if err != nil { return err }
files := afs.NewWithProviders(map[string]afs.Provider{"skill": provider})
body, err := files.DownloadWithURL(ctx, "skill://datly-reader/SKILL.md")
// skill://datly-reader/references/guide.md uses the same exact root.

Create another provider for another scheme and put both in the service's map. Create another service for another caller's mappings, even when scheme and authority names are identical. Both maps are copied at construction; request options cannot replace resource roots. There is no global Register call. Unspecified service schemes still use AFS's existing global bootstrap registry; this is a resource mapping API, not a service-wide allowlist. The caller remains responsible for concurrency safety of borrowed fs.FS and backend services. Closing the mapping does not close those borrowed resources.

Operations

Operation fs.FS / embed.FS URL root
Object / metadata fs.Stat native AFS Object
List fs.ReadDir, self + immediate children, native match/page options native AFS List, native ordering/filtering
Open / Download fs.FS.Open native AFS OpenURL
Exists fs.ErrNotExist becomes false native AFS Exists
Upload / Create / Delete ErrReadOnly native AFS operation
NewWriter ErrReadOnly native AFS NewWriter, including its fallback
Walk / batch upload / Copy / Move AFS service composition AFS service composition

File listings contain the file itself. Object and list URLs remain virtual; metadata, including native size, mode, time and Unwrap, is preserved. For example, the current mem provider reports size zero; mapping does not invent a new size. Native URL-root errors are propagated, including provider-specific missing-path errors. errors.Is(err, resource.ErrUnknownNamespace) distinguishes an unmapped authority, without trying another root. ErrBoundary covers invalid paths and rejected links. ErrReadOnly distinguishes filesystem-backed write attempts.

Copy/Move retain AFS streaming/tree semantics, including copying from an embedded root into a writable root. No mapped Copier/Mover shortcut is advertised: native server-side copy/rename optimization is not forwarded. AFS copy/move are not atomic or transactional; moving from a read-only root can copy the destination before failing to delete the source. No ACL, symlink-creation, or mapped ErrorCoder capability is added. Native credentials and ordinary operation options are passed to the backing service; a read Modifier runs once at the outer service boundary. Backend-specific options carrying additional paths require caller review; options are trusted configuration, not client input. Known native object.Link options are rejected because they can address a second pathname.

Boundaries and cancellation

Virtual paths are decoded once. Dot segments, empty interior segments, encoded separators, backslashes, NUL, remaining percent escapes, and colon-containing path segments are rejected. Query, fragment, userinfo and opaque URIs are rejected. Spaces and Unicode names are supported; listed child names are URL-escaped. Authorities match exactly, with no default namespace or implicit root search. Root configuration also rejects traversal. The mapping delegates only to the configured backend root; returned list URLs outside it are rejected.

For a local URL root, construction resolves the existing root's symlinks, and each operation checks every existing target/ancestor component with Lstat. Symlinks below that root, including links to another in-root file, are rejected. Directory listings containing symlinks fail explicitly. Missing tails are allowed for writes. These checks protect against stable symlink escapes, but native AFS file operations remain pathname based: an external process can replace a component between the check and the operation. This is not race-resistant filesystem confinement. Use roots whose filesystem tree is controlled by the application. Supporting untrusted concurrent filesystem writers requires a separate native file-provider change using descriptor-relative operations; it is not claimed by this patch.

An arbitrary fs.FS must supply its own confinement; embed.FS is suitable. The fs.FS interface cannot inspect the implementation's symlink resolution, so os.DirFS/fs.Sub(os.DirFS(...)) alone is not a confinement mechanism. Local files should use a file URL to receive the explicit checks above. FS configuration is lexically validated; its root is opened lazily and missing roots fail normally.

Cancellation is checked before resolution, after metadata/list/open calls and between reader/writer calls. Context is forwarded to native URL operations. An already blocked fs.FS read cannot be interrupted through the fs.FS interface; blocking I/O cancellation remains the backend's responsibility. Close still releases returned streams after cancellation.

Cloud URLs can be configured through an existing caller-owned AFS service and its native providers. This candidate has been exercised with embed.FS, fstest.MapFS, local file URLs and native mem URLs only. No cloud connectivity, permission, write, or server-side capability claim is made.

Scratchpad

Scratchpad continues to own contextual user roots, note envelopes, artifact records and allowed target schemes. Those policies are not plain-file mechanics and have not been extracted or rewritten. The reusable native mechanism is the service-scoped provider factory seam; a scratchpad regression test exercises it while retaining contextual note isolation and artifact policy.

Documentation

Overview

Package resource maps caller-defined URL schemes and authorities to exact filesystem roots. It supplies files, not application resource metadata.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrUnknownNamespace = errors.New("unknown resource namespace")
	ErrBoundary         = errors.New("resource path outside boundary")
	ErrReadOnly         = errors.New("read-only resource root")
)

Functions

func NewProvider

func NewProvider(config Config) (afs.Provider, error)

NewProvider builds an immutable factory suitable for afs.NewWithProviders. No global registration, authority discovery or network access occurs here. Storage options passed to the factory cannot change the configured mappings.

Example
provider, err := resource.NewProvider(resource.Config{
	Scheme: "skill",
	Roots: map[string]resource.Root{
		"datly-reader": {FS: embedded, Path: "testdata/skills/datly-reader"},
	},
})
if err != nil {
	panic(err)
}
files := afs.NewWithProviders(map[string]afs.Provider{"skill": provider})
data, err := files.DownloadWithURL(context.Background(), "skill://datly-reader/references/guide.md")
if err != nil {
	panic(err)
}
fmt.Print(string(data))
Output:
Supporting guide.

Types

type Config

type Config struct {
	Scheme  string
	Roots   map[string]Root
	Service afs.Service
}

Config is copied by NewProvider. The filesystem and backing service are borrowed and must remain valid and safe for concurrent use. Service is required for URL roots; the caller retains responsibility for closing it.

type Root

type Root struct {
	FS   fs.FS
	Path string
	URL  string
}

Root selects exactly one of FS or URL. Path is an exact subtree within FS (empty means "."). URL is an absolute native AFS directory URL. FS must provide its own confinement (embed.FS does; os.DirFS does not). Use a file URL for local paths so the mapping can check symlinks.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL