aqc_text_engine_core/requirement/
model.rs1#![allow(
4 clippy::module_name_repetitions,
5 reason = "Public text-engine values use the TextFile prefix."
6)]
7
8use core::any::Any;
9use core::cmp::Ordering;
10use core::fmt;
11
12use aqc_file_engine_core::{
13 ConflictEntry, EngineRequirement, FileItemRequirement, ItemAssertionInput, ItemRequirements,
14 RequiredItemResolution, ResolvedItemRequirements, ResolvedRequirement, ScalarAssertion,
15 ScalarValue,
16};
17
18#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
19pub struct TextSnippetId(String);
21
22#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
23pub struct TextFileContents(Vec<u8>);
25
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub enum TextFileValueError {
29 Empty { field: &'static str },
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct TextSnippet {
36 pub id: TextSnippetId,
38 pub contents: TextFileContents,
40}
41
42#[derive(Debug, Clone, Default)]
43pub struct TextFileRequirements {
45 pub exact_contents: Option<ScalarAssertion<TextFileContents>>,
47 pub required_snippets: ItemRequirements<TextSnippet>,
49}
50
51#[derive(Debug, Clone, Default)]
52pub struct ResolvedTextFileRequirements {
54 pub exact_contents: Option<
56 ResolvedRequirement<ScalarAssertion<TextFileContents>, ScalarAssertion<TextFileContents>>,
57 >,
58 pub required_snippets: ResolvedItemRequirements<TextSnippet>,
60}
61
62impl EngineRequirement for TextFileRequirements {
63 fn engine_id(&self) -> &'static str {
64 crate::ENGINE_ID
65 }
66
67 fn as_any(&self) -> &dyn Any {
68 self
69 }
70}
71
72impl TextSnippetId {
73 pub fn new(value: impl Into<String>) -> Result<Self, TextFileValueError> {
79 let value = value.into();
80 if value.is_empty() {
81 return Err(TextFileValueError::Empty { field: "snippet" });
82 }
83 Ok(Self(value))
84 }
85
86 #[must_use]
87 pub fn as_str(&self) -> &str {
88 &self.0
89 }
90}
91
92impl TextFileContents {
93 pub fn new(value: impl Into<Vec<u8>>) -> Result<Self, TextFileValueError> {
99 let value = value.into();
100 if value.is_empty() {
101 return Err(TextFileValueError::Empty { field: "contents" });
102 }
103 Ok(Self(value))
104 }
105
106 #[must_use]
107 pub fn as_bytes(&self) -> &[u8] {
108 &self.0
109 }
110}
111
112impl fmt::Display for TextFileValueError {
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 match self {
115 Self::Empty { field } => write!(f, "{field} must not be empty"),
116 }
117 }
118}
119
120impl fmt::Display for TextSnippetId {
121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122 f.write_str(&self.0)
123 }
124}
125
126impl ScalarValue for TextFileContents {
127 fn render(&self) -> String {
128 format!("{} bytes", self.0.len())
129 }
130
131 fn compare_for_order(&self, _other: &Self) -> Option<Ordering> {
132 None
133 }
134}
135
136impl FileItemRequirement for TextSnippet {
137 type Identity = TextSnippetId;
138
139 fn merge_identity(&self) -> Self::Identity {
140 self.id.clone()
141 }
142
143 fn compose_item(
144 key: &str,
145 items: Vec<ItemAssertionInput<Self>>,
146 conflicts: &mut Vec<ConflictEntry>,
147 ) -> Option<RequiredItemResolution<Self>> {
148 aqc_file_engine_core::compose_item_by(key, items, |item| item.contents.clone(), conflicts)
149 }
150}