feat(executor): expose ctx.request.method to scripts (F4)
The request map exposed path/headers/body/params/query/rest but not the HTTP method, forcing one script per verb (the E2E app needed 7 scripts where ~3 would do). Add a `method` field to ExecRequest (populated from the HTTP method on the sync-execute bypass and the HTTP outbox payload; empty for non-HTTP triggers) and surface it as `ctx.request.method`, uppercased. A single script can now branch GET/POST on one route. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -485,6 +485,7 @@ fn build_ctx_map(req: &ExecRequest) -> Map {
|
||||
|
||||
let mut request = Map::new();
|
||||
request.insert("path".into(), req.path.clone().into());
|
||||
request.insert("method".into(), req.method.clone().into());
|
||||
|
||||
let mut headers = Map::new();
|
||||
for (k, v) in &req.headers {
|
||||
|
||||
@@ -171,6 +171,7 @@ fn invoke_blocking(
|
||||
script_name: resolved.name.clone(),
|
||||
invocation_type: InvocationType::Function,
|
||||
path: "/invoke".into(),
|
||||
method: String::new(),
|
||||
headers: BTreeMap::new(),
|
||||
body: args_json,
|
||||
params: BTreeMap::new(),
|
||||
|
||||
@@ -27,6 +27,15 @@ pub struct ExecRequest {
|
||||
pub script_name: String,
|
||||
pub invocation_type: InvocationType,
|
||||
pub path: String,
|
||||
|
||||
/// HTTP method of the originating request, uppercased (`GET`, `POST`,
|
||||
/// …). Exposed to scripts as `ctx.request.method` so a single script
|
||||
/// can branch on the verb instead of needing one script per method.
|
||||
/// Empty for non-HTTP trigger invocations (cron/kv/docs/…), matching
|
||||
/// how `path`/`rest` are empty there.
|
||||
#[serde(default)]
|
||||
pub method: String,
|
||||
|
||||
pub headers: BTreeMap<String, String>,
|
||||
pub body: serde_json::Value,
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ fn req(body: serde_json::Value) -> ExecRequest {
|
||||
script_name: "test".into(),
|
||||
invocation_type: InvocationType::Http,
|
||||
path: "/test".into(),
|
||||
method: String::new(),
|
||||
headers: BTreeMap::new(),
|
||||
body,
|
||||
params: BTreeMap::new(),
|
||||
@@ -103,6 +104,7 @@ fn ctx_exposes_request_data() {
|
||||
";
|
||||
let r = ExecRequest {
|
||||
path: "/payments".into(),
|
||||
method: String::new(),
|
||||
body: json!({ "amount": 1234 }),
|
||||
script_name: "payments".into(),
|
||||
..req(json!(null))
|
||||
@@ -114,6 +116,19 @@ fn ctx_exposes_request_data() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ctx_exposes_request_method() {
|
||||
// A single script can branch on the verb instead of needing one
|
||||
// script per method (E2E report F4).
|
||||
let src = r"#{ statusCode: 200, body: #{ method: ctx.request.method } }";
|
||||
let r = ExecRequest {
|
||||
method: "PATCH".into(),
|
||||
..req(json!(null))
|
||||
};
|
||||
let resp = engine().execute(src, r).unwrap();
|
||||
assert_eq!(resp.body, json!({ "method": "PATCH" }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn captures_log_calls() {
|
||||
let src = r#"
|
||||
|
||||
@@ -66,6 +66,7 @@ fn req(app_id: AppId) -> ExecRequest {
|
||||
script_name: "redaction-test".into(),
|
||||
invocation_type: InvocationType::Http,
|
||||
path: "/x".into(),
|
||||
method: String::new(),
|
||||
headers: BTreeMap::new(),
|
||||
body: Value::Null,
|
||||
params: BTreeMap::new(),
|
||||
|
||||
@@ -120,6 +120,7 @@ fn req(app_id: AppId) -> ExecRequest {
|
||||
script_name: "test".into(),
|
||||
invocation_type: InvocationType::Http,
|
||||
path: "/test".into(),
|
||||
method: String::new(),
|
||||
headers: BTreeMap::new(),
|
||||
body: serde_json::Value::Null,
|
||||
params: BTreeMap::new(),
|
||||
|
||||
@@ -43,6 +43,7 @@ fn baseline_request() -> ExecRequest {
|
||||
script_name: "contract".into(),
|
||||
invocation_type: InvocationType::Http,
|
||||
path: "/contract-test".into(),
|
||||
method: String::new(),
|
||||
headers: BTreeMap::new(),
|
||||
body: Value::Null,
|
||||
params: BTreeMap::new(),
|
||||
|
||||
@@ -248,6 +248,7 @@ fn baseline_request(app_id: AppId) -> ExecRequest {
|
||||
script_name: "docs-test".into(),
|
||||
invocation_type: InvocationType::Http,
|
||||
path: "/docs-test".into(),
|
||||
method: String::new(),
|
||||
headers: BTreeMap::new(),
|
||||
body: Value::Null,
|
||||
params: BTreeMap::new(),
|
||||
|
||||
@@ -57,6 +57,7 @@ fn baseline_request(app_id: AppId) -> ExecRequest {
|
||||
script_name: "email-test".into(),
|
||||
invocation_type: InvocationType::Http,
|
||||
path: "/email-test".into(),
|
||||
method: String::new(),
|
||||
headers: BTreeMap::new(),
|
||||
body: Value::Null,
|
||||
params: BTreeMap::new(),
|
||||
|
||||
@@ -185,6 +185,7 @@ fn baseline_request(app_id: AppId) -> ExecRequest {
|
||||
script_name: "files-test".into(),
|
||||
invocation_type: InvocationType::Http,
|
||||
path: "/files-test".into(),
|
||||
method: String::new(),
|
||||
headers: BTreeMap::new(),
|
||||
body: Value::Null,
|
||||
params: BTreeMap::new(),
|
||||
|
||||
@@ -108,6 +108,7 @@ fn baseline_request(app_id: AppId, script_id: ScriptId) -> ExecRequest {
|
||||
script_name: "http-test".into(),
|
||||
invocation_type: InvocationType::Http,
|
||||
path: "/http-test".into(),
|
||||
method: String::new(),
|
||||
headers: BTreeMap::new(),
|
||||
body: Value::Null,
|
||||
params: BTreeMap::new(),
|
||||
|
||||
@@ -105,6 +105,7 @@ fn baseline_request(app_id: AppId) -> ExecRequest {
|
||||
script_name: "caller".into(),
|
||||
invocation_type: InvocationType::Http,
|
||||
path: "/caller".into(),
|
||||
method: String::new(),
|
||||
headers: BTreeMap::new(),
|
||||
body: Value::Null,
|
||||
params: BTreeMap::new(),
|
||||
|
||||
@@ -127,6 +127,7 @@ fn baseline_request(app_id: AppId) -> ExecRequest {
|
||||
script_name: "kv-test".into(),
|
||||
invocation_type: InvocationType::Http,
|
||||
path: "/kv-test".into(),
|
||||
method: String::new(),
|
||||
headers: BTreeMap::new(),
|
||||
body: Value::Null,
|
||||
params: BTreeMap::new(),
|
||||
|
||||
@@ -65,6 +65,7 @@ fn baseline_request(app_id: AppId) -> ExecRequest {
|
||||
script_name: "pubsub-test".into(),
|
||||
invocation_type: InvocationType::Http,
|
||||
path: "/pubsub-test".into(),
|
||||
method: String::new(),
|
||||
headers: BTreeMap::new(),
|
||||
body: Value::Null,
|
||||
params: BTreeMap::new(),
|
||||
|
||||
@@ -79,6 +79,7 @@ fn baseline_request(app_id: AppId) -> ExecRequest {
|
||||
script_name: "queue-test".into(),
|
||||
invocation_type: InvocationType::Http,
|
||||
path: "/queue-test".into(),
|
||||
method: String::new(),
|
||||
headers: BTreeMap::new(),
|
||||
body: Value::Null,
|
||||
params: BTreeMap::new(),
|
||||
|
||||
@@ -43,6 +43,7 @@ fn baseline_request(app_id: AppId) -> ExecRequest {
|
||||
script_name: "retry-test".into(),
|
||||
invocation_type: InvocationType::Http,
|
||||
path: "/retry-test".into(),
|
||||
method: String::new(),
|
||||
headers: BTreeMap::new(),
|
||||
body: Value::Null,
|
||||
params: BTreeMap::new(),
|
||||
|
||||
@@ -118,6 +118,7 @@ fn baseline_request(app_id: AppId) -> ExecRequest {
|
||||
script_name: "secrets-test".into(),
|
||||
invocation_type: InvocationType::Http,
|
||||
path: "/secrets-test".into(),
|
||||
method: String::new(),
|
||||
headers: BTreeMap::new(),
|
||||
body: Value::Null,
|
||||
params: BTreeMap::new(),
|
||||
|
||||
@@ -112,6 +112,7 @@ fn request(app_id: AppId, with_principal: bool) -> ExecRequest {
|
||||
script_name: "token-test".into(),
|
||||
invocation_type: InvocationType::Http,
|
||||
path: "/token-test".into(),
|
||||
method: String::new(),
|
||||
headers: BTreeMap::new(),
|
||||
body: Value::Null,
|
||||
params: BTreeMap::new(),
|
||||
|
||||
@@ -29,6 +29,7 @@ fn baseline_request() -> ExecRequest {
|
||||
script_name: "stdlib".into(),
|
||||
invocation_type: InvocationType::Http,
|
||||
path: "/stdlib-test".into(),
|
||||
method: String::new(),
|
||||
headers: BTreeMap::new(),
|
||||
body: Value::Null,
|
||||
params: BTreeMap::new(),
|
||||
|
||||
@@ -357,6 +357,8 @@ impl Dispatcher {
|
||||
script_name: script.name.clone(),
|
||||
invocation_type: InvocationType::Function,
|
||||
path: "/trigger/queue".into(),
|
||||
// Non-HTTP trigger invocation — no request method.
|
||||
method: String::new(),
|
||||
headers: std::collections::BTreeMap::new(),
|
||||
body: serde_json::Value::Null,
|
||||
params: std::collections::BTreeMap::new(),
|
||||
@@ -658,6 +660,8 @@ impl Dispatcher {
|
||||
script_name: resolved.script_name.clone(),
|
||||
invocation_type: InvocationType::Function,
|
||||
path: format!("/trigger/{}", trigger_event.source()),
|
||||
// Non-HTTP trigger invocation — no request method.
|
||||
method: String::new(),
|
||||
headers: std::collections::BTreeMap::new(),
|
||||
body: serde_json::Value::Null,
|
||||
params: std::collections::BTreeMap::new(),
|
||||
@@ -719,6 +723,7 @@ impl Dispatcher {
|
||||
script_name: payload.script_name.clone(),
|
||||
invocation_type: InvocationType::Http,
|
||||
path: payload.path.clone(),
|
||||
method: payload.method.clone(),
|
||||
headers: payload.headers,
|
||||
body: payload.body,
|
||||
params: payload.params,
|
||||
@@ -814,6 +819,7 @@ impl Dispatcher {
|
||||
script_name: script.name.clone(),
|
||||
invocation_type: InvocationType::Function,
|
||||
path: "/invoke_async".into(),
|
||||
method: String::new(),
|
||||
headers: std::collections::BTreeMap::new(),
|
||||
body: args,
|
||||
params: std::collections::BTreeMap::new(),
|
||||
|
||||
@@ -584,6 +584,8 @@ fn build_exec_request(
|
||||
script_name: name.to_string(),
|
||||
invocation_type: InvocationType::Http,
|
||||
path: format!("/api/execute/{id}"),
|
||||
// The `/api/v1/execute/{id}` bypass is POST-only.
|
||||
method: "POST".into(),
|
||||
headers: hmap,
|
||||
body: body_json,
|
||||
params: BTreeMap::new(),
|
||||
|
||||
Reference in New Issue
Block a user