1use std::fmt;
16
17use crate::ShapeError;
18
19#[non_exhaustive]
25#[derive(Debug)]
26pub enum TextError {
27 BatchTooLarge { len: usize, max: usize },
29 InputTooLong {
32 index: usize,
33 len: usize,
34 max: usize,
35 },
36 CorpusTooLarge { total_bytes: usize, max: usize },
39 VocabularyTooLarge { len: usize, max: usize },
41 VocabularyTokenTooLong {
43 index: usize,
44 len: usize,
45 max: usize,
46 },
47 EmptyVocabularyToken { index: usize },
49 ReservedVocabularyToken { index: usize },
51 DuplicateVocabularyToken { first: usize, second: usize },
53 InvalidMaxTokens { max_tokens: usize },
55 InvalidNgrams { n: usize, max: usize },
57 OutputSequenceLengthTooLarge { len: usize, max: usize },
59 TooManyDistinctTokens { max: usize },
61 OutputTooLarge { elements: usize, max: usize },
64 LimitAboveAbsoluteMaximum {
68 limit: &'static str,
69 value: usize,
70 max: usize,
71 },
72 NgramCountOverflow { tokens: usize, n: usize },
75 FrequencyOverflow,
79 MissingIdfWeights,
82 IdfWeightsLengthMismatch { len: usize, expected: usize },
85 InvalidIdfWeight { index: usize },
87 PadToMaxTokensWithoutMaxTokens,
89 TokenCountNotExact {
92 index: usize,
93 count: usize,
94 max: usize,
95 },
96 IdOutOfRange { index: usize },
99 Shape(ShapeError),
101}
102
103impl fmt::Display for TextError {
104 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105 match self {
106 TextError::BatchTooLarge { len, max } => write!(
107 f,
108 "バッチ(コーパス)の件数が上限を超えています({len} 件 > 上限 {max} 件)"
109 ),
110 TextError::InputTooLong { index, len, max } => write!(
111 f,
112 "入力文字列が上限を超えています(index={index}・{len} バイト > 上限 {max} バイト)"
113 ),
114 TextError::CorpusTooLarge { total_bytes, max } => write!(
115 f,
116 "コーパス総バイト数が上限を超えています({total_bytes} バイト > 上限 {max} バイト)"
117 ),
118 TextError::VocabularyTooLarge { len, max } => {
119 write!(f, "語彙数が上限を超えています({len} 件 > 上限 {max} 件)")
120 }
121 TextError::VocabularyTokenTooLong { index, len, max } => write!(
122 f,
123 "語彙が上限を超えています(index={index}・{len} バイト > 上限 {max} バイト)"
124 ),
125 TextError::EmptyVocabularyToken { index } => {
126 write!(f, "語彙が空文字列です(index={index})")
127 }
128 TextError::ReservedVocabularyToken { index } => {
129 write!(f, "語彙が予約語です(index={index})")
130 }
131 TextError::DuplicateVocabularyToken { first, second } => {
132 write!(f, "語彙が重複しています(index={first} と index={second})")
133 }
134 TextError::InvalidMaxTokens { max_tokens } => {
135 write!(f, "max_tokens は 3 以上が必要です(指定値 {max_tokens})")
136 }
137 TextError::InvalidNgrams { n, max } => {
138 write!(f, "n-gram の n が不正です(n={n}・有効範囲 1..={max})")
139 }
140 TextError::OutputSequenceLengthTooLarge { len, max } => write!(
141 f,
142 "output_sequence_length が上限を超えています({len} > 上限 {max})"
143 ),
144 TextError::TooManyDistinctTokens { max } => {
145 write!(f, "異なり語数が上限 {max} 件を超えています")
146 }
147 TextError::OutputTooLarge { elements, max } => write!(
148 f,
149 "出力要素数が上限を超えています({elements} 要素 > 上限 {max} 要素)"
150 ),
151 TextError::LimitAboveAbsoluteMaximum { limit, value, max } => write!(
152 f,
153 "上限の設定値が絶対上限を超えています({limit}={value} > 絶対上限 {max})"
154 ),
155 TextError::NgramCountOverflow { tokens, n } => write!(
156 f,
157 "n-gram の生成数が数えられる範囲を超えています(トークン数 {tokens} × n={n})"
158 ),
159 TextError::FrequencyOverflow => {
160 write!(f, "語の出現頻度が数えられる範囲を超えています")
161 }
162 TextError::MissingIdfWeights => write!(
163 f,
164 "idf 重みがありません(from_vocabulary_with_idf または adapt_tf_idf で作る必要があります)"
165 ),
166 TextError::IdfWeightsLengthMismatch { len, expected } => write!(
167 f,
168 "idf 重みの個数が語彙の個数と一致しません({len} 個 != 語彙 {expected} 個)"
169 ),
170 TextError::InvalidIdfWeight { index } => {
171 write!(f, "idf 重みが有限の非負値ではありません(index={index})")
172 }
173 TextError::PadToMaxTokensWithoutMaxTokens => {
174 write!(f, "pad_to_max_tokens には max_tokens の指定が必要です")
175 }
176 TextError::TokenCountNotExact { index, count, max } => write!(
177 f,
178 "トークン数が f32 で正確に数えられる上限を超えています(index={index}・{count} 個 > 上限 {max} 個)"
179 ),
180 TextError::IdOutOfRange { index } => {
181 write!(f, "id が語彙の範囲外です(index={index})")
182 }
183 TextError::Shape(e) => write!(f, "テンソル形状が不正です: {e}"),
184 }
185 }
186}
187
188impl std::error::Error for TextError {
189 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
190 match self {
191 TextError::Shape(e) => Some(e),
192 _ => None,
193 }
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200 use std::error::Error;
201
202 fn all_variants() -> Vec<TextError> {
203 vec![
204 TextError::BatchTooLarge { len: 3, max: 2 },
205 TextError::InputTooLong {
206 index: 1,
207 len: 9,
208 max: 8,
209 },
210 TextError::CorpusTooLarge {
211 total_bytes: 9,
212 max: 8,
213 },
214 TextError::VocabularyTooLarge { len: 5, max: 4 },
215 TextError::VocabularyTokenTooLong {
216 index: 2,
217 len: 7,
218 max: 6,
219 },
220 TextError::EmptyVocabularyToken { index: 0 },
221 TextError::ReservedVocabularyToken { index: 1 },
222 TextError::DuplicateVocabularyToken {
223 first: 0,
224 second: 4,
225 },
226 TextError::InvalidMaxTokens { max_tokens: 2 },
227 TextError::InvalidNgrams { n: 0, max: 8 },
228 TextError::OutputSequenceLengthTooLarge { len: 11, max: 10 },
229 TextError::TooManyDistinctTokens { max: 10 },
230 TextError::OutputTooLarge {
231 elements: 11,
232 max: 10,
233 },
234 TextError::LimitAboveAbsoluteMaximum {
235 limit: "max_ngrams",
236 value: 9,
237 max: 8,
238 },
239 TextError::NgramCountOverflow {
240 tokens: usize::MAX,
241 n: 2,
242 },
243 TextError::FrequencyOverflow,
244 TextError::MissingIdfWeights,
245 TextError::IdfWeightsLengthMismatch {
246 len: 3,
247 expected: 4,
248 },
249 TextError::InvalidIdfWeight { index: 2 },
250 TextError::PadToMaxTokensWithoutMaxTokens,
251 TextError::TokenCountNotExact {
252 index: 1,
253 count: 9,
254 max: 8,
255 },
256 TextError::IdOutOfRange { index: 3 },
257 TextError::Shape(ShapeError::RankMismatch {
258 expected: 2,
259 actual: 3,
260 }),
261 ]
262 }
263
264 #[test]
265 fn display_is_nonempty_for_every_variant() {
266 for e in all_variants() {
267 assert!(!e.to_string().is_empty(), "{e:?}");
268 }
269 }
270
271 #[test]
272 fn source_is_some_only_for_shape() {
273 for e in all_variants() {
274 let is_shape = matches!(e, TextError::Shape(_));
275 assert_eq!(e.source().is_some(), is_shape, "{e:?}");
276 }
277 }
278
279 #[test]
280 fn variants_hold_numbers_only_so_no_input_text_leaks() {
281 let marker = "SECRET_MARKER_TOKEN";
284 for e in all_variants() {
285 assert!(!e.to_string().contains(marker));
286 assert!(!format!("{e:?}").contains(marker));
287 }
288 }
289}