Documentation
¶
Overview ¶
Package s2 is a lightweight object storage abstraction with multiple backends and an embeddable S3-compatible server.
Overview ¶
s2 provides a single Storage interface that all backends implement, and a small set of value types (Object, Metadata, Config) for moving data through it. The same interface drives a local-filesystem backend (osfs), an in-memory backend (memfs), and an AWS S3 / S3-compatible backend (s3). Backends register themselves via blank import:
import (
_ "github.com/mojatter/s2/fs" // osfs + memfs
_ "github.com/mojatter/s2/s3" // AWS S3
)
strg, err := s2.NewStorage(ctx, s2.Config{Type: s2.TypeOSFS, Root: "/var/data"})
Errors ¶
Operations that report a missing object wrap the sentinel ErrNotExist. Detect them with errors.Is:
if _, err := strg.Get(ctx, "foo.txt"); errors.Is(err, s2.ErrNotExist) {
// not found
}
NewStorage wraps ErrUnknownType when the requested backend has not been registered.
Concurrency ¶
All Storage implementations shipped with s2 are safe for concurrent use by multiple goroutines. Methods that mutate state (Put, Delete, Copy, ...) are independently atomic per object: a single Put is either fully visible to subsequent reads or not visible at all. Multiple concurrent Puts to the same object resolve to one of the writes — there is no defined ordering — but no torn writes are exposed.
An Object's attribute accessors — Name, Length, LastModified, Metadata, ContentType and ETag — are safe for concurrent use by multiple goroutines, on an Object returned by Get or List as much as on one you built, and Put leaves them alone. A backend that resolves an attribute lazily on first call must synchronize that itself; the fs backend reads its sidecar under a sync.Once.
Open and OpenRange differ by how the Object was made. One from NewObjectReader or NewObjectBytes wraps a single reader and hands the same one to every caller, so its body can be read — and therefore stored with Put — only once. One from Get, List or NewObjectFromFile opens a new reader per call.
Writing to the map Object.Metadata returns changes the in-memory Object and never the stored object, and must not be done while the Object is shared between goroutines. WithMetadata keeps the caller's map by reference, so every Object built from one map shares it — and so does the caller; pass a Metadata.Clone to hand over an independent copy. Get returns a writable map even for an object that carries no metadata. A List result reports whatever the backend holds, which may be nil: reads are safe on a nil map, writes to one panic.
PutMetadata is NOT atomic with Put. Calling Put followed by PutMetadata leaves a window during which the object exists with whatever metadata Put itself wrote.
PutMetadata takes two requests on the s3 backend, so a Put between them loses its Content-Type; on gcs the write is one request, which fails when the object changed since it was read. See each backend's PutMetadata.
Atomicity per backend ¶
The following table summarizes the atomicity guarantees of each operation across the bundled backends.
Operation | osfs | memfs | s3 ----------------------|-----------------------|-----------------------|----------------------- Put | atomic (temp+rename) | atomic | atomic (server-side) Copy | atomic per dst | atomic per dst | atomic (server-side) Move (via [Move]) | atomic (rename) | non-atomic | non-atomic Delete | atomic | atomic | atomic DeleteRecursive | best-effort, partial | best-effort, partial | best-effort, partial
"Best-effort, partial" means the operation deletes objects one at a time (or in pages, for s3) and may leave some objects behind on error.
Stability ¶
s2 follows semantic versioning. Until v1.0.0 the public API may change between minor versions; breaking changes are documented in the release notes. After v1.0.0, the package will only break compatibility on a major-version bump.
Index ¶
- Variables
- func Key(prefix, name string) string
- func Move(ctx context.Context, s Storage, src, dst string) error
- func MustInt64(v uint64) int64
- func MustUint64(v int64) uint64
- func ParseRoot(root string) (name, prefix string)
- func PrefetchMetadata(objs []Object)
- func RegisterNewStorageFunc(t Type, fn NewStorageFunc)
- func RelName(prefix, name string) string
- func UnregisterNewStorageFunc(t Type)
- func ValidateName(name string) error
- func ValidatePrefix(prefix string) error
- type AzblobConfig
- type Config
- type Configs
- type CountOptions
- type CountResult
- type Counter
- type GCSConfig
- type ListOptions
- type ListResult
- type Metadata
- type Mover
- type NewStorageFunc
- type OSFSConfig
- type Object
- type ObjectOption
- type S3Config
- type SignedURLMethod
- type SignedURLOptions
- type Storage
- type Storages
- type Type
- type UploadOptions
- type UploadResult
- type Uploader
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrInvalidName = errors.New("s2: invalid name")
ErrInvalidName is returned for a name or prefix a storage refuses: one that would resolve to something other than it spells, as "../other" does, or one the backend keeps for its own state. Detect with errors.Is:
if errors.Is(err, s2.ErrInvalidName) {
// reject the caller's name
}
var ErrModified = errors.New("s2: object modified since Get")
ErrModified is returned by Object.Open and OpenRange once the key holds another version than the one Get or List returned; Get the key again to read it.
var ErrNotExist = errors.New("s2: object not exist")
ErrNotExist is returned when an operation targets an object that does not exist. Backends wrap this with the missing object's name via fmt.Errorf, so callers should detect it with errors.Is rather than direct equality:
if errors.Is(err, s2.ErrNotExist) {
// handle missing object
}
var ErrPreconditionFailed = errors.New("s2: precondition failed")
ErrPreconditionFailed is returned by Upload when a condition in UploadOptions does not hold; the key is left as it was.
var ErrRequiredConfigRoot = errors.New("s2: required config.root")
ErrRequiredConfigRoot is returned by NewStorage implementations when Config.Root is empty. Root identifies the bucket, container, or directory that the storage operates on and is always required.
var ErrUnknownETag = errors.New("s2: object stored but its etag is unknown")
ErrUnknownETag is returned by Upload when the object was stored but its ETag could not be learned, because the read-back that would have supplied it failed. The write happened; only UploadResult.ETag is missing. The read-back's own error is wrapped alongside, so errors.Is still finds ErrNotExist under it.
var ErrUnknownType = errors.New("s2: unknown storage type")
ErrUnknownType is returned by NewStorage when no plugin is registered for the requested Type. Detect with errors.Is:
if errors.Is(err, s2.ErrUnknownType) {
// unknown backend
}
Functions ¶
func Move ¶ added in v0.2.0
Move moves src to dst on s. If s implements Mover, its Move method is used (which may be atomic and is generally more efficient). Otherwise Move falls back to Copy followed by Delete; that fallback is NOT atomic: if Delete fails after a successful Copy, both objects exist.
Example ¶
ExampleMove uses the free function s2.Move, which transparently picks the fastest path: backends that implement the optional s2.Mover interface (e.g. osfs via filesystem rename) get an atomic rename, others fall back to Copy + Delete.
package main
import (
"context"
"errors"
"fmt"
"io"
"github.com/mojatter/s2"
_ "github.com/mojatter/s2/fs"
)
func main() {
ctx := context.Background()
strg, _ := s2.NewStorage(ctx, s2.Config{Type: s2.TypeMemFS})
_ = strg.Put(ctx, s2.NewObjectBytes("src.txt", []byte("hi")))
if err := s2.Move(ctx, strg, "src.txt", "dst.txt"); err != nil {
panic(err)
}
_, err := strg.Get(ctx, "src.txt")
fmt.Println("src missing:", errors.Is(err, s2.ErrNotExist))
got, _ := strg.Get(ctx, "dst.txt")
rc, _ := got.Open()
defer rc.Close()
body, _ := io.ReadAll(rc)
fmt.Println("dst body:", string(body))
}
Output: src missing: true dst body: hi
func MustInt64 ¶
MustInt64 converts a uint64 to int64, panicking if the value exceeds math.MaxInt64.
func MustUint64 ¶
MustUint64 converts an int64 to uint64, panicking if the value is negative.
func ParseRoot ¶ added in v0.5.0
ParseRoot splits a Root string like "bucket/some/prefix" into the top-level name (bucket, container, or directory) and an optional key prefix. Leading and trailing slashes are trimmed.
The prefix is cleaned, so the two ways a backend spells a full key -- joining it with the name, or concatenating to keep the name verbatim -- resolve to the same string, and one that resolves to the root is empty rather than ".".
func PrefetchMetadata ¶ added in v0.30.0
func PrefetchMetadata(objs []Object)
PrefetchMetadata reads the Metadata of objs, a List page whose ContentType, ETag or Metadata the caller will read, on up to 8 goroutines at once. Reads fail as the accessors do; the accessors' own errors are not reported.
func RegisterNewStorageFunc ¶
func RegisterNewStorageFunc(t Type, fn NewStorageFunc)
RegisterNewStorageFunc registers a new storage function.
func RelName ¶ added in v0.15.0
RelName is the inverse of Key: it strips the storage prefix, leaving a name that does not carry it untouched.
func UnregisterNewStorageFunc ¶
func UnregisterNewStorageFunc(t Type)
UnregisterNewStorageFunc unregisters a storage function. Primarily useful in tests that swap backends.
func ValidateName ¶ added in v0.18.1
ValidateName wraps ErrInvalidName unless name is already the name the storage will use: exactly fs.ValidPath. A name that only resolves after cleaning -- "../x", "/x", "./x", "a//b", "x/" -- is rejected rather than folded, so the name a caller authorizes is the name it reads.
func ValidatePrefix ¶ added in v0.18.1
ValidatePrefix is ValidateName for a value that selects objects rather than naming one: "" selects everything, and one trailing "/" confines the selection to a directory. Listing cursors take it too, since they are compared against names rather than resolved to one.
Types ¶
type AzblobConfig ¶ added in v0.5.0
type AzblobConfig struct {
// AccountName is the Azure storage account name.
AccountName string `json:"account_name,omitempty"`
// AccountKey is the shared key for the storage account.
AccountKey string `json:"account_key,omitempty"`
// ConnectionString is a full Azure Storage connection string.
ConnectionString string `json:"connection_string,omitempty"`
}
AzblobConfig holds Azure Blob Storage-specific configuration. Authentication priority: ConnectionString > AccountName+AccountKey > DefaultAzureCredential (environment, managed identity, Azure CLI, etc.).
type Config ¶
type Config struct {
// Type is the type of storage.
Type Type `json:"type"`
// Root is the root path of the storage.
// If Type is TypeOSFS, Root is the root path of the file system.
// If Type is TypeMemFS, Root is not used.
// If Type is TypeS3, Root is the S3 bucket name. The string following / in the bucket name is treated as a prefix.
// If Type is TypeGCS, Root is the GCS bucket name. The string following / is treated as a prefix.
Root string `json:"root"`
// SignedURL is the presign URL of the storage. This is used for TypeOSFS and TypeMemFS.
SignedURL string `json:"signed_url,omitempty"`
// OSFS holds OSFS-specific settings. Only used when Type is TypeOSFS.
OSFS *OSFSConfig `json:"osfs,omitempty"`
// S3 holds S3-specific settings. Only used when Type is TypeS3.
S3 *S3Config `json:"s3,omitempty"`
// GCS holds GCS-specific settings. Only used when Type is TypeGCS.
GCS *GCSConfig `json:"gcs,omitempty"`
// Azblob holds Azure Blob Storage-specific settings. Only used when Type is TypeAzblob.
Azblob *AzblobConfig `json:"azblob,omitempty"`
}
Config is a configuration for a storage.
type Configs ¶ added in v0.5.1
Configs is a map of logical names to Config objects.
func LoadConfigs ¶ added in v0.5.1
LoadConfigs parses JSON formatted data from the specified io.Reader into Configs.
func LoadConfigsFile ¶ added in v0.5.1
LoadConfigsFile reads the file at the given path and parses it as a JSON format into Configs.
type CountOptions ¶ added in v0.23.0
type CountOptions struct {
// Prefix selects the names beginning with it, at any depth, as a recursive List does.
Prefix string
// UpTo stops the count once more than UpTo objects are seen; zero counts all.
UpTo int64
}
CountOptions controls a Count call.
type CountResult ¶ added in v0.23.0
type CountResult struct {
// Objects is the exact count, or UpTo when Truncated.
Objects int64
// Truncated reports that more than UpTo objects exist.
Truncated bool
}
CountResult reports how many objects a Count call saw.
func Count ¶ added in v0.23.0
func Count(ctx context.Context, s Storage, opts CountOptions) (CountResult, error)
Count counts the objects on s matching opts, via Counter when s has it, else by paging a recursive List; a failure reports no partial count.
type Counter ¶ added in v0.23.0
type Counter interface {
Count(ctx context.Context, opts CountOptions) (CountResult, error)
}
Counter is an optional interface a Storage may satisfy to count faster than paging List; it must report what a recursive List of an unchanging, readable tree reports and may ignore UpTo, which Count caps.
type GCSConfig ¶ added in v0.5.0
type GCSConfig struct {
// CredentialsFile is a path to a service account JSON key file.
CredentialsFile string `json:"credentials_file,omitempty"`
}
GCSConfig holds GCS-specific configuration. When fields are empty, the GCS SDK defaults (environment variables, Application Default Credentials, etc.) are used.
type ListOptions ¶ added in v0.2.0
type ListOptions struct {
// Prefix restricts the listing to objects whose names begin with Prefix
// when Recursive is true. A non-recursive listing treats it as a
// directory, so every backend reads it as if it ended in "/"; one naming
// an object, and no directory, returns an empty page.
Prefix string
// After is an opaque continuation token returned by a previous call as
// ListResult.NextAfter; pass it to fetch the next page. Empty for the
// first page. Its encoding is backend-specific: never construct one or
// carry one across storages. After wins over StartAfter.
After string
// StartAfter is a key name picked by the caller, existing or not; the
// listing resumes at the first entry, object or common prefix, sorting
// after it, as S3's start-after does. Ignored when After is set.
StartAfter string
// Limit caps the number of returned entries, CommonPrefixes included, as
// S3's max-keys does. Zero means the backend's default page size; follow
// NextAfter for the rest.
Limit int
// Recursive, when true, walks subdirectories and returns no
// CommonPrefixes; when false, the listing stops at the first "/" past
// Prefix and "directory-like" entries are surfaced via CommonPrefixes.
Recursive bool
}
ListOptions controls a Storage.List call.
All fields are optional. The zero value lists the first page of the storage's flat namespace.
type ListResult ¶ added in v0.2.0
type ListResult struct {
// Objects are the objects matching the request, in lexicographic order.
// Their metadata may be unset depending on the backend, and Metadata()
// may report nil; use Storage.Get for full metadata and a writable map.
Objects []Object
// CommonPrefixes are the directory-like grouping prefixes (only populated
// when ListOptions.Recursive is false). Each ends in "/", as S3 returns it,
// and is listed once across the pages of a listing.
CommonPrefixes []string
// NextAfter is an opaque continuation token, set only when an entry
// followed the page as it was read. When empty, the listing is exhausted.
// On s3, gcs and azblob, hidden directory markers ending a listing can leave the page after it empty.
NextAfter string
}
ListResult is the response from Storage.List.
type Metadata ¶
Metadata holds object metadata as case-sensitive key/value pairs.
Metadata mirrors the http.Header / url.Values pattern: it is a named map type with helper methods, so callers can use the standard map operations (range, len, indexing, literal initialization) directly when convenient and the methods when ergonomics matter.
The zero value is nil and is read-safe; writes to a nil Metadata panic. Use make(s2.Metadata) or a literal s2.Metadata{...} to obtain a writable instance.
func (Metadata) Clone ¶ added in v0.2.0
Clone returns a deep copy of the metadata. The result is independent of the receiver: mutating one does not affect the other. Cloning a nil Metadata returns nil.
func (Metadata) Delete ¶ added in v0.2.0
Delete removes the entry for key. Calling Delete on a missing key is a no-op.
type Mover ¶ added in v0.2.0
Mover is an optional interface that a Storage implementation may satisfy to provide a move operation that is more efficient — and possibly atomic — than the default Copy + Delete fallback. The osfs backend implements Mover via a filesystem rename, for example.
Storage implementations are not required to satisfy Mover; the free function Move falls back to Copy + Delete when they do not.
Move(k, k) must keep k, and Move of a missing key returns ErrNotExist; s2test.TestStorageCopyMove checks both.
type NewStorageFunc ¶
NewStorageFunc is a function that creates a new storage.
type OSFSConfig ¶ added in v0.31.0
type OSFSConfig struct {
// ConfinedRoot keeps the storage inside Root: a symlink leading outside it, or an absolute one, leads nowhere.
ConfinedRoot bool `json:"confined_root,omitempty"`
}
OSFSConfig holds OSFS-specific settings.
type Object ¶
type Object interface {
// Name returns the name of the object.
Name() string
// Open opens the object for reading and returns the reader stream.
// The caller is responsible for closing the returned io.ReadCloser.
// From a Storage, it fails with ErrModified once the key holds another version than Get or List saw, and with ErrNotExist once the key is gone.
Open() (io.ReadCloser, error)
// OpenRange opens the object for reading the specified range and returns the reader stream.
// The caller is responsible for closing the returned io.ReadCloser.
// It fails as Open does.
OpenRange(offset, length uint64) (io.ReadCloser, error)
// Length returns the length of the object in bytes.
Length() uint64
// LastModified returns the last modified time of the object.
LastModified() time.Time
// Metadata returns the object's user metadata. Writing to the returned
// map changes the in-memory Object, never the stored one.
//
// Storage.Get returns a writable map even when the object has none. A
// List result reports whatever the backend holds, which may be nil: reads
// are safe on a nil map, writes to one panic. On some backends (e.g. S3)
// a listed object carries no metadata at all; use Storage.Get for it.
Metadata() Metadata
// ContentType returns the object's MIME type, or "" when unknown.
// Objects returned by List may report "" (e.g., S3); use Storage.Get.
ContentType() string
// ETag returns the object's entity tag in quoted form, or "" when unknown.
// Objects returned by List carry it without extra network round trips.
ETag() string
}
Object is an interface that represents an object in a storage.
func NewObjectBytes ¶
func NewObjectBytes(name string, body []byte, opts ...ObjectOption) Object
NewObjectBytes creates new object from byte slice.
func NewObjectFromFile ¶ added in v0.2.0
NewObjectFromFile creates a new Object backed by a file on the local filesystem. The file at name must exist and not be a directory; otherwise the returned error wraps ErrNotExist. The Object's Length and LastModified are populated from os.Stat; metadata can be supplied via WithMetadata.
Reads via Open are performed lazily by re-opening the underlying file. The supplied ctx is currently unused but reserved for future cancellation.
func NewObjectReader ¶
func NewObjectReader(name string, body io.ReadCloser, length uint64, opts ...ObjectOption) Object
NewObjectReader creates new object from io.ReadCloser.
type ObjectOption ¶
type ObjectOption func(*object)
ObjectOption is a functional option for configuring objects created by NewObject, NewObjectReader, and NewObjectBytes.
func WithContentType ¶ added in v0.18.0
func WithContentType(contentType string) ObjectOption
WithContentType sets the content type on the object.
func WithLastModified ¶
func WithLastModified(t time.Time) ObjectOption
WithLastModified sets the last modified time on the object.
func WithMetadata ¶
func WithMetadata(md Metadata) ObjectOption
WithMetadata sets the metadata on the object, keeping md by reference.
type S3Config ¶
type S3Config struct {
// EndpointURL is a custom S3-compatible endpoint (e.g. "http://localhost:9000").
EndpointURL string `json:"endpoint_url,omitempty"`
// Region is the AWS region (e.g. "ap-northeast-1").
Region string `json:"region,omitempty"`
// AccessKeyID is the AWS access key ID.
AccessKeyID string `json:"access_key_id,omitempty"`
// SecretAccessKey is the AWS secret access key.
SecretAccessKey string `json:"secret_access_key,omitempty"`
}
S3Config holds S3-specific configuration. When fields are empty, the AWS SDK defaults (environment variables, shared credentials, IAM role, etc.) are used.
type SignedURLMethod ¶ added in v0.2.0
type SignedURLMethod string
SignedURLMethod is the HTTP method that a presigned URL is authorized for.
const ( // SignedURLGet authorizes a GET request (download). SignedURLGet SignedURLMethod = "GET" // SignedURLPut authorizes a PUT request (upload). SignedURLPut SignedURLMethod = "PUT" )
type SignedURLOptions ¶ added in v0.2.0
type SignedURLOptions struct {
// Name is the object name to sign.
Name string
// Method is the HTTP method to authorize. Defaults to GET when empty.
Method SignedURLMethod
// TTL is how long the URL remains valid.
TTL time.Duration
}
SignedURLOptions controls a Storage.SignedURL call.
type Storage ¶
type Storage interface {
// Type returns the type of the storage.
Type() Type
// Sub returns a view of the given prefix that shares the parent's lifetime:
// an object written through either is the same object, with the same ETag,
// Content-Type and metadata, through the other.
Sub(ctx context.Context, prefix string) (Storage, error)
// List returns the objects (and, when non-recursive, common prefixes)
// matching opts.
List(ctx context.Context, opts ListOptions) (ListResult, error)
// Get returns the object identified by name, including its metadata.
// The returned Object's Metadata map is non-nil and writable even when
// the object carries none; a List result may report nil instead.
// If no object exists at name, the returned error wraps ErrNotExist.
Get(ctx context.Context, name string) (Object, error)
// Exists reports whether anything is present at name. Backends that
// expose a directory hierarchy (osfs, memfs) treat both regular
// files and directories as "present"; flat key-value backends (s3)
// only resolve to leaf objects since they have no directory
// primitive of their own.
Exists(ctx context.Context, name string) (bool, error)
// Put writes obj to the storage atomically per object. Any metadata on
// obj is persisted as part of the same call.
Put(ctx context.Context, obj Object) error
// PutMetadata replaces the metadata of an existing object without
// rewriting its body. It is intended for hash- or ETag-style metadata
// that can only be computed after the body is written. Note: PutMetadata
// is NOT atomic with Put; a crash between the two leaves the object on
// disk with whatever metadata Put itself wrote. Replaces (does not merge)
// any existing metadata. A backend may fail the call when the object
// changes concurrently; that error is the provider's own.
PutMetadata(ctx context.Context, name string, metadata Metadata) error
// Copy duplicates src to dst. The semantics are backend-defined: the s3
// backend uses server-side copy, while file-backed backends stream the
// body.
Copy(ctx context.Context, src, dst string) error
// Delete removes the object at name. Deleting a non-existent object is
// a no-op and does not return an error.
Delete(ctx context.Context, name string) error
// DeleteRecursive removes every object whose name begins with prefix.
// A prefix ending in "/" covers only that directory, not names that merely share it.
// The operation is best-effort and not atomic across objects.
DeleteRecursive(ctx context.Context, prefix string) error
// SignedURL returns a presigned URL for the object identified by opts.
// Backends that do not support presigning return an error.
SignedURL(ctx context.Context, opts SignedURLOptions) (string, error)
}
Storage is a simple object storage abstraction. Implementations are expected to be safe for concurrent use by multiple goroutines.
Errors that report a missing object wrap ErrNotExist; detect them with errors.Is(err, s2.ErrNotExist).
Every method takes names exactly as stored -- see ValidateName and ValidatePrefix -- and wraps ErrInvalidName for anything else.
func NewStorage ¶
NewStorage creates a new storage from the given configuration. If no plugin is registered for cfg.Type, the returned error wraps ErrUnknownType.
Example ¶
ExampleNewStorage shows how to construct an in-memory Storage. The blank import of github.com/mojatter/s2/fs registers both osfs and memfs.
package main
import (
"context"
"fmt"
"github.com/mojatter/s2"
_ "github.com/mojatter/s2/fs"
)
func main() {
ctx := context.Background()
strg, err := s2.NewStorage(ctx, s2.Config{Type: s2.TypeMemFS})
if err != nil {
panic(err)
}
fmt.Println(strg.Type())
}
Output: memfs
type Type ¶
type Type string
func KnownTypes ¶ added in v0.2.0
func KnownTypes() []Type
KnownTypes returns the list of storage Types that are known to s2. The returned slice is a fresh copy; mutating it does not affect future calls. Note that this only enumerates compiled-in types; whether a given type is *registered* depends on whether the corresponding backend package has been imported (e.g. _ "github.com/mojatter/s2/fs").
type UploadOptions ¶ added in v0.19.0
type UploadOptions struct {
// IfMatch, when set, writes only while the key holds an object with this ETag, in the quoted form Object.ETag reports; "*" is unsupported.
IfMatch string
// IfNoneMatch, when "*", writes only while the key holds no object; any other value is unsupported.
IfNoneMatch string
}
UploadOptions controls an Upload call. At most one condition is set, as S3 takes them; see Validate.
func (UploadOptions) Check ¶ added in v0.27.0
func (o UploadOptions) Check(name, etag string, exists bool) error
Check judges opts against the object at name, whose ETag is etag when exists. It wraps ErrNotExist when IfMatch finds no object, ErrPreconditionFailed when a condition fails, and errors.ErrUnsupported for what Validate refuses.
func (UploadOptions) Conditional ¶ added in v0.27.0
func (o UploadOptions) Conditional() bool
Conditional reports whether opts carries a condition.
func (UploadOptions) Validate ¶ added in v0.27.0
func (o UploadOptions) Validate() error
Validate refuses with errors.ErrUnsupported, without reading the key, what S3 answers 501 to: IfMatch "*", both conditions, an IfNoneMatch other than "*".
type UploadResult ¶ added in v0.19.0
type UploadResult struct {
// ETag is the stored object's entity tag in quoted form, the same value a
// following Get reports — including "" for a backend that has none, since
// [Object.ETag] is itself "" when unknown.
ETag string
}
UploadResult reports what the backend stored. A field the backend does not know is left at its zero value.
func Upload ¶ added in v0.19.0
func Upload(ctx context.Context, s Storage, obj Object, opts UploadOptions) (UploadResult, error)
Upload stores obj on s and reports what was stored. If s implements Uploader, its Upload method is used and no read-back happens. Otherwise Upload falls back to Put followed by Get, which costs one more round trip and answers the same ETag. A capability that reports no ETag is read back the same way, so the result carries exactly what a following Get answers — which is "" for a backend that reports no ETag at all.
A read-back failure is returned as the call's error even though the object was stored; it wraps ErrUnknownETag over the error Get gave. A caller mapping errors to a status must not turn that into "no such object" — the write succeeded — and can tell the case apart with errors.Is.
The fallback is not atomic: a concurrent write to the same name can land between the Put and the read-back, and the ETag then describes that write rather than obj. Only a backend implementing Uploader is free of this. For the same reason the fallback fails a conditional call with errors.ErrUnsupported.
type Uploader ¶ added in v0.19.0
type Uploader interface {
Upload(ctx context.Context, obj Object, opts UploadOptions) (UploadResult, error)
}
Uploader is an optional interface that a Storage implementation may satisfy to report what it stored. Put answers nothing, so a caller that needs the stored ETag otherwise has to read the object back; a backend already has the value in its own write response.
Storage implementations are not required to satisfy Uploader; the free function Upload falls back to Put followed by Get when they do not.
Upload must hold opts' conditions until the write lands, as UploadOptions.Check judges them, or fail a conditional call with an error wrapping errors.ErrUnsupported.