1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
8pub struct QueryId(String);
9
10impl QueryId {
11 pub fn single() -> Self {
13 Self("q-0".into())
14 }
15
16 pub fn batch(index: usize) -> Self {
18 Self(format!("q-{index}"))
19 }
20
21 pub fn parse(s: &str) -> Self {
23 Self(s.to_string())
24 }
25
26 pub fn as_str(&self) -> &str {
27 &self.0
28 }
29}
30
31impl fmt::Display for QueryId {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 self.0.fmt(f)
34 }
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct LlmQuery {
41 pub id: QueryId,
42 pub prompt: String,
43 pub system: Option<String>,
44 pub max_tokens: u32,
45}
46
47#[cfg(test)]
48mod tests {
49 use super::*;
50
51 #[test]
52 fn single_query_id() {
53 let id = QueryId::single();
54 assert_eq!(id.as_str(), "q-0");
55 assert_eq!(id.to_string(), "q-0");
56 }
57
58 #[test]
59 fn batch_query_ids_are_unique() {
60 let ids: Vec<QueryId> = (0..5).map(QueryId::batch).collect();
61 let set: std::collections::HashSet<&QueryId> = ids.iter().collect();
62 assert_eq!(set.len(), 5);
63 assert_eq!(ids[0].as_str(), "q-0");
64 assert_eq!(ids[3].as_str(), "q-3");
65 }
66
67 #[test]
68 fn single_equals_batch_zero() {
69 assert_eq!(QueryId::single(), QueryId::batch(0));
70 }
71
72 #[test]
73 fn parse_roundtrip() {
74 let id = QueryId::parse("q-42");
75 assert_eq!(id.as_str(), "q-42");
76 assert_eq!(id, QueryId::batch(42));
77 }
78
79 #[test]
80 fn parse_arbitrary() {
81 let id = QueryId::parse("custom-id");
82 assert_eq!(id.as_str(), "custom-id");
83 }
84
85 #[test]
86 fn query_id_roundtrip_json() {
87 let id = QueryId::batch(42);
88 let json = serde_json::to_string(&id).unwrap();
89 let restored: QueryId = serde_json::from_str(&json).unwrap();
90 assert_eq!(id, restored);
91 }
92
93 #[test]
94 fn llm_query_roundtrip_json() {
95 let query = LlmQuery {
96 id: QueryId::single(),
97 prompt: "test prompt".into(),
98 system: Some("system".into()),
99 max_tokens: 1024,
100 };
101 let json = serde_json::to_value(&query).unwrap();
102 let restored: LlmQuery = serde_json::from_value(json).unwrap();
103 assert_eq!(restored.id, query.id);
104 assert_eq!(restored.prompt, query.prompt);
105 assert_eq!(restored.system, query.system);
106 assert_eq!(restored.max_tokens, query.max_tokens);
107 }
108}
109
110#[cfg(test)]
111mod proptests {
112 use super::*;
113 use proptest::prelude::*;
114
115 proptest! {
116 #[test]
117 fn parse_roundtrip_arbitrary(s in "\\PC{1,100}") {
118 let id = QueryId::parse(&s);
119 prop_assert_eq!(id.as_str(), s.as_str());
120 }
121
122 #[test]
123 fn batch_roundtrip(index in 0usize..10_000) {
124 let id = QueryId::batch(index);
125 let expected = format!("q-{index}");
126 prop_assert_eq!(id.as_str(), expected.as_str());
127 }
128
129 #[test]
130 fn display_matches_as_str(s in "\\PC{1,100}") {
131 let id = QueryId::parse(&s);
132 prop_assert_eq!(id.to_string(), id.as_str().to_string());
133 }
134
135 #[test]
136 fn serde_roundtrip_arbitrary(s in "\\PC{1,100}") {
137 let id = QueryId::parse(&s);
138 let json = serde_json::to_string(&id).unwrap();
139 let restored: QueryId = serde_json::from_str(&json).unwrap();
140 prop_assert_eq!(id, restored);
141 }
142 }
143}