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();
|
let mut request = Map::new();
|
||||||
request.insert("path".into(), req.path.clone().into());
|
request.insert("path".into(), req.path.clone().into());
|
||||||
|
request.insert("method".into(), req.method.clone().into());
|
||||||
|
|
||||||
let mut headers = Map::new();
|
let mut headers = Map::new();
|
||||||
for (k, v) in &req.headers {
|
for (k, v) in &req.headers {
|
||||||
|
|||||||
@@ -171,6 +171,7 @@ fn invoke_blocking(
|
|||||||
script_name: resolved.name.clone(),
|
script_name: resolved.name.clone(),
|
||||||
invocation_type: InvocationType::Function,
|
invocation_type: InvocationType::Function,
|
||||||
path: "/invoke".into(),
|
path: "/invoke".into(),
|
||||||
|
method: String::new(),
|
||||||
headers: BTreeMap::new(),
|
headers: BTreeMap::new(),
|
||||||
body: args_json,
|
body: args_json,
|
||||||
params: BTreeMap::new(),
|
params: BTreeMap::new(),
|
||||||
|
|||||||
@@ -27,6 +27,15 @@ pub struct ExecRequest {
|
|||||||
pub script_name: String,
|
pub script_name: String,
|
||||||
pub invocation_type: InvocationType,
|
pub invocation_type: InvocationType,
|
||||||
pub path: String,
|
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 headers: BTreeMap<String, String>,
|
||||||
pub body: serde_json::Value,
|
pub body: serde_json::Value,
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ fn req(body: serde_json::Value) -> ExecRequest {
|
|||||||
script_name: "test".into(),
|
script_name: "test".into(),
|
||||||
invocation_type: InvocationType::Http,
|
invocation_type: InvocationType::Http,
|
||||||
path: "/test".into(),
|
path: "/test".into(),
|
||||||
|
method: String::new(),
|
||||||
headers: BTreeMap::new(),
|
headers: BTreeMap::new(),
|
||||||
body,
|
body,
|
||||||
params: BTreeMap::new(),
|
params: BTreeMap::new(),
|
||||||
@@ -103,6 +104,7 @@ fn ctx_exposes_request_data() {
|
|||||||
";
|
";
|
||||||
let r = ExecRequest {
|
let r = ExecRequest {
|
||||||
path: "/payments".into(),
|
path: "/payments".into(),
|
||||||
|
method: String::new(),
|
||||||
body: json!({ "amount": 1234 }),
|
body: json!({ "amount": 1234 }),
|
||||||
script_name: "payments".into(),
|
script_name: "payments".into(),
|
||||||
..req(json!(null))
|
..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]
|
#[test]
|
||||||
fn captures_log_calls() {
|
fn captures_log_calls() {
|
||||||
let src = r#"
|
let src = r#"
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ fn req(app_id: AppId) -> ExecRequest {
|
|||||||
script_name: "redaction-test".into(),
|
script_name: "redaction-test".into(),
|
||||||
invocation_type: InvocationType::Http,
|
invocation_type: InvocationType::Http,
|
||||||
path: "/x".into(),
|
path: "/x".into(),
|
||||||
|
method: String::new(),
|
||||||
headers: BTreeMap::new(),
|
headers: BTreeMap::new(),
|
||||||
body: Value::Null,
|
body: Value::Null,
|
||||||
params: BTreeMap::new(),
|
params: BTreeMap::new(),
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ fn req(app_id: AppId) -> ExecRequest {
|
|||||||
script_name: "test".into(),
|
script_name: "test".into(),
|
||||||
invocation_type: InvocationType::Http,
|
invocation_type: InvocationType::Http,
|
||||||
path: "/test".into(),
|
path: "/test".into(),
|
||||||
|
method: String::new(),
|
||||||
headers: BTreeMap::new(),
|
headers: BTreeMap::new(),
|
||||||
body: serde_json::Value::Null,
|
body: serde_json::Value::Null,
|
||||||
params: BTreeMap::new(),
|
params: BTreeMap::new(),
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ fn baseline_request() -> ExecRequest {
|
|||||||
script_name: "contract".into(),
|
script_name: "contract".into(),
|
||||||
invocation_type: InvocationType::Http,
|
invocation_type: InvocationType::Http,
|
||||||
path: "/contract-test".into(),
|
path: "/contract-test".into(),
|
||||||
|
method: String::new(),
|
||||||
headers: BTreeMap::new(),
|
headers: BTreeMap::new(),
|
||||||
body: Value::Null,
|
body: Value::Null,
|
||||||
params: BTreeMap::new(),
|
params: BTreeMap::new(),
|
||||||
|
|||||||
@@ -248,6 +248,7 @@ fn baseline_request(app_id: AppId) -> ExecRequest {
|
|||||||
script_name: "docs-test".into(),
|
script_name: "docs-test".into(),
|
||||||
invocation_type: InvocationType::Http,
|
invocation_type: InvocationType::Http,
|
||||||
path: "/docs-test".into(),
|
path: "/docs-test".into(),
|
||||||
|
method: String::new(),
|
||||||
headers: BTreeMap::new(),
|
headers: BTreeMap::new(),
|
||||||
body: Value::Null,
|
body: Value::Null,
|
||||||
params: BTreeMap::new(),
|
params: BTreeMap::new(),
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ fn baseline_request(app_id: AppId) -> ExecRequest {
|
|||||||
script_name: "email-test".into(),
|
script_name: "email-test".into(),
|
||||||
invocation_type: InvocationType::Http,
|
invocation_type: InvocationType::Http,
|
||||||
path: "/email-test".into(),
|
path: "/email-test".into(),
|
||||||
|
method: String::new(),
|
||||||
headers: BTreeMap::new(),
|
headers: BTreeMap::new(),
|
||||||
body: Value::Null,
|
body: Value::Null,
|
||||||
params: BTreeMap::new(),
|
params: BTreeMap::new(),
|
||||||
|
|||||||
@@ -185,6 +185,7 @@ fn baseline_request(app_id: AppId) -> ExecRequest {
|
|||||||
script_name: "files-test".into(),
|
script_name: "files-test".into(),
|
||||||
invocation_type: InvocationType::Http,
|
invocation_type: InvocationType::Http,
|
||||||
path: "/files-test".into(),
|
path: "/files-test".into(),
|
||||||
|
method: String::new(),
|
||||||
headers: BTreeMap::new(),
|
headers: BTreeMap::new(),
|
||||||
body: Value::Null,
|
body: Value::Null,
|
||||||
params: BTreeMap::new(),
|
params: BTreeMap::new(),
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ fn baseline_request(app_id: AppId, script_id: ScriptId) -> ExecRequest {
|
|||||||
script_name: "http-test".into(),
|
script_name: "http-test".into(),
|
||||||
invocation_type: InvocationType::Http,
|
invocation_type: InvocationType::Http,
|
||||||
path: "/http-test".into(),
|
path: "/http-test".into(),
|
||||||
|
method: String::new(),
|
||||||
headers: BTreeMap::new(),
|
headers: BTreeMap::new(),
|
||||||
body: Value::Null,
|
body: Value::Null,
|
||||||
params: BTreeMap::new(),
|
params: BTreeMap::new(),
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ fn baseline_request(app_id: AppId) -> ExecRequest {
|
|||||||
script_name: "caller".into(),
|
script_name: "caller".into(),
|
||||||
invocation_type: InvocationType::Http,
|
invocation_type: InvocationType::Http,
|
||||||
path: "/caller".into(),
|
path: "/caller".into(),
|
||||||
|
method: String::new(),
|
||||||
headers: BTreeMap::new(),
|
headers: BTreeMap::new(),
|
||||||
body: Value::Null,
|
body: Value::Null,
|
||||||
params: BTreeMap::new(),
|
params: BTreeMap::new(),
|
||||||
|
|||||||
@@ -127,6 +127,7 @@ fn baseline_request(app_id: AppId) -> ExecRequest {
|
|||||||
script_name: "kv-test".into(),
|
script_name: "kv-test".into(),
|
||||||
invocation_type: InvocationType::Http,
|
invocation_type: InvocationType::Http,
|
||||||
path: "/kv-test".into(),
|
path: "/kv-test".into(),
|
||||||
|
method: String::new(),
|
||||||
headers: BTreeMap::new(),
|
headers: BTreeMap::new(),
|
||||||
body: Value::Null,
|
body: Value::Null,
|
||||||
params: BTreeMap::new(),
|
params: BTreeMap::new(),
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ fn baseline_request(app_id: AppId) -> ExecRequest {
|
|||||||
script_name: "pubsub-test".into(),
|
script_name: "pubsub-test".into(),
|
||||||
invocation_type: InvocationType::Http,
|
invocation_type: InvocationType::Http,
|
||||||
path: "/pubsub-test".into(),
|
path: "/pubsub-test".into(),
|
||||||
|
method: String::new(),
|
||||||
headers: BTreeMap::new(),
|
headers: BTreeMap::new(),
|
||||||
body: Value::Null,
|
body: Value::Null,
|
||||||
params: BTreeMap::new(),
|
params: BTreeMap::new(),
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ fn baseline_request(app_id: AppId) -> ExecRequest {
|
|||||||
script_name: "queue-test".into(),
|
script_name: "queue-test".into(),
|
||||||
invocation_type: InvocationType::Http,
|
invocation_type: InvocationType::Http,
|
||||||
path: "/queue-test".into(),
|
path: "/queue-test".into(),
|
||||||
|
method: String::new(),
|
||||||
headers: BTreeMap::new(),
|
headers: BTreeMap::new(),
|
||||||
body: Value::Null,
|
body: Value::Null,
|
||||||
params: BTreeMap::new(),
|
params: BTreeMap::new(),
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ fn baseline_request(app_id: AppId) -> ExecRequest {
|
|||||||
script_name: "retry-test".into(),
|
script_name: "retry-test".into(),
|
||||||
invocation_type: InvocationType::Http,
|
invocation_type: InvocationType::Http,
|
||||||
path: "/retry-test".into(),
|
path: "/retry-test".into(),
|
||||||
|
method: String::new(),
|
||||||
headers: BTreeMap::new(),
|
headers: BTreeMap::new(),
|
||||||
body: Value::Null,
|
body: Value::Null,
|
||||||
params: BTreeMap::new(),
|
params: BTreeMap::new(),
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ fn baseline_request(app_id: AppId) -> ExecRequest {
|
|||||||
script_name: "secrets-test".into(),
|
script_name: "secrets-test".into(),
|
||||||
invocation_type: InvocationType::Http,
|
invocation_type: InvocationType::Http,
|
||||||
path: "/secrets-test".into(),
|
path: "/secrets-test".into(),
|
||||||
|
method: String::new(),
|
||||||
headers: BTreeMap::new(),
|
headers: BTreeMap::new(),
|
||||||
body: Value::Null,
|
body: Value::Null,
|
||||||
params: BTreeMap::new(),
|
params: BTreeMap::new(),
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ fn request(app_id: AppId, with_principal: bool) -> ExecRequest {
|
|||||||
script_name: "token-test".into(),
|
script_name: "token-test".into(),
|
||||||
invocation_type: InvocationType::Http,
|
invocation_type: InvocationType::Http,
|
||||||
path: "/token-test".into(),
|
path: "/token-test".into(),
|
||||||
|
method: String::new(),
|
||||||
headers: BTreeMap::new(),
|
headers: BTreeMap::new(),
|
||||||
body: Value::Null,
|
body: Value::Null,
|
||||||
params: BTreeMap::new(),
|
params: BTreeMap::new(),
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ fn baseline_request() -> ExecRequest {
|
|||||||
script_name: "stdlib".into(),
|
script_name: "stdlib".into(),
|
||||||
invocation_type: InvocationType::Http,
|
invocation_type: InvocationType::Http,
|
||||||
path: "/stdlib-test".into(),
|
path: "/stdlib-test".into(),
|
||||||
|
method: String::new(),
|
||||||
headers: BTreeMap::new(),
|
headers: BTreeMap::new(),
|
||||||
body: Value::Null,
|
body: Value::Null,
|
||||||
params: BTreeMap::new(),
|
params: BTreeMap::new(),
|
||||||
|
|||||||
@@ -357,6 +357,8 @@ impl Dispatcher {
|
|||||||
script_name: script.name.clone(),
|
script_name: script.name.clone(),
|
||||||
invocation_type: InvocationType::Function,
|
invocation_type: InvocationType::Function,
|
||||||
path: "/trigger/queue".into(),
|
path: "/trigger/queue".into(),
|
||||||
|
// Non-HTTP trigger invocation — no request method.
|
||||||
|
method: String::new(),
|
||||||
headers: std::collections::BTreeMap::new(),
|
headers: std::collections::BTreeMap::new(),
|
||||||
body: serde_json::Value::Null,
|
body: serde_json::Value::Null,
|
||||||
params: std::collections::BTreeMap::new(),
|
params: std::collections::BTreeMap::new(),
|
||||||
@@ -658,6 +660,8 @@ impl Dispatcher {
|
|||||||
script_name: resolved.script_name.clone(),
|
script_name: resolved.script_name.clone(),
|
||||||
invocation_type: InvocationType::Function,
|
invocation_type: InvocationType::Function,
|
||||||
path: format!("/trigger/{}", trigger_event.source()),
|
path: format!("/trigger/{}", trigger_event.source()),
|
||||||
|
// Non-HTTP trigger invocation — no request method.
|
||||||
|
method: String::new(),
|
||||||
headers: std::collections::BTreeMap::new(),
|
headers: std::collections::BTreeMap::new(),
|
||||||
body: serde_json::Value::Null,
|
body: serde_json::Value::Null,
|
||||||
params: std::collections::BTreeMap::new(),
|
params: std::collections::BTreeMap::new(),
|
||||||
@@ -719,6 +723,7 @@ impl Dispatcher {
|
|||||||
script_name: payload.script_name.clone(),
|
script_name: payload.script_name.clone(),
|
||||||
invocation_type: InvocationType::Http,
|
invocation_type: InvocationType::Http,
|
||||||
path: payload.path.clone(),
|
path: payload.path.clone(),
|
||||||
|
method: payload.method.clone(),
|
||||||
headers: payload.headers,
|
headers: payload.headers,
|
||||||
body: payload.body,
|
body: payload.body,
|
||||||
params: payload.params,
|
params: payload.params,
|
||||||
@@ -814,6 +819,7 @@ impl Dispatcher {
|
|||||||
script_name: script.name.clone(),
|
script_name: script.name.clone(),
|
||||||
invocation_type: InvocationType::Function,
|
invocation_type: InvocationType::Function,
|
||||||
path: "/invoke_async".into(),
|
path: "/invoke_async".into(),
|
||||||
|
method: String::new(),
|
||||||
headers: std::collections::BTreeMap::new(),
|
headers: std::collections::BTreeMap::new(),
|
||||||
body: args,
|
body: args,
|
||||||
params: std::collections::BTreeMap::new(),
|
params: std::collections::BTreeMap::new(),
|
||||||
|
|||||||
@@ -584,6 +584,8 @@ fn build_exec_request(
|
|||||||
script_name: name.to_string(),
|
script_name: name.to_string(),
|
||||||
invocation_type: InvocationType::Http,
|
invocation_type: InvocationType::Http,
|
||||||
path: format!("/api/execute/{id}"),
|
path: format!("/api/execute/{id}"),
|
||||||
|
// The `/api/v1/execute/{id}` bypass is POST-only.
|
||||||
|
method: "POST".into(),
|
||||||
headers: hmap,
|
headers: hmap,
|
||||||
body: body_json,
|
body: body_json,
|
||||||
params: BTreeMap::new(),
|
params: BTreeMap::new(),
|
||||||
|
|||||||
Reference in New Issue
Block a user