Documentation
¶
Overview ¶
Package workspacemedia writes model-generated media into the user's workspace as ordinary, visible files — the same way generated code or text lands there. It is a pure naming/collision layer: the caller hands it a workspace root, a requested relative path (or a generic fallback name such as "generated"), the bytes, and the MIME type; it returns the exact workspace-relative path it wrote.
Guarantees:
- Never overwrites. A completed sibling temp file is hard-linked to the final name, making name selection and publication one atomic no-replace operation. An existing entry of any kind (including a symlink) means "taken", so the writer retries with a dash suffix (name-1.ext, name-2.ext, ...), which also makes concurrent same-name writers safe.
- Atomic publish. A reader observes either no final entry or the full content, never a placeholder or partial write. Filesystems without hard-link support return an explicit error rather than falling back to a replace-capable rename.
- Workspace containment. Every directory and file operation goes through os.Root, so absolute paths, ".." traversal, invalid or Windows-reserved segments, and symlink escapes are rejected with ErrPathEscape rather than followed.
- The final filename's extension always agrees with the MIME type when the type is known; a conflicting requested extension is corrected and reported via Result so the caller can show a notice.
Files use ordinary user-file modes (0o755 directories, 0o644 files, both umask-masked). On Windows modes are ignored and entries inherit the parent directory's ACLs — same convention as pkg/atomicfile.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrNameExhausted = errors.New("no free filename after exhausting collision suffixes")
ErrNameExhausted classifies collision-suffix exhaustion: every candidate name up to maxNameAttempts already exists. Match with errors.Is when the failure must be explained without echoing the requested path.
var ErrPathEscape = errors.New("path escapes the workspace or has invalid segments")
ErrPathEscape classifies a requested path the writer refuses to touch: absolute, containing "..", an empty/invalid/Windows-reserved segment, or resolving outside the workspace root through a symlinked parent. Callers can redirect refused targets to a safe workspace basename.
Functions ¶
func RequestedBasename ¶
RequestedBasename returns the final meaningful segment of a requested path — the name to redirect to when the full path is refused — or "" when none exists (empty, separators only, or only "."/".." segments).
Types ¶
type PathClass ¶
type PathClass int
PathClass is the outcome of classifying a model-requested target path before any I/O. Classification is purely lexical: a PathWorkspaceRelative path can still be refused at write time when a symlinked parent resolves outside the root (surfaced as ErrPathEscape by Write).
const ( // PathInvalid marks a path that names nothing usable: empty, only // separators/dots, an invalid or Windows-reserved final segment. It is // unusable — callers should fall back to a safe generated name. PathInvalid PathClass = iota // PathWorkspaceRelative marks a path contained under the workspace // root, directly writable via [Write]. PathWorkspaceRelative // PathEscaping marks a path that targets a location outside the // workspace: absolute, traversing above the root via "..", or rooted at // a home directory via a leading "~" segment. PathEscaping )
func ClassifyRequestedPath ¶
ClassifyRequestedPath classifies requested and, for PathWorkspaceRelative, returns the normalized slash-separated relative path to hand to Write (both separator styles accepted; interior "." and ".." segments resolved lexically, so "a/../b.png" is contained rather than escaping). For every other class the second return is "".
type Result ¶
type Result struct {
// RelPath is the exact final path written, relative to the workspace root
// and slash-separated. Persist this verbatim.
RelPath string
// ExtensionCorrected reports that the requested filename's extension
// conflicted with the MIME-derived one and was replaced. Callers should
// surface a notice (e.g. "saved as sunshine.png — the provider returned
// PNG data"). RequestedExtension holds the original, with leading dot.
ExtensionCorrected bool
RequestedExtension string
}
Result describes a completed write.
func Write ¶
Write stores data under workspaceRoot at requestedPath, sanitized and collision-avoided per the package contract, and returns the exact workspace-relative path written. Prompt-directed subdirectories in requestedPath are created as needed. A rejected path returns an error matching ErrPathEscape, collision-suffix exhaustion one matching ErrNameExhausted; any other failure (unwritable directory, full disk, ...) is returned as-is for the caller to surface.