refactor(sdk): consolidate the SDK error/bridge helpers

`http.rs` hand-rolled its own `block_on` + `map_http_err`, and `secrets.rs` /
`email.rs` / `users.rs` each carried a byte-identical `runtime_err` — five
`#[allow(clippy::unnecessary_box_returns)]` across four files for one pattern.

Move `runtime_err` to `bridge.rs` beside `block_on` (single home, single allow)
and have the three bridges import it. `http.rs` now calls the shared
`bridge::block_on("http", …)` (same "http:"-prefixed error, so messages are
unchanged) and its `err` validation helper delegates to `bridge::runtime_err`.
Deletes 4 duplicate fns and 3 of the 5 allows; no behavior change (pinned by
the executor-core SDK contract tests).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-13 19:36:31 +02:00
parent aa3f0531f0
commit f1b480f2eb
5 changed files with 28 additions and 55 deletions

View File

@@ -43,6 +43,15 @@ where
})
}
/// Build a Rhai runtime error from a message. Returns the boxed error
/// directly because every caller needs a `Box<EvalAltResult>` (Rhai's error
/// type) — the shared home for the pattern the SDK bridges (secrets, email,
/// users, …) previously each copied.
#[allow(clippy::unnecessary_box_returns)]
pub(crate) fn runtime_err(msg: &str) -> Box<EvalAltResult> {
EvalAltResult::ErrorRuntime(msg.into(), rhai::Position::NONE).into()
}
/// Convert a `serde_json::Value` into a Rhai `Dynamic` suitable for
/// pushing into a script's scope. Numbers prefer the narrowest type
/// (`i64` over `f64`); anything that can't round-trip falls back to a

View File

@@ -26,7 +26,7 @@
use std::sync::Arc;
use super::bridge::block_on;
use super::bridge::{block_on, runtime_err};
use picloud_shared::{OutboundEmail, SdkCallCx, Services};
use rhai::{Array, Engine as RhaiEngine, EvalAltResult, Map, Module};
@@ -124,8 +124,3 @@ fn addresses(opts: &Map, key: &str) -> Result<Vec<String>, Box<EvalAltResult>> {
}
}
}
#[allow(clippy::unnecessary_box_returns)]
fn runtime_err(msg: &str) -> Box<EvalAltResult> {
EvalAltResult::ErrorRuntime(msg.into(), rhai::Position::NONE).into()
}

View File

@@ -32,11 +32,10 @@
use std::collections::BTreeMap;
use std::sync::Arc;
use picloud_shared::{HttpError, HttpRequest, HttpResponse, HttpService, SdkCallCx, Services};
use picloud_shared::{HttpRequest, HttpResponse, HttpService, SdkCallCx, Services};
use rhai::{Dynamic, Engine as RhaiEngine, EvalAltResult, Map, Module};
use tokio::runtime::Handle as TokioHandle;
use super::bridge::{dynamic_to_json, json_to_dynamic};
use super::bridge::{block_on, dynamic_to_json, json_to_dynamic, runtime_err};
/// Bridge-side defaults (the service clamps server-side too). The
/// `MAX_*` ceilings stay `i64` because they're compared against the
@@ -280,7 +279,11 @@ fn invoke(
max_redirects: opts.max_redirects,
script_id: Some(cx.script_id.to_string()),
};
let resp = block_on(svc, cx, req)?;
let resp = {
let svc = svc.clone();
let cx = cx.clone();
block_on("http", async move { svc.request(&cx, req).await })?
};
Ok(response_to_dynamic(&resp))
}
@@ -310,7 +313,11 @@ fn invoke_form(
max_redirects: opts.max_redirects,
script_id: Some(cx.script_id.to_string()),
};
let resp = block_on(svc, cx, req)?;
let resp = {
let svc = svc.clone();
let cx = cx.clone();
block_on("http", async move { svc.request(&cx, req).await })?
};
Ok(response_to_dynamic(&resp))
}
@@ -356,36 +363,10 @@ fn dyn_to_string(v: &Dynamic) -> String {
}
}
// Rhai's native-fn error channel is `Box<EvalAltResult>`, so these
// helpers return the boxed form the call sites need.
// A validation error, prefixed like the service's runtime errors (the shared
// `bridge::block_on("http", …)` prefixes the same way, so messages are uniform).
// Delegates to `bridge::runtime_err`; the boxed return is Rhai's error channel.
#[allow(clippy::unnecessary_box_returns)]
fn err(msg: String) -> Box<EvalAltResult> {
EvalAltResult::ErrorRuntime(format!("http: {msg}").into(), rhai::Position::NONE).into()
}
/// Run the async service call from the synchronous Rhai context. Same
/// pattern as `kv`/`docs`: the script runs under `spawn_blocking`, so a
/// runtime handle is reachable and blocking on it is correct.
fn block_on(
svc: &Arc<dyn HttpService>,
cx: &Arc<SdkCallCx>,
req: HttpRequest,
) -> Result<HttpResponse, Box<EvalAltResult>> {
let handle = TokioHandle::try_current().map_err(|e| -> Box<EvalAltResult> {
EvalAltResult::ErrorRuntime(
format!("http: no tokio runtime available: {e}").into(),
rhai::Position::NONE,
)
.into()
})?;
let svc = svc.clone();
let cx = cx.clone();
handle
.block_on(async move { svc.request(&cx, req).await })
.map_err(map_http_err)
}
#[allow(clippy::unnecessary_box_returns)]
fn map_http_err(e: HttpError) -> Box<EvalAltResult> {
EvalAltResult::ErrorRuntime(format!("http: {e}").into(), rhai::Position::NONE).into()
runtime_err(&format!("http: {msg}"))
}

View File

@@ -21,7 +21,7 @@ use std::sync::Arc;
use picloud_shared::{SdkCallCx, SecretsListPage, Services};
use rhai::{Array, Dynamic, Engine as RhaiEngine, EvalAltResult, Map, Module};
use super::bridge::{block_on, dynamic_to_json, json_to_dynamic};
use super::bridge::{block_on, dynamic_to_json, json_to_dynamic, runtime_err};
pub(super) fn register(engine: &mut RhaiEngine, services: &Services, cx: Arc<SdkCallCx>) {
let svc = services.secrets.clone();
@@ -124,10 +124,3 @@ fn list_page_to_map(page: SecretsListPage) -> Map {
);
m
}
// Returns the boxed error directly because every caller needs a
// `Box<EvalAltResult>` (Rhai's error type), matching the other bridges.
#[allow(clippy::unnecessary_box_returns)]
fn runtime_err(msg: &str) -> Box<EvalAltResult> {
EvalAltResult::ErrorRuntime(msg.into(), rhai::Position::NONE).into()
}

View File

@@ -50,7 +50,7 @@
use std::sync::Arc;
use super::bridge::block_on;
use super::bridge::{block_on, runtime_err};
use picloud_shared::{
AppUser, AppUserId, CreateUserInput, EmailTemplateOpts, GeneratedAccept, GeneratedSession,
InviteOpts, SdkCallCx, Services, UpdateUserInput, UsersListOpts, UsersListPage, UsersService,
@@ -615,8 +615,3 @@ fn optional_string(opts: &Map, key: &str) -> Option<String> {
Some(d) => Some(d.to_string()),
}
}
#[allow(clippy::unnecessary_box_returns)]
fn runtime_err(msg: &str) -> Box<EvalAltResult> {
EvalAltResult::ErrorRuntime(msg.into(), rhai::Position::NONE).into()
}