use std::collections::BTreeMap;
use serde_json::Value;
use crate::errors::Error;
use crate::questions::{validate, Question};
#[derive(Debug, Clone, PartialEq)]
pub struct SystemOneRequest {
pub state: Value,
pub model: Option<String>,
pub questions: BTreeMap<String, Question>,
}
impl SystemOneRequest {
pub fn new(state: impl Into<Value>) -> Self {
Self {
state: state.into(),
model: None,
questions: BTreeMap::new(),
}
}
pub fn with_state_serialize<T: serde::Serialize>(state: &T) -> Result<Self, Error> {
let value = serde_json::to_value(state).map_err(|error| {
Error::InvalidRequest(format!("state failed to serialize: {error}"))
})?;
if !matches!(value, Value::String(_) | Value::Object(_) | Value::Array(_)) {
return Err(Error::InvalidRequest(
"state must serialize to a JSON string, object, or array.".into(),
));
}
Ok(Self::new(value))
}
pub fn model(mut self, model: impl Into<String>) -> Self {
self.model = Some(model.into());
self
}
pub fn question(mut self, id: impl Into<String>, question: impl Into<Question>) -> Self {
self.questions.insert(id.into(), question.into());
self
}
pub fn questions<I, S>(mut self, questions: I) -> Self
where
I: IntoIterator<Item = (S, Question)>,
S: Into<String>,
{
for (id, question) in questions {
self.questions.insert(id.into(), question);
}
self
}
pub(crate) fn validate(&self) -> Result<(), Error> {
validate(&self.state, &self.questions)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::questions::{Choice, Noul, Score};
use serde_json::json;
#[derive(serde::Serialize)]
struct Ticket {
subject: String,
body: String,
}
#[test]
fn with_state_serialize_accepts_objects() {
let ticket = Ticket {
subject: "Refund".into(),
body: "Charged twice".into(),
};
let request = SystemOneRequest::with_state_serialize(&ticket).unwrap();
assert_eq!(
request.state,
json!({"subject": "Refund", "body": "Charged twice"})
);
}
#[test]
fn with_state_serialize_rejects_primitives() {
let err = SystemOneRequest::with_state_serialize(&42).unwrap_err();
assert!(matches!(err, Error::InvalidRequest(_)));
let err = SystemOneRequest::with_state_serialize(&true).unwrap_err();
assert!(matches!(err, Error::InvalidRequest(_)));
let err = SystemOneRequest::with_state_serialize::<Option<u8>>(&None).unwrap_err();
assert!(matches!(err, Error::InvalidRequest(_)));
let err = SystemOneRequest::with_state_serialize(&"string is fine").unwrap();
assert_eq!(err.state, json!("string is fine"));
}
#[test]
fn builder_methods() {
let request = SystemOneRequest::new("text")
.model("jev-1.13.0")
.question("a", Noul::new("q1"))
.question("b", Choice::new("q2").option("x", "y"))
.question("c", Score::new("q3", ["1", "2"]));
assert_eq!(request.model.as_deref(), Some("jev-1.13.0"));
assert_eq!(request.questions.len(), 3);
assert_eq!(request.questions["a"], Question::Noul(Noul::new("q1")));
assert!(request.validate().is_ok());
}
}