Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rust APIが公開するエラーの種類をErrorKindとして表現する #589

Merged
merged 7 commits into from
Sep 3, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
9 changes: 9 additions & 0 deletions crates/voicevox_core/src/__internal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// FIXME: 要議論: https://github.com/VOICEVOX/voicevox_core/issues/595
qryxip marked this conversation as resolved.
Show resolved Hide resolved

pub fn to_zenkaku(surface: &str) -> String {
crate::user_dict::to_zenkaku(surface)
}
Hiroshiba marked this conversation as resolved.
Show resolved Hide resolved

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> {
Copy link
Member Author

@qryxip qryxip Aug 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

各エラー型の可視性の降格により、シグネチャにそれらのエラー型が表われる関数がコンパイルエラーになるので同様に可視性を調整。

これによりコード中に特に理由無くpubpub(crate)が混在することになりますが、Rust API内のアイテムの可視性については元々深く考えられていないということで今のところはよいかなと思いました。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

混じってしまうのはまあありなのかなと思いました。
ただ混じってしまうぐらいなんだったら、今は全部統一的にpubにしておいて、pub(crate)にする機会を別途作成してそこで一気に変更とかもありかもとか思いました。

まあでもせっかく書いちゃったんだし、こちらのプルリクエストの形で一旦マージするのもありかもです。
あまり強い意見ではないです。

Copy link
Member Author

@qryxip qryxip Aug 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#594 を作りました。
(初心者歓迎タスクにしました)

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);
Comment on lines +11 to +12
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

多分できることがError==ErrorReprだと思うのですが、であれば最初から内部で使う構造体もErrorで良いのではとちょっと思いました。
メリットがあれば知りたいです。
あるいは一般的にクレート内のエラー構造体と外に出すエラー構造体はレイヤーが異なるため型を分けるべきである、みたいな思想があるのであれば一旦従うのもありかなと思いました。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

こちらのコメントで納得したのでResolveです!


#[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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UnloadedModelと統合される形になると思ってます。

#[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 {
Copy link
Member Author

@qryxip qryxip Aug 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ErrorKindの定義および変換をしてくれるライブラリがあった。
kinded

作者はnutypewhatlangを作った人。強制的にDisplay/FromStrが付く点には異議を唱えたいところではあるが、それ以外は筋がよいと感じるし、実装のコードも簡潔。

が、initial commitが28日前、最終更新日が21日前で、総ダウンロード数658…

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

もうちょっと待ってみて、ライブラリ側やこちら側も安定して来てそうだったら導入を検討しても良さそうなのかなと思いました!

/// 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,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここのdocは現状のResultCodeからそのまま持って来ました。

}

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
Loading