storage

package
v0.49.0 Latest Latest
Warning

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

Go to latest
Published: Sep 28, 2026 License: MIT Imports: 5 Imported by: 0

Documentation

Overview

Package storage is the file contract: what a tenant uploaded, and where it went.

It is not an optional package: a file is customer data, and a path without a tenant is a leak with a directory name. Every operation takes a security.Grant, and the stored path is prefixed by the tenant that Grant carries.

This package is a bridge. It is removed in v1.0.0; import github.com/arandu-io/hesape/filesystem directly.

The components moved to github.com/arandu-io/hesape, under new names, and this package is now the old names pointing at them. One hesape package answers for all of it:

hesape/filesystem  Key, CleanKey, ErrNotFound, ErrNoTenant, ErrBadKey,
                   and the Adapter/Disk pair that replaced Store

The death date above is what keeps this from being a second way to import one type. Nothing here holds an implementation: the errors are Go aliases, Path and CleanKey are one-line calls through, and the two shapes that could not follow the rename are declared with their old form and nothing else.

What the rename reshaped

hesape split the old Store in two. An Adapter is what a driver implements and it never hears of a tenant; a Disk is what an application calls and every one of its methods takes a Grant; between them sits filesystem.Key, which turns a Grant and a key into the one stored path that Grant may reach. Path was renamed to Key in the move, and that split is why: the prefix is applied once, in hesape, instead of in each driver remembering to ask.

The two shapes that stay declared here, and why

Store  hesape/filesystem.Adapter takes stored paths and no Grant, and has a
       sixth method (Stat). A driver implements the five-method,
       Grant-taking shape from a module this one does not compile, so an
       alias would compile here and break it in silence.
File   hesape/filesystem.File carries its metadata in an embedded Info, so
       the flat composite literal a driver writes does not compile against
       it. The fields and their meanings are unchanged.

Neither declaration is a way around the Grant: the only thing that produces a stored path is Path, and Path needs one.

Index

Constants

This section is empty.

Variables

View Source
var ErrBadKey = filesystem.ErrBadKey

ErrBadKey is returned for a key that would escape its tenant's prefix.

View Source
var ErrNoTenant = filesystem.ErrNoTenant

ErrNoTenant is returned when the Grant carries no tenant, or carries one that cannot be a path segment.

View Source
var ErrNotFound = filesystem.ErrNotFound

ErrNotFound is returned when a key does not exist for this tenant.

It is the same error whether the file is absent or belongs to somebody else, and that is deliberate: distinguishing them would tell a caller which keys exist in other tenants.

It is one value with filesystem.ErrNotFound, so a driver written against either name satisfies the other's contract without a line changing.

Functions

func CleanKey

func CleanKey(key string) (string, error)

CleanKey normalizes a key and refuses one that would escape.

This is the check that matters. A key is often a filename that came from an upload, and "../../../etc/passwd" is what an upload form eventually receives. Rejecting rather than sanitizing: a key that had to be rewritten to be safe is a key the caller did not mean, and silently storing it somewhere else is worse than an error.

func Path

func Path(g security.Grant, key string) (string, error)

Path builds the stored path for a key: <tenant>/<key>.

Every driver calls it, which is what makes tenant isolation a property of the contract rather than of each implementation remembering. The tenant comes from the Grant and never from the key, so naming another tenant in the key reaches nothing.

Renamed on the way to hesape: it is filesystem.Key there.

Types

type File

type File struct {
	Key         string
	Size        int64
	ContentType string
	ModifiedAt  time.Time
	// Body is the content. The caller closes it.
	Body io.ReadCloser
}

File is what a Get returns.

It stays declared here rather than aliasing filesystem.File, which carries the same four fields inside an embedded filesystem.Info. The fields and their meanings are identical, but the flat composite literal the two driver modules write does not compile against an embedded one, and a bridge that changes a shape is not a bridge.

type Store

type Store interface {
	// Put writes a file under the tenant of the Grant.
	Put(ctx context.Context, g security.Grant, key string, body io.Reader, contentType string) error
	// Get reads one back.
	Get(ctx context.Context, g security.Grant, key string) (File, error)
	// Delete removes it. Removing what is not there is not an error.
	Delete(ctx context.Context, g security.Grant, key string) error
	// List returns the keys under a prefix, without the tenant part.
	List(ctx context.Context, g security.Grant, prefix string) ([]string, error)
	// Exists reports whether the key is there.
	Exists(ctx context.Context, g security.Grant, key string) (bool, error)
}

Store is what a driver implements.

Every method takes a Grant. That is not ceremony: it is the only thing standing between "the application stores files" and "any handler can read any customer's files by building a string". Reads are not exempt -- List, Get and Exists take one for the same reason Put does, and a listing is the one call where forgetting it hands over the names of every file in the system.

It stays declared here rather than aliasing filesystem.Adapter, which took the Grant off every method and added a sixth. The Grant did not disappear there: it moved up to filesystem.Disk, which resolves the path before the driver sees it. Here it is still the driver that calls Path, and Path is still the only thing that produces a stored path.

Jump to

Keyboard shortcuts

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