← all adapters

aws-s3-style

simulates Amazon S3 API 2006-03-01

restadapter v0.2.09 routes

verification

SDKverified with aws-sdk-go-v2 v1.47.1· surface 9 · not implemented 9 · differs 16

use it

# add to stunt.yaml
services:
  aws-s3:
    adapter: embedded:aws-s3-style

# then
$ stunt up

The embedded: source extracts this adapter from the binary — no git clone, fully offline.

how to mock the Amazon S3 API locally

Install stunt, paste the service block above into stunt.yaml, run stunt up — then point your client at localhost instead of Amazon S3 API. It works offline, keeps state across requests and restarts, and every response is synthetic: the route shapes, status codes, pagination, and webhook delivery follow the real Amazon S3 API 2006-03-01 surface — the data is fake, the behavior is real.

backing stores

bucketsobjects

api surface (9)

generated from the adapter manifest — exactly what boots, nothing more

9 routes
GET/{bucket}
POST/{bucket}
PUT/{bucket}
DELETE/{bucket}
PUT/{bucket}/{key+}
GET/{bucket}/{key+}
HEAD/{bucket}/{key+}
DELETE/{bucket}/{key+}
POST/{bucket}/{key+}

SDK-verified behaviors (16)

  • ✓CreateBucket
  • ✓PutObject (binary body)
  • ✓GetObject byte-exact round-trip (incl. non-UTF-8)
  • ✓HeadObject metadata
  • ✓ListObjectsV2 paginator follows continuation (4 over MaxKeys=2)
  • ✓DeleteObject
  • ✓CreateMultipartUpload
  • ✓UploadPart
  • ✓ListParts
  • ✓DeleteBucket refused while a multipart upload is in progress
  • ✓AbortMultipartUpload releases the blocked bucket delete
  • ✓CompleteMultipartUpload with the SDK's XML-escaped ETag
  • ✓Object tagging subresources refused without touching the object
  • ✓Presigned GET URL (real presigner, signed headers sent)
  • ✓Presigned DELETE (real presigner)
  • ✓Presigned GET rejects a tampered signature

Not implemented (9)

  • No bucket policy, tagging, ACL, CORS, lifecycle, or website subresources
  • No object versioning (?versions listing, versionId delete) or object lock
  • No CopyObject (x-amz-copy-source) or UploadPartCopy
  • No browser form POST uploads (POST policy) — POST /{bucket} returns 501 NotImplemented when SigV4-signed, but a real browser form upload (which authenticates with form fields, so carries no Authorization header) is refused earlier with 403 MissingSecurityHeader
  • No object-lock configuration subresource (?object-lock) on the bucket
  • No ListMultipartUploads (GET /{bucket}?uploads returns 501 NotImplemented)
  • No multi-object DeleteObjects (POST /{bucket}?delete returns 501 NotImplemented)
  • No object-level subresources: ?acl, ?tagging, ?retention, ?legal-hold, ?torrent, ?attributes, ?annotation, ?encryption — 501 NotImplemented rather than falling through to the object operation
  • No SelectObjectContent (?select) or RestoreObject (?restore) — POST verbs, answered 501 NotImplemented (a bare POST /{bucket}/{key} with no query is 405)

Differs from the real API (16)

  • ETags are MD5 hex (multipart MD5(binary-concat)-N)
  • Multipart 5 MiB minimum part size not enforced (small parts allowed)
  • DELETE of a missing bucket is an idempotent 204 (real S3: 404 NoSuchBucket)
  • DeleteBucket is refused with 409 BucketNotEmpty while a multipart upload is in progress — AWS documents and enforces this for directory buckets, while for general-purpose buckets it documents only that all objects must be deleted and says nothing about an upload in progress, so refusing the delete is an inference there
  • Unimplemented subresources return 501 NotImplemented rather than falling through to the object operation on the same route. The mutating routes use an allowlist and reject every query parameter except the SigV4 presigned-auth ones and the route's own; the read routes use a denylist. The guard runs before the multipart dispatch
  • Starlark handlers are step-bounded: a single ListObjectsV2 or DeleteBucket over a large enough bucket exhausts the budget and returns a JSON 500 rather than S3 XML. The reachable threshold for ListObjectsV2 is a few hundred objects in one un-paginated page; DeleteBucket did not reproduce it in a few hundred rows
  • x-amz-meta-* suffixes are lowercased and the first occurrence wins
  • On mutating routes (bucket PUT/DELETE, object PUT/POST/DELETE) x-id must name an operation this adapter routes; the SDK's x-id=CopyObject, x-id=RestoreObject, x-id=SelectObjectContent, x-id=PutObjectLegalHold and x-id=UploadPartCopy all return 501 rather than falling through. AbortMultipartUpload is routed and must not be refused
  • CopyObject selected by the x-amz-copy-source header alone, with no x-id query parameter, still writes a zero-byte object; the query allowlist cannot see a header-selected subresource
  • PUT /{bucket}/{key}?partNumber=N without uploadId returns 400 InvalidRequest rather than falling through to PutObject
  • Per-chunk STREAMING signatures not verified (header SigV4 only)
  • Streaming checksum trailers discarded (unsupported-checksum)
  • DELETE object against a missing bucket is 204 (real S3: 404 NoSuchBucket)
  • GET/HEAD object against a missing bucket is 404 NoSuchKey (real S3: 404 NoSuchBucket)
  • SigV4 canonical URI/query rebuilt from decoded values — duplicates indistinguishable
  • No RFC 1123 Date fallback; canonical header whitespace collapsing not applied
want more of the Amazon S3 API surface? adapters are YAML + Starlark — the authoring guide covers adding routes, fixtures, and state machines. Open an issue for anything big first, or just file a PR — merged routes ship in the next release.