Documentation
¶
Overview ¶
Package protocol provides shared AWS protocol helpers used by all service handlers: error serialisation, request IDs, ARN construction, and response writing. Nothing in this package is service-specific.
Index ¶
- Constants
- Variables
- func APIV2ARN(region, apiID string) string
- func ARN(region, accountID, service, resource string) string
- func Cause(aerr *AWSError) error
- func ContextWithRequestID(ctx context.Context, id string) context.Context
- func DistributionARN(accountID, distributionID string) string
- func LambdaARN(region, accountID, functionName string) string
- func LambdaCodeSigningConfigARN(region, accountID, cscID string) string
- func LambdaVersionARN(region, accountID, functionName string, version int) string
- func LayerARN(region, accountID, layerName string) string
- func LayerVersionARN(region, accountID, layerName string, version int) string
- func LogGroupARN(region, accountID, groupName string) string
- func LogStreamARN(region, accountID, groupName, streamName string) string
- func MethodNotAllowedXML(w http.ResponseWriter, r *http.Request)
- func NewRequestID() string
- func NotImplementedEC2QueryXML(w http.ResponseWriter, r *http.Request)
- func NotImplementedJSON(w http.ResponseWriter, r *http.Request)
- func NotImplementedQueryXML(w http.ResponseWriter, r *http.Request)
- func NotImplementedXML(w http.ResponseWriter, r *http.Request)
- func ParseFormPreservingBody(r *http.Request) error
- func QueueARN(region, accountID, queueName string) string
- func RequestIDFromContext(ctx context.Context) string
- func RestAPIARN(region, apiID string) string
- func ServiceFromARN(arn string) string
- func TableARN(region, accountID, tableName string) string
- func TopicARN(region, accountID, topicName string) string
- func WriteAWSJSON(w http.ResponseWriter, r *http.Request, status int, v any, contentType string)
- func WriteEC2QueryXMLError(w http.ResponseWriter, r *http.Request, aerr *AWSError)
- func WriteEmpty(w http.ResponseWriter, r *http.Request, status int)
- func WriteJSON(w http.ResponseWriter, r *http.Request, status int, v any)
- func WriteJSONError(w http.ResponseWriter, r *http.Request, aerr *AWSError)
- func WriteQueryXML(w http.ResponseWriter, r *http.Request, status int, v any)
- func WriteQueryXMLError(w http.ResponseWriter, r *http.Request, aerr *AWSError)
- func WriteXML(w http.ResponseWriter, r *http.Request, status int, v any)
- func WriteXMLError(w http.ResponseWriter, r *http.Request, aerr *AWSError)
- type AWSError
- type ResponseMetadata
Constants ¶
const MaxQueryFormBody = 1 << 20 // 1 MiB
MaxQueryFormBody bounds how much of a request body is ever read into memory in order to treat it as an AWS Query form — by ParseFormPreservingBody here, and by the Query codec's direct-body-read fallback. Real Query requests are far smaller: the largest inline payloads AWS accepts on this protocol are SNS Publish and SQS SendMessageBatch at 256 KiB and a CloudFormation inline TemplateBody at 51,200 bytes. This is generous headroom, not a realistic limit for legitimate traffic. Anything past it is not a Query request, and buffering it would mean holding an arbitrarily large S3 upload in memory.
Variables ¶
var ( // ErrNotImplemented is returned for endpoints that exist in the routing // table but have not yet been implemented. The x-emulator-unsupported // header is added automatically by WriteXMLError / WriteJSONError. ErrNotImplemented = &AWSError{ Code: "NotImplemented", Message: "This operation is not yet emulated. Check docs/services/ for the support matrix.", HTTPStatus: http.StatusNotImplemented, } // ErrMethodNotAllowed is returned for a method AWS itself rejects on a // resource — not for something Overcast has yet to implement. The two are // different claims: NotImplemented tells a client the emulator is // incomplete and invites a workaround, when real AWS would refuse the same // request. S3's wording, verbatim. ErrMethodNotAllowed = &AWSError{ Code: "MethodNotAllowed", Message: "The specified method is not allowed against this resource.", HTTPStatus: http.StatusMethodNotAllowed, } // ErrInternalError is returned when the emulator itself encounters an // unexpected failure (state backend error, serialisation failure, etc.). // Always Wrap() this with the underlying error for log context. ErrInternalError = &AWSError{ Code: "InternalError", Message: "An internal error occurred.", HTTPStatus: http.StatusInternalServerError, } // ErrStorageMigrating is returned by middleware.NotReady while the // storage backend is still completing a one-time schema migration on // startup (see internal/state/migrate.go) and cannot yet serve requests // reliably — without this check, a request in this window would either // block indefinitely (persistent mode) or silently observe incomplete // state as if it simply didn't exist (hybrid mode's TierHot reads, // before the post-migration seed has populated memory). "ServiceUnavailable" // is a real AWS error code multiple services return for their own // transient unavailability, so AWS SDKs already retry it automatically // per their standard retry policy — a client normally needs no special // handling for this. ErrStorageMigrating = &AWSError{ Code: "ServiceUnavailable", Message: "Overcast is completing a one-time database migration and is not yet ready to serve requests. This happens after an upgrade or first startup against existing data and should resolve within moments — retry the request.", HTTPStatus: http.StatusServiceUnavailable, } )
var ErrFormBodyTooLarge = errors.New("protocol: request body exceeds the AWS Query form parse limit")
ErrFormBodyTooLarge reports that a request body exceeded MaxQueryFormBody, so its form fields were not parsed. The body is left intact and readable.
Functions ¶
func APIV2ARN ¶
APIV2ARN builds an API Gateway v2 (HTTP/WebSocket) API ARN. Format: arn:aws:apigateway:{region}::/apis/{apiId}.
func ARN ¶
ARN builds an AWS ARN string. Format: arn:aws:<service>:<region>:<accountID>:<resource>
Examples:
ARN("us-east-1", "000000000000", "s3", "my-bucket")
→ "arn:aws:s3:::my-bucket" (S3 omits region and account)
ARN("us-east-1", "000000000000", "sqs", "my-queue")
→ "arn:aws:sqs:us-east-1:000000000000:my-queue"
func Cause ¶
Cause returns the underlying cause of aerr, or nil if none was set. Prefer errors.Is / errors.As over Cause for most use cases — they traverse arbitrarily deep chains. Use Cause only when you need the immediate cause.
func ContextWithRequestID ¶
ContextWithRequestID returns a new context carrying the given request ID. Called by the request-ID middleware on every incoming request.
func DistributionARN ¶
DistributionARN builds a CloudFront distribution ARN. CloudFront ARNs omit the region — this is an AWS quirk. Format: arn:aws:cloudfront::{accountID}:distribution/{distributionID}.
func LambdaCodeSigningConfigARN ¶
LambdaCodeSigningConfigARN builds a Lambda code signing configuration ARN. Format: arn:aws:lambda:{region}:{account}:code-signing-config:{cscID}.
func LambdaVersionARN ¶
LambdaVersionARN builds a Lambda function ARN with a numeric version qualifier. Format: arn:aws:lambda:{region}:{account}:function:{name}:{version}.
func LayerARN ¶
LayerARN builds the unversioned Lambda layer ARN (no version suffix). Format: arn:aws:lambda:{region}:{account}:layer:{name}.
func LayerVersionARN ¶
LayerVersionARN builds a Lambda layer version ARN. Format: arn:aws:lambda:{region}:{account}:layer:{name}:{version}.
func LogGroupARN ¶
LogGroupARN builds a CloudWatch Logs log group ARN.
func LogStreamARN ¶
LogStreamARN builds a CloudWatch Logs log stream ARN.
func MethodNotAllowedXML ¶
func MethodNotAllowedXML(w http.ResponseWriter, r *http.Request)
MethodNotAllowedXML is a convenience handler for a method AWS rejects on a resource, for use as a dispatch fallback where no subresource selects an operation.
func NewRequestID ¶
func NewRequestID() string
NewRequestID generates a new unique AWS-style request ID. AWS uses UUID v4 format without hyphens in some services, with hyphens in others. We always use with-hyphens — both forms are accepted by SDKs.
func NotImplementedEC2QueryXML ¶
func NotImplementedEC2QueryXML(w http.ResponseWriter, r *http.Request)
NotImplementedEC2QueryXML writes a 501 for unimplemented EC2 Query operations.
func NotImplementedJSON ¶
func NotImplementedJSON(w http.ResponseWriter, r *http.Request)
NotImplementedJSON is a convenience handler for unimplemented JSON endpoints.
func NotImplementedQueryXML ¶
func NotImplementedQueryXML(w http.ResponseWriter, r *http.Request)
NotImplementedQueryXML writes a 501 for unimplemented Query-protocol operations.
func NotImplementedXML ¶
func NotImplementedXML(w http.ResponseWriter, r *http.Request)
NotImplementedXML is a convenience handler for unimplemented S3 endpoints.
func ParseFormPreservingBody ¶
ParseFormPreservingBody populates r.Form and r.PostForm the way (*http.Request).ParseForm does, but leaves r.Body readable afterwards.
This exists because Content-Type is not a routing decision. The standard library's ParseForm drains r.Body for any POST/PUT/PATCH labelled application/x-www-form-urlencoded, so shared code that sniffs a request for AWS Query fields — protocol detection, IAM action extraction — would silently swallow the payload of a request that turned out to belong to a REST service. S3 PutObject is the case that bites: `curl --data-binary` sends that content type by default, and AWS stores the body regardless.
Callers that only ever see genuine Query traffic can keep using ParseForm; anything running ahead of routing must use this.
func RequestIDFromContext ¶
RequestIDFromContext retrieves the request ID stored in ctx. Returns a freshly generated ID if none was set — this is a safety net and should not happen in normal operation (the middleware always sets one).
func RestAPIARN ¶
RestAPIARN builds an API Gateway REST API ARN. API Gateway ARNs omit the account ID — this is an AWS quirk. Format: arn:aws:apigateway:{region}::/restapis/{apiId}.
func ServiceFromARN ¶
ServiceFromARN extracts the service name (e.g. "appsync", "kafka") from an ARN's third colon-delimited segment ("arn:partition:service:..."). Returns "" if arn is not a well-formed ARN string.
This is the authoritative way to identify which service owns a resource when a path is shared between services (e.g. /v1/tags/{resourceArn}) — the ARN is self-describing, unlike the request's SigV4 credential scope, which callers may not always set to match (hand-built requests, or resource-group-style tagging tools that sign generically).
func WriteAWSJSON ¶
WriteAWSJSON serialises v as JSON and writes it with the provided AWS JSON content type and request ID headers.
Pass an explicit content type per service protocol, for example:
- application/x-amz-json-1.0
- application/x-amz-json-1.1
If contentType is empty, application/x-amz-json-1.0 is used.
func WriteEC2QueryXMLError ¶
func WriteEC2QueryXMLError(w http.ResponseWriter, r *http.Request, aerr *AWSError)
WriteEC2QueryXMLError writes an EC2 Query-protocol XML error response.
func WriteEmpty ¶
func WriteEmpty(w http.ResponseWriter, r *http.Request, status int)
WriteEmpty writes a response with no body and the standard request ID header. Used for operations like DeleteObject which return 204 or an empty 200.
func WriteJSON ¶
WriteJSON serialises v as JSON and writes it with the correct Content-Type and request ID headers. Services call this for successful responses.
func WriteJSONError ¶
func WriteJSONError(w http.ResponseWriter, r *http.Request, aerr *AWSError)
WriteJSONError writes an AWS JSON-protocol error response. The cause, if any, is NOT included in the response body.
func WriteQueryXML ¶
WriteQueryXML serialises v as XML with text/xml content type. Used by Query-protocol services (SNS, STS, IAM). The request ID is set as both a response header and should be embedded in the response struct's ResponseMetadata. The request body is drained so the HTTP/1.1 connection can be reused by the SDK client.
func WriteQueryXMLError ¶
func WriteQueryXMLError(w http.ResponseWriter, r *http.Request, aerr *AWSError)
WriteQueryXMLError writes an AWS Query-protocol XML error response (SNS format).
func WriteXML ¶
WriteXML serialises v as XML and writes it with the correct Content-Type and request ID headers. Services call this for successful responses.
func WriteXMLError ¶
func WriteXMLError(w http.ResponseWriter, r *http.Request, aerr *AWSError)
WriteXMLError writes an AWS REST-XML error response (S3 format). The cause, if any, is NOT included in the response — it stays server-side for logging. The x-emulator-unsupported header is set automatically for 501.
Types ¶
type AWSError ¶
type AWSError struct {
// Code is the AWS error code string, e.g. "NoSuchBucket", "QueueDoesNotExist".
Code string
// Message is the human-readable description sent to the client.
Message string
// HTTPStatus is the HTTP status code to send, e.g. 404, 400, 500.
HTTPStatus int
// contains filtered or unexported fields
}
AWSError represents a structured AWS API error that maps to an HTTP response.
It implements the standard error interface and supports Go's error wrapping convention, equivalent to JavaScript's `new Error("msg", { cause: err })`.
Wrapping pattern — use Wrap() to attach an underlying cause while presenting a clean AWS error to callers:
// Service code:
raw, found, err := s.store.Get(ctx, ns, key)
if err != nil {
return protocol.Wrap(protocol.ErrInternalError, err)
}
// The HTTP layer sees ErrInternalError (clean AWS error code + message).
// The original err is preserved in the chain for logging and debugging:
logger.Error("state read failed", zap.Error(aerr))
// → logs both "InternalError" and the underlying storage error
Inspection pattern — anywhere in the call chain:
var aerr *protocol.AWSError
if errors.As(err, &aerr) {
// aerr.Code, aerr.HTTPStatus available
}
func AsAWSError ¶
AsAWSError extracts the first *AWSError from the error chain. Returns nil if no *AWSError is found.
This is a convenience wrapper around errors.As for the common case of checking whether an error from a helper function is an AWSError:
aerr := protocol.AsAWSError(err)
if aerr != nil {
protocol.WriteJSONError(w, r, aerr)
return
}
func ErrInvalidArgument ¶
ErrInvalidArgument returns a 400 error for malformed input.
func ErrMissingParameter ¶
ErrMissingParameter returns a 400 error for a missing required parameter.
func ErrSerialization ¶
ErrSerialization returns the 400 error real AWS JSON-protocol services use for a request body that cannot be parsed at all — DynamoDB and other coral-framework services return __type com.amazon.coral.service#SerializationException, and Smithy's malformed-request protocol tests pin 400 + x-amzn-errortype: SerializationException. Use this for parse-level failures only; semantic validation of a successfully parsed request stays ValidationException / InvalidArgument / service-specific codes.
func Wrap ¶
Wrap returns a new *AWSError with the same Code, Message, and HTTPStatus as template, but with cause attached as the underlying error.
This is the primary way to preserve error context in service code:
func (s *s3Store) getBucket(ctx context.Context, name string) (*Bucket, *AWSError) {
raw, found, err := s.store.Get(ctx, nsBuckets, name)
if err != nil {
// ErrInternalError is shown to the client.
// err (e.g. "sqlite: no such table") is preserved for logging.
return nil, protocol.Wrap(ErrInternalError, err)
}
...
}
Wrap never modifies the template — it always returns a new AWSError value.
func (*AWSError) Error ¶
Error implements the error interface. Returns the AWS error code and message; the cause is accessible via Unwrap.
func (*AWSError) Unwrap ¶
Unwrap returns the underlying cause, enabling errors.Is / errors.As to traverse the full error chain. This is the Go equivalent of error.cause in JavaScript.
// Check if a specific underlying error occurred:
if errors.Is(aerr, sql.ErrNoRows) { ... }
// Extract a specific error type from anywhere in the chain:
var pgErr *pgconn.PgError
if errors.As(aerr, &pgErr) { ... }
type ResponseMetadata ¶
type ResponseMetadata struct {
RequestID string `xml:"RequestId"`
}
ResponseMetadata is embedded in AWS Query-protocol XML responses. It carries the request ID that SDKs surface as response.ResultMetadata.
func QueryResponseMetadata ¶
func QueryResponseMetadata(r *http.Request) ResponseMetadata
QueryResponseMetadata returns a ResponseMetadata populated from the request context.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package codec defines the wire-protocol abstraction used by Overcast's typed operation dispatcher.
|
Package codec defines the wire-protocol abstraction used by Overcast's typed operation dispatcher. |
|
Package op defines the typed operation dispatcher used by Overcast's Smithy-aligned services.
|
Package op defines the typed operation dispatcher used by Overcast's Smithy-aligned services. |