Documentation
¶
Overview ¶
Package storage provides S3-compatible file storage operations.
It offers a simple interface for uploading, retrieving, and managing files with automatic MIME detection, validation, and multi-tenant support.
Basic Usage ¶
Create a storage client and upload files:
cfg := storage.Config{
Bucket: "my-bucket",
Region: "us-east-1",
AccessKey: os.Getenv("AWS_ACCESS_KEY_ID"),
SecretKey: os.Getenv("AWS_SECRET_ACCESS_KEY"),
}
store, err := storage.New(cfg)
if err != nil {
log.Fatal(err)
}
// Upload from form
fh, _ := c.FormFile("avatar")
info, err := storage.PutFile(ctx, store, fh,
storage.WithPrefix("avatars"),
storage.WithACL(storage.ACLPublicRead),
)
Validation ¶
Use WithValidation for validated uploads:
info, err := storage.PutFile(ctx, store, fh,
storage.WithValidation(
storage.MaxSize(5 << 20), // 5MB
storage.ImageOnly(),
),
storage.WithTenant(tenantID),
storage.WithPrefix("avatars"),
)
if err != nil {
var verr *storage.FileValidationError
if errors.As(err, &verr) {
// Handle validation error
}
}
URL Generation ¶
Generate URLs for stored files:
// Auto-detect based on ACL (public vs signed)
url, err := store.URL(ctx, info.Key)
// Force signed URL with custom expiry
url, err := store.URL(ctx, info.Key,
storage.WithSigned(time.Hour),
)
// Signed URL with download disposition
url, err := store.URL(ctx, info.Key,
storage.WithDownload("document.pdf"),
)
Multi-Tenant Support ¶
Use WithTenant for tenant isolation:
info, err := storage.PutFile(ctx, store, fh,
storage.WithTenant(tenantID),
storage.WithPrefix("documents"),
)
// Key: {tenant}/{prefix}/{ulid}.{ext}
Configuration ¶
The Config struct supports environment variables:
type Config struct {
Bucket string // STORAGE_BUCKET
AccessKey string // STORAGE_ACCESS_KEY
SecretKey string // STORAGE_SECRET_KEY
Endpoint string // STORAGE_ENDPOINT (for MinIO/custom S3)
Region string // STORAGE_REGION (default: us-east-1)
PublicURL string // STORAGE_PUBLIC_URL (CDN URL)
DefaultACL string // DEFAULT_ACL (default: private)
PathStyle bool // STORAGE_PATH_STYLE (for MinIO)
MaxDownloadSize int64 // STORAGE_MAX_DOWNLOAD (default: 50MB)
}
Index ¶
- Constants
- Variables
- func DetectMIME(fh *multipart.FileHeader) string
- func ExtFromMIME(mimeType string) string
- func IsAudio(fh *multipart.FileHeader) bool
- func IsDocument(fh *multipart.FileHeader) bool
- func IsImage(fh *multipart.FileHeader) bool
- func IsVideo(fh *multipart.FileHeader) bool
- func ValidateFile(fh *multipart.FileHeader, mimeType string, rules ...ValidationRule) error
- func ValidateReader(size int64, mimeType string, rules ...ValidationRule) error
- type ACL
- type Config
- type FileInfo
- func PutBytes(ctx context.Context, s Storage, data []byte, filename string, opts ...Option) (*FileInfo, error)
- func PutFile(ctx context.Context, s Storage, fh *multipart.FileHeader, opts ...Option) (*FileInfo, error)
- func PutFromURL(ctx context.Context, s Storage, sourceURL string, maxSize int64, ...) (*FileInfo, error)
- type FileValidationError
- type Option
- type ReaderValidationRule
- type S3Storage
- func (s *S3Storage) Copy(ctx context.Context, srcKey, dstKey string) error
- func (s *S3Storage) Delete(ctx context.Context, key string) error
- func (s *S3Storage) Get(ctx context.Context, key string) (io.ReadCloser, error)
- func (s *S3Storage) HeadObject(ctx context.Context, key string) (*FileInfo, error)
- func (s *S3Storage) Put(ctx context.Context, r io.Reader, size int64, opts ...Option) (*FileInfo, error)
- func (s *S3Storage) URL(ctx context.Context, key string, opts ...URLOption) (string, error)
- type Storage
- type URLOption
- type ValidationRule
Constants ¶
const ( DefaultRegion = "us-east-1" DefaultMaxDownloadSize = 50 << 20 // 50MB to prevent abuse DefaultSignedURLExpiry = 15 * 60 // 15 minutes in seconds )
Default configuration values.
const ( ErrCodeFileTooLarge = "file_too_large" ErrCodeFileTooSmall = "file_too_small" ErrCodeInvalidMIME = "invalid_mime" ErrCodeEmptyFile = "empty_file" )
Error codes for FileValidationError.
const DefaultURLExpiry = 15 * time.Minute
DefaultURLExpiry is the default expiry for signed URLs.
const (
MIMEOctetStream = "application/octet-stream"
)
MIME type constants.
Variables ¶
var ( ErrNotConfigured = errors.New("storage: not configured") ErrInvalidConfig = errors.New("storage: invalid configuration") ErrEmptyFile = errors.New("storage: file is empty") ErrFileTooLarge = errors.New("storage: file exceeds size limit") ErrFileTooSmall = errors.New("storage: file below minimum size") ErrInvalidMIME = errors.New("storage: file type not allowed") ErrNotFound = errors.New("storage: file not found") ErrAccessDenied = errors.New("storage: access denied") ErrUploadFailed = errors.New("storage: upload failed") ErrDeleteFailed = errors.New("storage: delete failed") ErrPresignFailed = errors.New("storage: presign failed") ErrInvalidURL = errors.New("storage: invalid URL") ErrDownloadFailed = errors.New("storage: failed to download from URL") ErrDownloadTooLarge = errors.New("storage: download exceeds size limit") )
Sentinel errors for storage operations.
Functions ¶
func DetectMIME ¶
func DetectMIME(fh *multipart.FileHeader) string
DetectMIME detects the MIME type of a multipart file header by reading magic bytes. Returns "application/octet-stream" if detection fails.
func ExtFromMIME ¶
ExtFromMIME returns the file extension for a MIME type. Returns empty string if MIME type is unknown.
func IsAudio ¶
func IsAudio(fh *multipart.FileHeader) bool
IsAudio checks if the file is audio based on magic bytes.
func IsDocument ¶
func IsDocument(fh *multipart.FileHeader) bool
IsDocument checks if the file is a document based on magic bytes.
func IsImage ¶
func IsImage(fh *multipart.FileHeader) bool
IsImage checks if the file is an image based on magic bytes.
func IsVideo ¶
func IsVideo(fh *multipart.FileHeader) bool
IsVideo checks if the file is a video based on magic bytes.
func ValidateFile ¶
func ValidateFile(fh *multipart.FileHeader, mimeType string, rules ...ValidationRule) error
ValidateFile runs all validation rules against a file. Returns the first validation error encountered, or nil if all pass. The mimeType should be pre-detected from magic bytes for accuracy.
func ValidateReader ¶
func ValidateReader(size int64, mimeType string, rules ...ValidationRule) error
ValidateReader runs validation rules against Reader-available data (size and MIME type). Only rules that implement ReaderValidationRule are applied; others are silently skipped. Returns the first validation error encountered, or nil if all pass.
Types ¶
type Config ¶
type Config struct {
Bucket string `env:"BUCKET,required"`
AccessKey string `env:"ACCESS_KEY,required"`
SecretKey string `env:"SECRET_KEY,required"`
// Endpoint is the custom S3 endpoint URL (optional, for MinIO or other S3-compatible services).
Endpoint string `env:"ENDPOINT"`
Region string `env:"REGION" envDefault:"us-east-1"`
// PublicURL is the CDN or public URL prefix for public files (optional).
// If set, public URLs will use this prefix instead of the S3 URL.
PublicURL string `env:"PUBLIC_URL"`
DefaultACL string `env:"DEFAULT_ACL" envDefault:"private"`
// PathStyle enables path-style URLs (required for MinIO).
PathStyle bool `env:"PATH_STYLE" envDefault:"false"`
MaxDownloadSize int64 `env:"MAX_DOWNLOAD_SIZE" envDefault:"52428800"`
}
Config holds S3-compatible storage configuration.
type FileInfo ¶
FileInfo contains metadata about an uploaded file.
func PutBytes ¶
func PutBytes(ctx context.Context, s Storage, data []byte, filename string, opts ...Option) (*FileInfo, error)
PutBytes uploads byte data to storage. The filename is used to help with key generation but MIME type is detected from content.
func PutFile ¶
func PutFile(ctx context.Context, s Storage, fh *multipart.FileHeader, opts ...Option) (*FileInfo, error)
PutFile uploads a multipart file header to storage. MIME type is detected from magic bytes, not the filename extension. Returns ErrEmptyFile if the file is nil or has zero size. If WithValidation is used and any rule fails, returns *FileValidationError.
func PutFromURL ¶
func PutFromURL(ctx context.Context, s Storage, sourceURL string, maxSize int64, opts ...Option) (*FileInfo, error)
PutFromURL downloads a file from a URL and uploads it to storage. maxSize limits the download size (0 uses default from config). Returns ErrInvalidURL for malformed URLs. Returns ErrDownloadTooLarge if the file exceeds maxSize. Returns ErrDownloadFailed for network or HTTP errors.
type FileValidationError ¶
type FileValidationError struct {
Details map[string]any // Error-specific data
Field string // Form field name (e.g., "file")
Code string // Error code (e.g., "file_too_large", "invalid_mime", "empty_file")
Message string // Human-readable message
}
FileValidationError represents a file validation failure.
func (*FileValidationError) Error ¶
func (e *FileValidationError) Error() string
type Option ¶
type Option func(*putOptions)
Option configures Put operations.
func WithContentType ¶
WithContentType overrides the auto-detected content type. Use sparingly; auto-detection from magic bytes is preferred.
func WithKey ¶
WithKey sets an explicit storage key, replacing the auto-generated ULID-based key. Use this to overwrite an existing file at a specific location.
func WithPrefix ¶
WithPrefix sets a path prefix for the uploaded file. The prefix is added after the tenant (if any) and before the filename. Example: WithPrefix("avatars") results in "avatars/{ulid}.{ext}"
func WithTenant ¶
WithTenant sets a tenant ID for multi-tenant isolation. The tenant ID becomes the first path segment. Example: WithTenant("tenant123") results in "tenant123/{prefix}/{ulid}.{ext}"
func WithValidation ¶
func WithValidation(rules ...ValidationRule) Option
WithValidation adds validation rules to be applied before upload. If any rule fails, the upload is aborted and a *FileValidationError is returned.
type ReaderValidationRule ¶
ReaderValidationRule validates uploads using only Reader-available data (size and MIME type). Rules that implement this interface can be used with Put() to validate uploads from io.Reader.
type S3Storage ¶
type S3Storage struct {
// contains filtered or unexported fields
}
S3Storage implements Storage using S3-compatible object storage.
func (*S3Storage) Copy ¶
Copy copies a file from one key to another within the same bucket. S3 CopyObject preserves ACL by default.
func (*S3Storage) HeadObject ¶
HeadObject checks if a file exists and returns its metadata without downloading it.
func (*S3Storage) Put ¶
func (s *S3Storage) Put(ctx context.Context, r io.Reader, size int64, opts ...Option) (*FileInfo, error)
Put uploads data from a reader to S3.
type Storage ¶
type Storage interface {
// Put uploads data from a reader to storage.
// The size parameter is used for content-length header.
// Options can customize key, prefix, tenant, ACL, and content type.
Put(ctx context.Context, r io.Reader, size int64, opts ...Option) (*FileInfo, error)
// Get retrieves a file from storage.
// The caller is responsible for closing the returned reader.
Get(ctx context.Context, key string) (io.ReadCloser, error)
// Delete removes a file from storage.
Delete(ctx context.Context, key string) error
// URL generates a URL for accessing the file.
// For private files, returns a signed URL. For public files, returns the public URL.
// Use URLOptions to customize expiry, download disposition, or force signed/public.
URL(ctx context.Context, key string, opts ...URLOption) (string, error)
}
Storage defines the interface for file storage operations.
type URLOption ¶
type URLOption func(*urlOptions)
URLOption configures URL generation.
func WithDownload ¶
WithDownload sets the filename for Content-Disposition: attachment header. This forces the browser to download the file with the specified name. Also implies a signed URL.
func WithExpiry ¶
WithExpiry sets the expiry duration for signed URLs. Default is 15 minutes.
func WithPublic ¶
func WithPublic() URLOption
WithPublic forces a public URL regardless of the file's ACL. Note: This only works if the file was uploaded with ACLPublicRead or if the bucket has public access configured.
func WithSigned ¶
WithSigned forces a signed URL regardless of the file's ACL. Optionally set the expiry duration; if zero, uses default expiry.
type ValidationRule ¶
type ValidationRule interface {
// Validate checks the file and returns an error if validation fails.
Validate(fh *multipart.FileHeader, mimeType string) error
}
ValidationRule defines a validation check for file uploads.
func AllowedTypes ¶
func AllowedTypes(patterns ...string) ValidationRule
AllowedTypes returns a rule that only accepts files matching the given MIME patterns. Supports wildcards like "image/*".
func DocumentsOnly ¶
func DocumentsOnly() ValidationRule
DocumentsOnly returns a rule that only accepts document files. Includes PDF, Word, Excel, PowerPoint, text, and CSV files.
func ImageOnly ¶
func ImageOnly() ValidationRule
ImageOnly returns a rule that only accepts image files. Equivalent to AllowedTypes("image/*").
func MaxSize ¶
func MaxSize(bytes int64) ValidationRule
MaxSize returns a rule that rejects files larger than the specified size.
func MinSize ¶
func MinSize(bytes int64) ValidationRule
MinSize returns a rule that rejects files smaller than the specified size.