made_core/value_objects/
task_description.rs1use std::fmt;
9
10use serde::{Deserialize, Serialize};
11
12use crate::error::DomainError;
13
14pub const MAX_TASK_DESCRIPTION_LEN: usize = 64 * 1024;
17
18#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
19#[serde(transparent)]
20pub struct TaskDescription(String);
21
22impl TaskDescription {
23 pub fn new(raw: impl Into<String>) -> Result<Self, DomainError> {
24 let value = raw.into();
25 if value.trim().is_empty() {
26 return Err(DomainError::EmptyField {
27 field: "task_description",
28 });
29 }
30 if value.len() > MAX_TASK_DESCRIPTION_LEN {
31 return Err(DomainError::FieldTooLong {
32 field: "task_description",
33 actual: value.len(),
34 max: MAX_TASK_DESCRIPTION_LEN,
35 });
36 }
37 Ok(Self(value))
38 }
39
40 #[must_use]
41 pub fn as_str(&self) -> &str {
42 &self.0
43 }
44
45 #[must_use]
46 pub fn into_inner(self) -> String {
47 self.0
48 }
49}
50
51impl fmt::Display for TaskDescription {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 f.write_str(&self.0)
54 }
55}
56
57impl TryFrom<&str> for TaskDescription {
58 type Error = DomainError;
59 fn try_from(value: &str) -> Result<Self, Self::Error> {
60 Self::new(value)
61 }
62}
63
64impl TryFrom<String> for TaskDescription {
65 type Error = DomainError;
66 fn try_from(value: String) -> Result<Self, Self::Error> {
67 Self::new(value)
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[test]
76 fn typical_prompt_is_accepted() {
77 assert_eq!(
78 TaskDescription::new("Summarize the alert payload.")
79 .unwrap()
80 .as_str(),
81 "Summarize the alert payload."
82 );
83 }
84
85 #[test]
86 fn whitespace_only_is_rejected() {
87 assert!(matches!(
88 TaskDescription::new(" \n\t").unwrap_err(),
89 DomainError::EmptyField {
90 field: "task_description"
91 }
92 ));
93 }
94
95 #[test]
96 fn overlong_is_rejected() {
97 let too_long = "a".repeat(MAX_TASK_DESCRIPTION_LEN + 1);
98 assert!(matches!(
99 TaskDescription::new(too_long).unwrap_err(),
100 DomainError::FieldTooLong { .. }
101 ));
102 }
103
104 #[test]
105 fn multiline_is_preserved_verbatim() {
106 let d = TaskDescription::new("line one\nline two").unwrap();
107 assert_eq!(d.as_str(), "line one\nline two");
108 }
109
110 #[test]
111 fn serde_is_transparent() {
112 let d = TaskDescription::new("hi").unwrap();
113 assert_eq!(serde_json::to_string(&d).unwrap(), "\"hi\"");
114 }
115}