Skip to content

Commit

Permalink
Rust APIが公開するエラーの種類をErrorKindとして表現する (#589)
Browse files Browse the repository at this point in the history
Co-authored-by: Hiroshiba <[email protected]>
  • Loading branch information
qryxip and Hiroshiba authored Sep 3, 2023
1 parent 5e82723 commit 203a75b
Show file tree
Hide file tree
Showing 20 changed files with 201 additions and 118 deletions.
13 changes: 11 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/voicevox_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ cfg-if = "1.0.0"
derive-getters.workspace = true
derive-new = "0.5.9"
derive_more = "0.99.17"
duplicate = "1.0.0"
easy-ext.workspace = true
fs-err.workspace = true
futures = "0.3.26"
Expand Down
10 changes: 10 additions & 0 deletions crates/voicevox_core/src/__internal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//! VOICEVOX CORE内のラッパー向けの実装
// FIXME: 要議論: https://github.com/VOICEVOX/voicevox_core/issues/595

pub fn to_zenkaku(surface: &str) -> String {
crate::user_dict::to_zenkaku(surface)
}

pub fn validate_pronunciation(pronunciation: &str) -> crate::Result<()> {
crate::user_dict::validate_pronunciation(pronunciation).map_err(Into::into)
}
2 changes: 1 addition & 1 deletion crates/voicevox_core/src/devices.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ impl SupportedDevices {
let mut cuda_support = false;
let mut dml_support = false;
for provider in onnxruntime::session::get_available_providers()
.map_err(|e| Error::GetSupportedDevices(e.into()))?
.map_err(|e| ErrorRepr::GetSupportedDevices(e.into()))?
.iter()
{
match provider.as_str() {
Expand Down
12 changes: 6 additions & 6 deletions crates/voicevox_core/src/engine/full_context_label.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use once_cell::sync::Lazy;
use regex::Regex;

#[derive(thiserror::Error, Debug)]
pub enum FullContextLabelError {
pub(crate) enum FullContextLabelError {
#[error("label parse error label:{label}")]
LabelParse { label: String },

Expand Down Expand Up @@ -49,7 +49,7 @@ fn string_feature_by_regex(re: &Regex, label: &str) -> Result<String> {
}

impl Phoneme {
pub fn from_label(label: impl Into<String>) -> Result<Self> {
pub(crate) fn from_label(label: impl Into<String>) -> Result<Self> {
let mut contexts = HashMap::<String, String>::with_capacity(10);
let label = label.into();
contexts.insert("p3".into(), string_feature_by_regex(&P3_REGEX, &label)?);
Expand Down Expand Up @@ -116,7 +116,7 @@ pub struct AccentPhrase {
}

impl AccentPhrase {
pub fn from_phonemes(mut phonemes: Vec<Phoneme>) -> Result<Self> {
pub(crate) fn from_phonemes(mut phonemes: Vec<Phoneme>) -> Result<Self> {
let mut moras = Vec::with_capacity(phonemes.len());
let mut mora_phonemes = Vec::with_capacity(phonemes.len());
for i in 0..phonemes.len() {
Expand Down Expand Up @@ -208,7 +208,7 @@ pub struct BreathGroup {
}

impl BreathGroup {
pub fn from_phonemes(phonemes: Vec<Phoneme>) -> Result<Self> {
pub(crate) fn from_phonemes(phonemes: Vec<Phoneme>) -> Result<Self> {
let mut accent_phrases = Vec::with_capacity(phonemes.len());
let mut accent_phonemes = Vec::with_capacity(phonemes.len());
for i in 0..phonemes.len() {
Expand Down Expand Up @@ -256,7 +256,7 @@ pub struct Utterance {
}

impl Utterance {
pub fn from_phonemes(phonemes: Vec<Phoneme>) -> Result<Self> {
pub(crate) fn from_phonemes(phonemes: Vec<Phoneme>) -> Result<Self> {
let mut breath_groups = vec![];
let mut group_phonemes = Vec::with_capacity(phonemes.len());
let mut pauses = vec![];
Expand Down Expand Up @@ -305,7 +305,7 @@ impl Utterance {
self.phonemes().iter().map(|p| p.label().clone()).collect()
}

pub fn extract_full_context_label(
pub(crate) fn extract_full_context_label(
open_jtalk: &open_jtalk::OpenJtalk,
text: impl AsRef<str>,
) -> Result<Self> {
Expand Down
4 changes: 2 additions & 2 deletions crates/voicevox_core/src/engine/kana_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const WIDE_INTERROGATION_MARK: char = '?';
const LOOP_LIMIT: usize = 300;

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct KanaParseError(String);
pub(crate) struct KanaParseError(String);

impl std::fmt::Display for KanaParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Expand Down Expand Up @@ -122,7 +122,7 @@ fn text_to_accent_phrase(phrase: &str) -> KanaParseResult<AccentPhraseModel> {
))
}

pub fn parse_kana(text: &str) -> KanaParseResult<Vec<AccentPhraseModel>> {
pub(crate) fn parse_kana(text: &str) -> KanaParseResult<Vec<AccentPhraseModel>> {
const TERMINATOR: char = '\0';
let mut parsed_result = Vec::new();
let chars_of_text = text.chars().chain([TERMINATOR]);
Expand Down
25 changes: 13 additions & 12 deletions crates/voicevox_core/src/engine/open_jtalk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ use tempfile::NamedTempFile;

use ::open_jtalk::*;

use crate::{Error, UserDict};
use crate::{error::ErrorRepr, UserDict};

#[derive(thiserror::Error, Debug)]
pub enum OpenJtalkError {
pub(crate) enum OpenJtalkError {
#[error("open_jtalk load error")]
Load { mecab_dict_dir: PathBuf },
#[error("open_jtalk extract_fullcontext error")]
Expand All @@ -21,7 +21,7 @@ pub enum OpenJtalkError {
},
}

pub type Result<T> = std::result::Result<T, OpenJtalkError>;
type Result<T> = std::result::Result<T, OpenJtalkError>;

/// テキスト解析器としてのOpen JTalk。
pub struct OpenJtalk {
Expand Down Expand Up @@ -54,7 +54,7 @@ impl OpenJtalk {
) -> crate::result::Result<Self> {
let mut s = Self::new_without_dic();
s.load(open_jtalk_dict_dir)
.map_err(|_| Error::NotLoadedOpenjtalkDict)?;
.map_err(|_| ErrorRepr::NotLoadedOpenjtalkDict)?;
Ok(s)
}

Expand All @@ -67,15 +67,16 @@ impl OpenJtalk {
.dict_dir
.as_ref()
.and_then(|dict_dir| dict_dir.to_str())
.ok_or(Error::NotLoadedOpenjtalkDict)?;
.ok_or(ErrorRepr::NotLoadedOpenjtalkDict)?;

// ユーザー辞書用のcsvを作成
let mut temp_csv = NamedTempFile::new().map_err(|e| Error::UseUserDict(e.to_string()))?;
let mut temp_csv =
NamedTempFile::new().map_err(|e| ErrorRepr::UseUserDict(e.to_string()))?;
temp_csv
.write_all(user_dict.to_mecab_format().as_bytes())
.map_err(|e| Error::UseUserDict(e.to_string()))?;
.map_err(|e| ErrorRepr::UseUserDict(e.to_string()))?;
let temp_csv_path = temp_csv.into_temp_path();
let temp_dict = NamedTempFile::new().map_err(|e| Error::UseUserDict(e.to_string()))?;
let temp_dict = NamedTempFile::new().map_err(|e| ErrorRepr::UseUserDict(e.to_string()))?;
let temp_dict_path = temp_dict.into_temp_path();

// Mecabでユーザー辞書をコンパイル
Expand All @@ -99,15 +100,15 @@ impl OpenJtalk {
let result = mecab.load_with_userdic(Path::new(dict_dir), Some(Path::new(&temp_dict_path)));

if !result {
return Err(Error::UseUserDict(
"辞書のコンパイルに失敗しました".to_string(),
));
return Err(
ErrorRepr::UseUserDict("辞書のコンパイルに失敗しました".to_string()).into(),
);
}

Ok(())
}

pub fn extract_fullcontext(&self, text: impl AsRef<str>) -> Result<Vec<String>> {
pub(crate) fn extract_fullcontext(&self, text: impl AsRef<str>) -> Result<Vec<String>> {
let Resources {
mecab,
njd,
Expand Down
109 changes: 100 additions & 9 deletions crates/voicevox_core/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,66 @@
use self::engine::{FullContextLabelError, KanaParseError};
use super::*;
//use engine::
use duplicate::duplicate_item;
use std::path::PathBuf;
use thiserror::Error;
use uuid::Uuid;

/// VOICEVOX COREのエラー。
#[derive(Error, Debug)]
pub enum Error {
#[error(transparent)]
pub struct Error(#[from] ErrorRepr);

#[duplicate_item(
E;
[ LoadModelError ];
[ FullContextLabelError ];
[ KanaParseError ];
)]
impl From<E> for Error {
fn from(err: E) -> Self {
Self(err.into())
}
}

// FIXME: `ErrorRepr::InvalidWord`を`#[error(transparent)]`にする
impl From<InvalidWordError> for Error {
fn from(err: InvalidWordError) -> Self {
ErrorRepr::InvalidWord(err).into()
}
}

impl Error {
/// 対応する[`ErrorKind`]を返す。
pub fn kind(&self) -> ErrorKind {
match &self.0 {
ErrorRepr::NotLoadedOpenjtalkDict => ErrorKind::NotLoadedOpenjtalkDict,
ErrorRepr::GpuSupport => ErrorKind::GpuSupport,
ErrorRepr::LoadModel(LoadModelError { context, .. }) => match context {
LoadModelErrorKind::OpenZipFile => ErrorKind::OpenZipFile,
LoadModelErrorKind::ReadZipEntry { .. } => ErrorKind::ReadZipEntry,
LoadModelErrorKind::ModelAlreadyLoaded { .. } => ErrorKind::ModelAlreadyLoaded,
LoadModelErrorKind::StyleAlreadyLoaded { .. } => ErrorKind::StyleAlreadyLoaded,
LoadModelErrorKind::InvalidModelData => ErrorKind::InvalidModelData,
},
ErrorRepr::UnloadedModel { .. } => ErrorKind::UnloadedModel,
ErrorRepr::GetSupportedDevices(_) => ErrorKind::GetSupportedDevices,
ErrorRepr::InvalidStyleId { .. } => ErrorKind::InvalidStyleId,
ErrorRepr::InvalidModelId { .. } => ErrorKind::InvalidModelId,
ErrorRepr::InferenceFailed => ErrorKind::InferenceFailed,
ErrorRepr::ExtractFullContextLabel(_) => ErrorKind::ExtractFullContextLabel,
ErrorRepr::ParseKana(_) => ErrorKind::ParseKana,
ErrorRepr::LoadUserDict(_) => ErrorKind::LoadUserDict,
ErrorRepr::SaveUserDict(_) => ErrorKind::SaveUserDict,
ErrorRepr::UnknownWord(_) => ErrorKind::UnknownWord,
ErrorRepr::UseUserDict(_) => ErrorKind::UseUserDict,
ErrorRepr::InvalidWord(_) => ErrorKind::InvalidWord,
}
}
}

#[derive(Error, Debug)]
pub(crate) enum ErrorRepr {
#[error("OpenJTalkの辞書が読み込まれていません")]
NotLoadedOpenjtalkDict,

Expand All @@ -26,6 +79,7 @@ pub enum Error {
#[error("無効なspeaker_idです: {style_id:?}")]
InvalidStyleId { style_id: StyleId },

#[allow(dead_code)] // FIXME
#[error("無効なmodel_idです: {model_id:?}")]
InvalidModelId { model_id: VoiceModelId },

Expand Down Expand Up @@ -54,6 +108,49 @@ pub enum Error {
InvalidWord(InvalidWordError),
}

/// エラーの種類。
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum ErrorKind {
/// open_jtalk辞書ファイルが読み込まれていない。
NotLoadedOpenjtalkDict,
/// GPUモードがサポートされていない。
GpuSupport,
/// ZIPファイルを開くことに失敗した。
OpenZipFile,
/// ZIP内のファイルが読めなかった。
ReadZipEntry,
/// すでに読み込まれている音声モデルを読み込もうとした。
ModelAlreadyLoaded,
/// すでに読み込まれているスタイルを読み込もうとした。
StyleAlreadyLoaded,
/// 無効なモデルデータ。
InvalidModelData,
/// Modelが読み込まれていない。
UnloadedModel,
/// サポートされているデバイス情報取得に失敗した。
GetSupportedDevices,
/// 無効なstyle_idが指定された。
InvalidStyleId,
/// 無効なmodel_idが指定された。
InvalidModelId,
/// 推論に失敗した。
InferenceFailed,
/// コンテキストラベル出力に失敗した。
ExtractFullContextLabel,
/// AquesTalk風記法のテキストの解析に失敗した。
ParseKana,
/// ユーザー辞書を読み込めなかった。
LoadUserDict,
/// ユーザー辞書を書き込めなかった。
SaveUserDict,
/// ユーザー辞書に単語が見つからなかった。
UnknownWord,
/// OpenJTalkのユーザー辞書の設定に失敗した。
UseUserDict,
/// ユーザー辞書の単語のバリデーションに失敗した。
InvalidWord,
}

pub(crate) type LoadModelResult<T> = std::result::Result<T, LoadModelError>;

/// 音声モデル読み込みのエラー。
Expand All @@ -62,21 +159,15 @@ pub(crate) type LoadModelResult<T> = std::result::Result<T, LoadModelError>;
"`{path}`の読み込みに失敗しました: {context}{}",
source.as_ref().map(|e| format!(": {e}")).unwrap_or_default())
]
pub struct LoadModelError {
pub(crate) struct LoadModelError {
pub(crate) path: PathBuf,
pub(crate) context: LoadModelErrorKind,
#[source]
pub(crate) source: Option<anyhow::Error>,
}

impl LoadModelError {
pub fn context(&self) -> &LoadModelErrorKind {
&self.context
}
}

#[derive(derive_more::Display, Debug)]
pub enum LoadModelErrorKind {
pub(crate) enum LoadModelErrorKind {
#[display(fmt = "ZIPファイルとして開くことができませんでした")]
OpenZipFile,
#[display(fmt = "`{filename}`を読み取れませんでした")]
Expand Down
8 changes: 4 additions & 4 deletions crates/voicevox_core/src/inference_core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ impl InferenceCore {
}
Ok(Self { status })
} else {
Err(Error::GpuSupport)
Err(ErrorRepr::GpuSupport.into())
}
}

Expand Down Expand Up @@ -65,7 +65,7 @@ impl InferenceCore {
style_id: StyleId,
) -> Result<Vec<f32>> {
if !self.status.validate_speaker_id(style_id) {
return Err(Error::InvalidStyleId { style_id });
return Err(ErrorRepr::InvalidStyleId { style_id }.into());
}

let (model_id, model_inner_id) = self.status.ids_for(style_id)?;
Expand Down Expand Up @@ -100,7 +100,7 @@ impl InferenceCore {
style_id: StyleId,
) -> Result<Vec<f32>> {
if !self.status.validate_speaker_id(style_id) {
return Err(Error::InvalidStyleId { style_id });
return Err(ErrorRepr::InvalidStyleId { style_id }.into());
}

let (model_id, model_inner_id) = self.status.ids_for(style_id)?;
Expand Down Expand Up @@ -139,7 +139,7 @@ impl InferenceCore {
style_id: StyleId,
) -> Result<Vec<f32>> {
if !self.status.validate_speaker_id(style_id) {
return Err(Error::InvalidStyleId { style_id });
return Err(ErrorRepr::InvalidStyleId { style_id }.into());
}

let (model_id, model_inner_id) = self.status.ids_for(style_id)?;
Expand Down
Loading

0 comments on commit 203a75b

Please sign in to comment.