use std::fmt;
use crate::ShapeError;
#[non_exhaustive]
#[derive(Debug)]
pub enum TextError {
BatchTooLarge { len: usize, max: usize },
InputTooLong {
index: usize,
len: usize,
max: usize,
},
CorpusTooLarge { total_bytes: usize, max: usize },
VocabularyTooLarge { len: usize, max: usize },
VocabularyTokenTooLong {
index: usize,
len: usize,
max: usize,
},
EmptyVocabularyToken { index: usize },
ReservedVocabularyToken { index: usize },
DuplicateVocabularyToken { first: usize, second: usize },
InvalidMaxTokens { max_tokens: usize },
InvalidNgrams { n: usize, max: usize },
OutputSequenceLengthTooLarge { len: usize, max: usize },
TooManyDistinctTokens { max: usize },
OutputTooLarge { elements: usize, max: usize },
LimitAboveAbsoluteMaximum {
limit: &'static str,
value: usize,
max: usize,
},
NgramCountOverflow { tokens: usize, n: usize },
FrequencyOverflow,
MissingIdfWeights,
IdfWeightsLengthMismatch { len: usize, expected: usize },
InvalidIdfWeight { index: usize },
PadToMaxTokensWithoutMaxTokens,
TokenCountNotExact {
index: usize,
count: usize,
max: usize,
},
IdOutOfRange { index: usize },
Shape(ShapeError),
}
impl fmt::Display for TextError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TextError::BatchTooLarge { len, max } => write!(
f,
"バッチ(コーパス)の件数が上限を超えています({len} 件 > 上限 {max} 件)"
),
TextError::InputTooLong { index, len, max } => write!(
f,
"入力文字列が上限を超えています(index={index}・{len} バイト > 上限 {max} バイト)"
),
TextError::CorpusTooLarge { total_bytes, max } => write!(
f,
"コーパス総バイト数が上限を超えています({total_bytes} バイト > 上限 {max} バイト)"
),
TextError::VocabularyTooLarge { len, max } => {
write!(f, "語彙数が上限を超えています({len} 件 > 上限 {max} 件)")
}
TextError::VocabularyTokenTooLong { index, len, max } => write!(
f,
"語彙が上限を超えています(index={index}・{len} バイト > 上限 {max} バイト)"
),
TextError::EmptyVocabularyToken { index } => {
write!(f, "語彙が空文字列です(index={index})")
}
TextError::ReservedVocabularyToken { index } => {
write!(f, "語彙が予約語です(index={index})")
}
TextError::DuplicateVocabularyToken { first, second } => {
write!(f, "語彙が重複しています(index={first} と index={second})")
}
TextError::InvalidMaxTokens { max_tokens } => {
write!(f, "max_tokens は 3 以上が必要です(指定値 {max_tokens})")
}
TextError::InvalidNgrams { n, max } => {
write!(f, "n-gram の n が不正です(n={n}・有効範囲 1..={max})")
}
TextError::OutputSequenceLengthTooLarge { len, max } => write!(
f,
"output_sequence_length が上限を超えています({len} > 上限 {max})"
),
TextError::TooManyDistinctTokens { max } => {
write!(f, "異なり語数が上限 {max} 件を超えています")
}
TextError::OutputTooLarge { elements, max } => write!(
f,
"出力要素数が上限を超えています({elements} 要素 > 上限 {max} 要素)"
),
TextError::LimitAboveAbsoluteMaximum { limit, value, max } => write!(
f,
"上限の設定値が絶対上限を超えています({limit}={value} > 絶対上限 {max})"
),
TextError::NgramCountOverflow { tokens, n } => write!(
f,
"n-gram の生成数が数えられる範囲を超えています(トークン数 {tokens} × n={n})"
),
TextError::FrequencyOverflow => {
write!(f, "語の出現頻度が数えられる範囲を超えています")
}
TextError::MissingIdfWeights => write!(
f,
"idf 重みがありません(from_vocabulary_with_idf または adapt_tf_idf で作る必要があります)"
),
TextError::IdfWeightsLengthMismatch { len, expected } => write!(
f,
"idf 重みの個数が語彙の個数と一致しません({len} 個 != 語彙 {expected} 個)"
),
TextError::InvalidIdfWeight { index } => {
write!(f, "idf 重みが有限の非負値ではありません(index={index})")
}
TextError::PadToMaxTokensWithoutMaxTokens => {
write!(f, "pad_to_max_tokens には max_tokens の指定が必要です")
}
TextError::TokenCountNotExact { index, count, max } => write!(
f,
"トークン数が f32 で正確に数えられる上限を超えています(index={index}・{count} 個 > 上限 {max} 個)"
),
TextError::IdOutOfRange { index } => {
write!(f, "id が語彙の範囲外です(index={index})")
}
TextError::Shape(e) => write!(f, "テンソル形状が不正です: {e}"),
}
}
}
impl std::error::Error for TextError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
TextError::Shape(e) => Some(e),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error;
fn all_variants() -> Vec<TextError> {
vec![
TextError::BatchTooLarge { len: 3, max: 2 },
TextError::InputTooLong {
index: 1,
len: 9,
max: 8,
},
TextError::CorpusTooLarge {
total_bytes: 9,
max: 8,
},
TextError::VocabularyTooLarge { len: 5, max: 4 },
TextError::VocabularyTokenTooLong {
index: 2,
len: 7,
max: 6,
},
TextError::EmptyVocabularyToken { index: 0 },
TextError::ReservedVocabularyToken { index: 1 },
TextError::DuplicateVocabularyToken {
first: 0,
second: 4,
},
TextError::InvalidMaxTokens { max_tokens: 2 },
TextError::InvalidNgrams { n: 0, max: 8 },
TextError::OutputSequenceLengthTooLarge { len: 11, max: 10 },
TextError::TooManyDistinctTokens { max: 10 },
TextError::OutputTooLarge {
elements: 11,
max: 10,
},
TextError::LimitAboveAbsoluteMaximum {
limit: "max_ngrams",
value: 9,
max: 8,
},
TextError::NgramCountOverflow {
tokens: usize::MAX,
n: 2,
},
TextError::FrequencyOverflow,
TextError::MissingIdfWeights,
TextError::IdfWeightsLengthMismatch {
len: 3,
expected: 4,
},
TextError::InvalidIdfWeight { index: 2 },
TextError::PadToMaxTokensWithoutMaxTokens,
TextError::TokenCountNotExact {
index: 1,
count: 9,
max: 8,
},
TextError::IdOutOfRange { index: 3 },
TextError::Shape(ShapeError::RankMismatch {
expected: 2,
actual: 3,
}),
]
}
#[test]
fn display_is_nonempty_for_every_variant() {
for e in all_variants() {
assert!(!e.to_string().is_empty(), "{e:?}");
}
}
#[test]
fn source_is_some_only_for_shape() {
for e in all_variants() {
let is_shape = matches!(e, TextError::Shape(_));
assert_eq!(e.source().is_some(), is_shape, "{e:?}");
}
}
#[test]
fn variants_hold_numbers_only_so_no_input_text_leaks() {
let marker = "SECRET_MARKER_TOKEN";
for e in all_variants() {
assert!(!e.to_string().contains(marker));
assert!(!format!("{e:?}").contains(marker));
}
}
}