use core::future::Future;
use core::pin::Pin;
use std::sync::Arc;
use serde_json::{Map, Value};
use crate::domain::CapabilitySet;
use crate::domain::ir::JsonObject;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HandlerRefusal {
pub code: i64,
pub message: String,
}
impl HandlerRefusal {
#[must_use]
pub fn unsupported(method: &str) -> Self {
Self {
code: -32601,
message: format!("this client does not handle `{method}`"),
}
}
}
pub trait ServerRequestHandler: Send + Sync {
fn handle<'a>(
&'a self,
method: &'a str,
params: &'a JsonObject,
) -> Pin<Box<dyn Future<Output = Result<JsonObject, HandlerRefusal>> + Send + 'a>>;
}
#[derive(Clone, Default)]
pub struct ConnectOptions {
pub(crate) capabilities: CapabilitySet,
pub(crate) handler: Option<Arc<dyn ServerRequestHandler>>,
pub(crate) auth: Option<Arc<dyn super::bearer::BearerAuth>>,
}
impl core::fmt::Debug for ConnectOptions {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ConnectOptions")
.field("capabilities", &self.capabilities)
.field("handler", &self.handler.is_some())
.field("auth", &self.auth.is_some())
.finish()
}
}
impl ConnectOptions {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub const fn declaring(mut self, capabilities: CapabilitySet) -> Self {
self.capabilities = capabilities;
self
}
#[must_use]
pub fn authorizing(mut self, auth: Arc<dyn super::bearer::BearerAuth>) -> Self {
self.auth = Some(auth);
self
}
#[must_use]
pub fn answering(mut self, handler: Arc<dyn ServerRequestHandler>) -> Self {
self.handler = Some(handler);
self
}
}
#[must_use]
pub fn apply_elicitation_defaults(request: &JsonObject, answer: &JsonObject) -> JsonObject {
let Ok(Value::Object(mut result)) = serde_json::from_str::<Value>(answer.as_str()) else {
return answer.clone();
};
if result.get("action").and_then(Value::as_str) != Some("accept") {
return answer.clone();
}
let Ok(Value::Object(params)) = serde_json::from_str::<Value>(request.as_str()) else {
return answer.clone();
};
let Some(properties) = params
.get("requestedSchema")
.and_then(|schema| schema.get("properties"))
.and_then(Value::as_object)
else {
return answer.clone();
};
let content = result
.entry("content")
.or_insert_with(|| Value::Object(Map::new()));
if let Some(content) = content.as_object_mut() {
for (name, schema) in properties {
if !content.contains_key(name)
&& let Some(default) = schema.get("default")
{
content.insert(name.clone(), default.clone());
}
}
}
JsonObject::from_value(Value::Object(result)).unwrap_or_else(|_| answer.clone())
}
#[cfg(test)]
mod tests {
use super::apply_elicitation_defaults;
use crate::domain::ir::JsonObject;
fn object(text: &str) -> JsonObject {
JsonObject::new(text).unwrap_or_default()
}
#[test]
fn omitted_fields_take_their_default() {
let request = object(
r#"{"message":"m","requestedSchema":{"type":"object","properties":{"name":{"type":"string","default":"John"},"age":{"type":"integer","default":30},"plain":{"type":"string"}}}}"#,
);
let filled =
apply_elicitation_defaults(&request, &object(r#"{"action":"accept","content":{}}"#));
let parsed: serde_json::Value = serde_json::from_str(filled.as_str()).unwrap_or_default();
assert_eq!(
parsed.pointer("/content/name"),
Some(&serde_json::json!("John"))
);
assert_eq!(parsed.pointer("/content/age"), Some(&serde_json::json!(30)));
assert_eq!(
parsed.pointer("/content/plain"),
None,
"no default, nothing invented"
);
}
#[test]
fn a_value_the_user_gave_is_kept() {
let request = object(
r#"{"requestedSchema":{"properties":{"name":{"type":"string","default":"John"}}}}"#,
);
let filled = apply_elicitation_defaults(
&request,
&object(r#"{"action":"accept","content":{"name":"Ada"}}"#),
);
assert!(filled.as_str().contains("Ada") && !filled.as_str().contains("John"));
}
#[test]
fn a_decline_is_untouched() {
let request = object(
r#"{"requestedSchema":{"properties":{"name":{"type":"string","default":"John"}}}}"#,
);
let answer = object(r#"{"action":"decline"}"#);
assert_eq!(apply_elicitation_defaults(&request, &answer), answer);
}
}