Skip to main content

cactus_rs/
model.rs

1//! Which of the two models a weight archive, an engine slot or an error is about.
2//!
3//! ## Overview
4//!
5//! The linked engine holds one model per kind: a text model (Needle) and a speech model
6//! (Whistle). Both ship as `.cact` archives with the same magic tag, so the crate names the model
7//! wherever the two could be confused, most visibly in
8//! [`Error::WrongModel`](crate::Error::WrongModel).
9//!
10//! ## Usage
11//!
12//! ```rust
13//! use cactus_rs::Model;
14//!
15//! assert_eq!(Model::Whistle.archive(), "whistle.cact");
16//! assert_eq!(Model::Needle.to_string(), "Needle");
17//! ```
18
19use std::fmt;
20
21/// One of the models the engine can hold.
22///
23/// The enum is `#[non_exhaustive]`: matching it needs a `_` arm, and a model added by a later
24/// engine is not a breaking change.
25///
26/// # Examples
27///
28/// ```rust
29/// use cactus_rs::Model;
30///
31/// let model = Model::Needle;
32/// assert_eq!(model.as_str(), "Needle");
33/// assert_eq!(model.archive(), "needle3.cact");
34/// ```
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
36#[non_exhaustive]
37pub enum Model {
38    /// Needle 3, the text model: tool calling, structured extraction and text embedding.
39    Needle,
40    /// Whistle, the speech model: transcription, word timestamps and speech embedding.
41    Whistle,
42}
43
44impl Model {
45    /// The model's name, as it appears in messages.
46    ///
47    /// # Examples
48    ///
49    /// ```rust
50    /// use cactus_rs::Model;
51    ///
52    /// assert_eq!(Model::Whistle.as_str(), "Whistle");
53    /// ```
54    #[inline]
55    #[must_use]
56    pub const fn as_str(self) -> &'static str {
57        match self {
58            Model::Needle => "Needle",
59            Model::Whistle => "Whistle",
60        }
61    }
62
63    /// The file name upstream publishes the model's weights under.
64    ///
65    /// # Examples
66    ///
67    /// ```rust
68    /// use cactus_rs::Model;
69    ///
70    /// assert_eq!(Model::Needle.archive(), "needle3.cact");
71    /// assert_eq!(Model::Whistle.archive(), "whistle.cact");
72    /// ```
73    #[inline]
74    #[must_use]
75    pub const fn archive(self) -> &'static str {
76        match self {
77            Model::Needle => "needle3.cact",
78            Model::Whistle => "whistle.cact",
79        }
80    }
81}
82
83/// Writes the model's name, the same text as [`Model::as_str`].
84impl fmt::Display for Model {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        f.write_str(self.as_str())
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn display_is_the_name() {
96        for model in [Model::Needle, Model::Whistle] {
97            assert_eq!(model.to_string(), model.as_str());
98        }
99    }
100
101    #[test]
102    fn archives_are_distinct() {
103        assert_ne!(Model::Needle.archive(), Model::Whistle.archive());
104    }
105}