system2 0.1.0

An (unofficial) idiomatic Rust SDK for TypeSafe's System One model, Jev.
Documentation
use super::wire::{RawAnswerSet, RawQuestionSet, Sealed};
use crate::QuestionSet;
use std::marker::PhantomData;
use std::{any, fmt};

/// The question set of a named set: the struct `A`, which derives [`Answers`].
///
/// The type `A` selects the questions, so `Questions` takes no data. Each question is a constant,
/// and `cargo check` validates it. `Questions` makes its questions for each request, and again to
/// read the answers.
///
/// # Examples
///
/// ```no_run
/// use system2::prelude::*;
/// use system2::NoulAnswer;
///
/// #[derive(Debug, Answers)]
/// struct Triage {
///     #[system2(question = noul!("Is the ticket urgent?"))]
///     urgent: NoulAnswer,
///     #[system2(question = noul!("Is the customer angry?"))]
///     angry: NoulAnswer,
/// }
///
/// // the struct is the caller's, so it can have methods
/// impl Triage {
///     fn needs_a_call(&self) -> bool {
///         self.urgent.is_likely() && self.angry.is_likely()
///     }
/// }
///
/// const TRIAGE: Questions<Triage> = Questions::new();
///
/// # async fn run() -> system2::Result<()> {
/// let triage = TRIAGE.ask("The site is down again.").await?;
/// if triage.needs_a_call() { /* … */ }
/// let triage: Triage = Questions::new().ask("The site is down again.").await?;
/// # Ok(())
/// # }
/// ```
pub struct Questions<A: Answers> {
    answers: PhantomData<fn() -> A>,
}

/// The answers of a named set: a struct that holds an answer set in each field.
///
/// Only [`derive(Answers)`](macro@crate::Answers) implements it. The derive adds no method to the
/// struct, so each name is free for the methods of the caller. [`Questions`] asks the questions
/// of the struct.
#[diagnostic::on_unimplemented(
    message = "`{Self}` is not the answers of a named set",
    label = "not the answers of a named set",
    note = "add `#[derive(Answers)]` to a struct with named fields, \
            then `Questions<{Self}>` asks its questions"
)]
pub trait Answers: SealedAnswers {}

// hidden, so a named set comes from the derive with constant questions; a caller can still
// reach it through `__private`, but the crate supports no implementation by hand
#[doc(hidden)]
#[diagnostic::on_unimplemented(
    message = "`{Self}` must derive `Answers`, not implement it",
    label = "an implementation of `Answers` by hand",
    note = "add `#[derive(Answers)]` to a struct with named fields, \
            or use `questions! {{ … }}` for other question sets"
)]
pub trait SealedAnswers: Sized {
    fn question_set() -> impl QuestionSet<AnswerSet = Self>;
}

// the question set of a derive, with the struct of its fields as the answers. the derive names
// no type of the set in its impl, so a public struct can hold the answer of a private type
#[doc(hidden)]
pub fn answers<Q: QuestionSet, A>(
    question_set: Q,
    from_answer_set: fn(Q::AnswerSet) -> A,
) -> impl QuestionSet<AnswerSet = A> {
    Named {
        question_set,
        from_answer_set,
    }
}

struct Named<Q: QuestionSet, A> {
    question_set: Q,
    from_answer_set: fn(Q::AnswerSet) -> A,
}

impl<Q: QuestionSet, A> QuestionSet for Named<Q, A> {
    type AnswerSet = A;
}

impl<Q: QuestionSet, A> Sealed for Named<Q, A> {
    fn write(&mut self, raw_question_set: &mut RawQuestionSet) {
        self.question_set.write(raw_question_set);
    }

    fn read(
        self,
        raw_answer_set: &mut RawAnswerSet,
    ) -> Result<<Self as QuestionSet>::AnswerSet, String> {
        let answer_set = self.question_set.read(raw_answer_set)?;
        Ok((self.from_answer_set)(answer_set))
    }
}

// ties a question of the derive to the answer of its field, so a wrong question is an error at
// the question; the opaque type keeps the error of the whole set to the same short message
#[doc(hidden)]
pub fn question_set<A, Q: QuestionSet<AnswerSet = A>>(
    question_set: Q,
) -> impl QuestionSet<AnswerSet = A> {
    question_set
}

impl<A: Answers> Questions<A> {
    /// Makes the question set of `A`.
    #[must_use]
    pub const fn new() -> Self {
        let answers = PhantomData;
        Questions { answers }
    }
}

impl<A: Answers> QuestionSet for Questions<A> {
    type AnswerSet = A;
}

// the derive cannot name the type of the question set, so the set makes its questions again to
// read the answers; the derive gives constant questions, so they are equal each time
impl<A: Answers> Sealed for Questions<A> {
    fn write(&mut self, raw_question_set: &mut RawQuestionSet) {
        A::question_set().write(raw_question_set);
    }

    fn read(
        self,
        raw_answer_set: &mut RawAnswerSet,
    ) -> Result<<Self as QuestionSet>::AnswerSet, String> {
        A::question_set().read(raw_answer_set)
    }
}

impl<A: Answers> Default for Questions<A> {
    fn default() -> Self {
        Questions::new()
    }
}

impl<A: Answers> Clone for Questions<A> {
    fn clone(&self) -> Self {
        Questions::new()
    }
}

impl<A: Answers> fmt::Debug for Questions<A> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Questions<{}>", any::type_name::<A>())
    }
}

#[cfg(test)]
mod tests {
    use super::{Answers, Questions, SealedAnswers};
    use crate::ask::wire::{RawAnswerSet, RawQuestionSet, Sealed};
    use crate::{Noul, NoulAnswer, QuestionSet};
    use serde_json::json;
    use std::any;

    fn noul(text: &str) -> Noul {
        Noul::new(text).unwrap()
    }

    // by hand, as the derive writes it
    #[derive(Debug)]
    struct Review {
        buggy: NoulAnswer,
        tested: Option<NoulAnswer>,
    }

    impl Answers for Review {}

    impl SealedAnswers for Review {
        fn question_set() -> impl QuestionSet<AnswerSet = Self> {
            super::answers(
                (noul("Is it buggy?"), Some(noul("Is it tested?"))),
                |(buggy, tested)| Review { buggy, tested },
            )
        }
    }

    fn texts(mut question_set: impl QuestionSet) -> Vec<String> {
        let mut raw_question_set = RawQuestionSet::default();
        question_set.write(&mut raw_question_set);
        raw_question_set
            .texts()
            .into_iter()
            .map(str::to_owned)
            .collect()
    }

    #[test]
    fn write_gives_the_questions_of_the_answers() {
        assert_eq!(
            texts(Questions::<Review>::new()),
            texts(Review::question_set())
        );
    }

    #[test]
    fn read_gives_the_answers() {
        let answers = vec![
            json!({ "type": "noul", "noul": 0.1 }),
            json!({ "type": "noul", "noul": 0.2 }),
        ];

        let review = Questions::<Review>::new()
            .read(&mut RawAnswerSet::of(answers))
            .unwrap();

        assert_eq!(review.buggy.probability(), 0.1);
        assert_eq!(review.tested.unwrap().probability(), 0.2);
    }

    #[test]
    fn read_stops_at_a_missing_answer() {
        let answers = vec![json!({ "type": "noul", "noul": 0.1 })];

        let error = Questions::<Review>::new()
            .read(&mut RawAnswerSet::of(answers))
            .unwrap_err();

        assert_eq!(error, "must answer each question: `q1` has no answer");
    }

    #[test]
    fn default_and_clone_give_the_questions_of_new() {
        let question_set = Questions::<Review>::new();

        assert_eq!(
            texts(question_set.clone()),
            texts(Questions::<Review>::new())
        );
        assert_eq!(texts(Questions::<Review>::default()), texts(question_set));
    }

    #[test]
    fn debug_names_the_answers() {
        assert_eq!(
            format!("{:?}", Questions::<Review>::new()),
            format!("Questions<{}>", any::type_name::<Review>())
        );
    }

    #[test]
    fn question_set_gives_the_question_set_back() {
        assert_eq!(
            texts(super::question_set(noul("Is it buggy?"))),
            ["{\"type\":\"noul\",\"instructions\":\"Is it buggy?\"}"]
        );
    }
}