Summary
All mock responses currently return only Content-Type: application/json. There is no way to set custom response headers, serve non-JSON content types, add CORS headers, set Location on redirects, or simulate Cache-Control / X-RateLimit-* headers.
Motivation
Real-world use cases that are blocked today:
- CORS: Browser-based apps need
Access-Control-Allow-Origin on every response
- Redirects:
301 Moved Permanently requires a Location header
- Non-JSON responses: Some APIs return XML, CSV, plain text, or HTML
- Rate limit simulation:
X-RateLimit-Remaining: 0, Retry-After: 60
- Caching headers:
Cache-Control: no-store, ETag: "abc123"
- Auth challenges:
WWW-Authenticate: Bearer realm="api"
Proposed Configuration
{
"method": "GET",
"path": "/data.xml",
"status": 200,
"response_headers": {
"Content-Type": "application/xml; charset=utf-8",
"Cache-Control": "no-cache",
"X-Custom-Header": "my-value"
},
"response": "<users><user id=\"1\"/></users>"
}
{
"method": "POST",
"path": "/resources",
"status": 201,
"response_headers": {
"Location": "/resources/99",
"X-Request-Id": "abc-123"
},
"response": { "id": 99 }
}
Implementation Plan
src/types.rs
pub struct MockConfig {
// existing fields ...
/// Optional custom response headers
#[serde(default, skip_serializing_if = "Option::is_none")]
pub response_headers: Option<HashMap<String, String>>,
}
src/handler.rs
Replace the simple (status, Json(body)).into_response() with a Response::builder() approach:
let mut builder = Response::builder().status(status);
// Inject custom headers
if let Some(ref custom_headers) = mock.response_headers {
for (name, value) in custom_headers {
builder = builder.header(name, value);
}
}
// Default Content-Type if not overridden
let has_content_type = mock.response_headers
.as_ref()
.map(|h| h.keys().any(|k| k.eq_ignore_ascii_case("content-type")))
.unwrap_or(false);
if !has_content_type {
builder = builder.header(CONTENT_TYPE, "application/json");
}
let body = serde_json::to_string(&mock.response).unwrap_or_default();
builder.body(Body::from(body)).unwrap().into_response()
Backward Compatibility
When response_headers is absent (the current default), behavior is identical to today — Content-Type: application/json is set automatically.
Acceptance Criteria
Summary
All mock responses currently return only
Content-Type: application/json. There is no way to set custom response headers, serve non-JSON content types, add CORS headers, setLocationon redirects, or simulateCache-Control/X-RateLimit-*headers.Motivation
Real-world use cases that are blocked today:
Access-Control-Allow-Originon every response301 Moved Permanentlyrequires aLocationheaderX-RateLimit-Remaining: 0,Retry-After: 60Cache-Control: no-store,ETag: "abc123"WWW-Authenticate: Bearer realm="api"Proposed Configuration
{ "method": "GET", "path": "/data.xml", "status": 200, "response_headers": { "Content-Type": "application/xml; charset=utf-8", "Cache-Control": "no-cache", "X-Custom-Header": "my-value" }, "response": "<users><user id=\"1\"/></users>" }{ "method": "POST", "path": "/resources", "status": 201, "response_headers": { "Location": "/resources/99", "X-Request-Id": "abc-123" }, "response": { "id": 99 } }Implementation Plan
src/types.rssrc/handler.rsReplace the simple
(status, Json(body)).into_response()with aResponse::builder()approach:Backward Compatibility
When
response_headersis absent (the current default), behavior is identical to today —Content-Type: application/jsonis set automatically.Acceptance Criteria
response_headersfield added toMockConfig(optional)Content-Type: application/jsonis NOT added whenresponse_headerscontains aContent-Type