Keep -32602 and existing validation order; tell callers to send the object itself. Cover host routing, stdout fast paths, SDK calls and generated dispatch. Card: SAU-317
This commit is contained in:
@@ -270,7 +270,7 @@ pub fn rpc(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
fn methods_document()->::sausage_plugin::serde_json::Value {let mut doc=::sausage_plugin::document(env!("CARGO_PKG_NAME"),env!("CARGO_PKG_VERSION")); Self::add_methods(&mut doc); #grace ::sausage_plugin::finish_document(doc)}
|
||||
fn describe()->::sausage_plugin::serde_json::Value {let mut doc=::sausage_plugin::document(env!("CARGO_PKG_NAME"),env!("CARGO_PKG_VERSION")); Self::add_methods(&mut doc); #grace #(doc.endpoint::<#types>(#names);)* ::sausage_plugin::finish_document(doc)}
|
||||
fn discover(&self)->::sausage_plugin::serde_json::Value {<Self as ::sausage_plugin::Interface>::describe()}
|
||||
fn dispatch<'a>(&'a self, method:&'a str, params: ::sausage_plugin::serde_json::Value,ctx:&'a ::sausage_plugin::Ctx)->::std::pin::Pin<Box<dyn ::std::future::Future<Output=::sausage_plugin::Result<::sausage_plugin::serde_json::Value>>+'a>> {Box::pin(async move {if !params.is_object() {return Err(::sausage_plugin::Error::new(-32602,"params must be an object"));} match method {#(#arms,)* _=>Err(::sausage_plugin::Error::new(-32601,"method not found"))}})}
|
||||
fn dispatch<'a>(&'a self, method:&'a str, params: ::sausage_plugin::serde_json::Value,ctx:&'a ::sausage_plugin::Ctx)->::std::pin::Pin<Box<dyn ::std::future::Future<Output=::sausage_plugin::Result<::sausage_plugin::serde_json::Value>>+'a>> {Box::pin(async move {if !params.is_object() {return Err(::sausage_plugin::Error::params_not_object(¶ms));} match method {#(#arms,)* _=>Err(::sausage_plugin::Error::new(-32601,"method not found"))}})}
|
||||
#stop
|
||||
}})
|
||||
})();
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ impl IntoFuture for Call {
|
||||
fn into_future(self) -> Self::IntoFuture {
|
||||
Box::pin(async move {
|
||||
if !self.params.is_object() {
|
||||
return Err(Error::new(-32602, "params must be an object"));
|
||||
return Err(Error::params_not_object(&self.params));
|
||||
}
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let id = STATE.with(|s| {
|
||||
|
||||
@@ -15,6 +15,25 @@ pub struct Error {
|
||||
pub data: Option<Value>,
|
||||
}
|
||||
impl Error {
|
||||
/// Build the object-params diagnostic used by calls and generated dispatch.
|
||||
#[doc(hidden)]
|
||||
pub fn params_not_object(params: &Value) -> Self {
|
||||
let kind = match params {
|
||||
Value::String(_) => "a string",
|
||||
Value::Array(_) => "an array",
|
||||
Value::Number(_) => "a number",
|
||||
Value::Null => "null",
|
||||
Value::Bool(_) => "a boolean",
|
||||
Value::Object(_) => "an object",
|
||||
};
|
||||
Self::new(
|
||||
-32602,
|
||||
format!(
|
||||
"params must be a JSON object, got {kind}. Send the object itself, not a JSON-encoded string."
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/// Construct an error without structured details.
|
||||
pub fn new(code: i64, message: impl Into<String>) -> Self {
|
||||
Self {
|
||||
|
||||
@@ -116,6 +116,53 @@ fn discovery_and_dispatch() {
|
||||
.into();
|
||||
assert_eq!(internal.code, -32603);
|
||||
}
|
||||
#[sausage_plugin::test]
|
||||
async fn params_must_be_objects() {
|
||||
for (params, kind) in [
|
||||
(json!("{\"a\":2,\"b\":3}"), "a string"),
|
||||
(json!([]), "an array"),
|
||||
(json!(42), "a number"),
|
||||
(json!(null), "null"),
|
||||
(json!(true), "a boolean"),
|
||||
] {
|
||||
let message = format!(
|
||||
"params must be a JSON object, got {kind}. Send the object itself, not a JSON-encoded string."
|
||||
);
|
||||
let outgoing = sausage_plugin::call("plugin.add", params.clone())
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_eq!(outgoing.code, -32602);
|
||||
assert_eq!(outgoing.message, message);
|
||||
for method in ["add", "choice", "unknown"] {
|
||||
let incoming = Plugin
|
||||
.dispatch(method, params.clone(), &Ctx::default())
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_eq!(incoming.code, -32602);
|
||||
assert_eq!(incoming.message, message);
|
||||
}
|
||||
}
|
||||
assert_eq!(
|
||||
Plugin
|
||||
.dispatch("add", json!({"a":2,"b":3}), &Ctx::default())
|
||||
.await
|
||||
.unwrap(),
|
||||
5
|
||||
);
|
||||
assert_eq!(
|
||||
Plugin
|
||||
.dispatch("defaults", json!({}), &Ctx::default())
|
||||
.await
|
||||
.unwrap(),
|
||||
json!({"value":""})
|
||||
);
|
||||
// An object reaches transport validation rather than the params rejection.
|
||||
let outgoing = sausage_plugin::call("plugin.add", json!({"a":2,"b":3}))
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert!(outgoing.message.contains("no core transport"));
|
||||
}
|
||||
|
||||
#[sausage_plugin::test]
|
||||
async fn missing_core_fails_without_wait() {
|
||||
assert!(
|
||||
|
||||
Reference in New Issue
Block a user