Trove

Driver System

Pluggable storage driver interface, DSN parsing, driver registry, and conformance testing.

Trove's storage layer is built on a pluggable driver interface. Every backend -- local filesystem, in-memory, S3, GCS, Azure Blob, SFTP -- implements the same driver.Driver interface. Your application code never imports a cloud SDK directly; it talks to the driver abstraction.

The Driver Interface

The driver.Driver interface defines the minimum surface area every storage backend must implement:

type Driver interface {
    // Identity & lifecycle
    Name() string
    Open(ctx context.Context, dsn string, opts ...Option) error
    Close(ctx context.Context) error
    Ping(ctx context.Context) error

    // Object operations
    Put(ctx context.Context, bucket, key string, r io.Reader, opts ...PutOption) (*ObjectInfo, error)
    Get(ctx context.Context, bucket, key string, opts ...GetOption) (*ObjectReader, error)
    Delete(ctx context.Context, bucket, key string, opts ...DeleteOption) error
    Head(ctx context.Context, bucket, key string) (*ObjectInfo, error)
    List(ctx context.Context, bucket string, opts ...ListOption) (*ObjectIterator, error)
    Copy(ctx context.Context, srcBucket, srcKey, dstBucket, dstKey string, opts ...CopyOption) (*ObjectInfo, error)

    // Bucket operations
    CreateBucket(ctx context.Context, name string, opts ...BucketOption) error
    DeleteBucket(ctx context.Context, name string) error
    ListBuckets(ctx context.Context) ([]BucketInfo, error)
}

Advanced features such as multipart uploads, pre-signed URLs, and versioning are exposed through optional capability interfaces rather than bloating the core contract.

DSN Format

Drivers are opened with a DSN (Data Source Name) string that encodes the backend type, credentials, host, path, and options:

scheme://[user:password@]host/path[?params]

Each driver defines its own DSN scheme:

DriverDSN ExampleDescription
Local filesystemfile:///var/data/storageHost-less path to a root directory
In-memorymem://No path or host required
Amazon S3s3://my-bucket?region=us-east-1Bucket in path, region as query param
Google Cloud Storagegs://my-bucketBucket in path
Azure Blob Storageazblob://my-containerContainer in path
SFTPsftp://user@host:22/uploadsSSH host with remote path

Parse a DSN programmatically with driver.ParseDSN:

cfg, err := driver.ParseDSN("s3://my-bucket?region=us-east-1&endpoint=http://localhost:9000")
// cfg.Scheme   = "s3"
// cfg.Path     = "/my-bucket"
// cfg.Params   = url.Values{"region": ["us-east-1"], "endpoint": ["http://localhost:9000"]}

Driver Registry

Drivers register themselves at init time using driver.Register. This enables DSN-based discovery without importing every driver package explicitly.

// In your driver package (e.g., s3driver/init.go):
func init() {
    driver.Register("s3", func() driver.Driver {
        return &S3Driver{}
    })
}

Look up and instantiate a driver by name:

factory, ok := driver.Lookup("s3")
if !ok {
    log.Fatal("s3 driver not registered")
}
drv := factory()
err := drv.Open(ctx, "s3://my-bucket?region=us-east-1")

List all registered drivers:

names := driver.Drivers() // ["s3", "gcs", "local", "mem", ...]

Conformance Test Suite

Every driver must pass the conformance suite defined in trovetest.RunDriverSuite. The suite exercises all core operations -- bucket CRUD, object put/get/delete/head/list/copy, pagination, metadata, and concurrency.

package mydriver_test

import (
    "testing"

    "github.com/xraph/trove/driver"
    "github.com/xraph/trove/trovetest"
)

func TestMyDriver(t *testing.T) {
    trovetest.RunDriverSuite(t, func(t *testing.T) driver.Driver {
        drv := mydriver.New()
        err := drv.Open(context.Background(), "myscheme:///tmp/test-data")
        if err != nil {
            t.Fatal(err)
        }
        t.Cleanup(func() { drv.Close(context.Background()) })
        return drv
    })
}

The suite covers:

  • Bucket lifecycle -- CreateBucket, DeleteBucket, ListBuckets, duplicate detection
  • Object CRUD -- Put, Get, Delete, Head with content types, metadata, and overwrite
  • Listing -- Empty listing, prefix filtering, pagination with cursors
  • Copy -- Within-bucket and cross-bucket copies, missing source detection
  • Concurrency -- 20 parallel puts and 20 parallel reads against the same bucket
  • Ping -- Backend connectivity verification

Building a Custom Driver

To build a custom driver, implement the driver.Driver interface, register it, and run the conformance suite:

package customdriver

import "github.com/xraph/trove/driver"

type Custom struct {
    // your fields
}

func New() *Custom { return &Custom{} }

func (c *Custom) Name() string { return "custom" }
func (c *Custom) Open(ctx context.Context, dsn string, opts ...driver.Option) error {
    cfg, err := driver.ParseDSN(dsn)
    if err != nil {
        return err
    }
    // Initialize your backend using cfg...
    return nil
}

// Implement remaining methods: Close, Ping, Put, Get, Delete,
// Head, List, Copy, CreateBucket, DeleteBucket, ListBuckets...

func init() {
    driver.Register("custom", func() driver.Driver { return New() })
}

For advanced features, implement the optional capability interfaces such as MultipartDriver or PresignDriver.

On this page