Skip to main content

wp_error/
run_error.rs

1use crate::config_error::{ConfCore, ConfError, ConfReason};
2use crate::parse_error::{OMLCodeError, OMLCodeReason};
3use derive_more::From;
4
5use orion_error::{OrionError, StructError, UnifiedReason};
6use orion_sec::OrionSecReason;
7use serde::Serialize;
8use thiserror::Error;
9
10pub type RunError = StructError<RunReason>;
11pub type RunResult<T> = Result<T, RunError>;
12
13#[derive(Debug, Error, PartialEq, Serialize, From)]
14pub enum DistFocus {
15    #[error("sink error : {0}")]
16    SinkError(String),
17    #[error("stg-ctrl")]
18    StgCtrl,
19}
20#[derive(Debug, Error, PartialEq, Serialize)]
21pub enum SourceFocus {
22    #[error("no data")]
23    NoData,
24    #[error("eof")]
25    Eof,
26    #[error("supplier error : {0}")]
27    SupplierError(String),
28    #[error("other : {0}")]
29    Other(String),
30    #[from(skip)]
31    #[error("disconnect : {0}")]
32    Disconnect(String),
33}
34
35#[derive(Debug, PartialEq, Serialize, From, OrionError)]
36pub enum RunReason {
37    #[orion_error(identity = "biz.dist")]
38    Dist(DistFocus),
39    #[orion_error(identity = "biz.source")]
40    Source(SourceFocus),
41    #[orion_error(transparent)]
42    Uvs(UnifiedReason),
43}
44
45impl From<ConfReason<ConfCore>> for RunReason {
46    fn from(_: ConfReason<ConfCore>) -> Self {
47        Self::core_conf()
48    }
49}
50impl From<OMLCodeReason> for RunReason {
51    fn from(_: OMLCodeReason) -> Self {
52        Self::core_conf()
53    }
54}
55
56/// 将 OML 错误转换为 `RunError`,并保留解析详情。
57///
58/// `conv_err()` 仅按 reason 转换(`OMLCodeReason → RunReason`),
59/// 而 OML 的富错误消息(`[path]/[where]/[error]`)存放在
60/// `Syntax/NotFound` 的 inner String 中、不在 StructError 的 detail 字段里,
61/// 因此转换后会被压缩为无 detail 的配置错误。
62/// 本 trait 把 inner 消息提取为 detail 并保留 source,供加载层调用。
63pub trait IntoRunError {
64    fn into_run_err(self) -> RunError;
65}
66
67impl IntoRunError for OMLCodeError {
68    fn into_run_err(self) -> RunError {
69        let detail = match self.reason() {
70            OMLCodeReason::Syntax(s) => s.clone(),
71            OMLCodeReason::NotFound(s) => s.clone(),
72            OMLCodeReason::Uvs(_) => self.to_string(),
73        };
74        RunReason::core_conf()
75            .to_err()
76            .with_detail(detail)
77            .with_source(self)
78    }
79}
80
81impl IntoRunError for ConfError {
82    fn into_run_err(self) -> RunError {
83        // 与 OML 同构:Syntax/NotFound 的内层消息才是真正的解析详情
84        let detail = match self.reason() {
85            ConfReason::Syntax(s) => s.clone(),
86            ConfReason::NotFound(s) => s.clone(),
87            ConfReason::Uvs(_) | ConfReason::_Take(_) => self.to_string(),
88        };
89        RunReason::core_conf()
90            .to_err()
91            .with_detail(detail)
92            .with_source(self)
93    }
94}
95
96impl IntoRunError for orion_conf::OrionConfError {
97    fn into_run_err(self) -> RunError {
98        // ConfIOReason::Other 携带底层解析错误(如 toml 语法错误),
99        // 转换时若丢弃则错误提示只剩泛化的 "配置错误"
100        let detail = match self.reason() {
101            orion_conf::ConfIOReason::Other(s) => s.clone(),
102            _ => self.to_string(),
103        };
104        RunReason::core_conf()
105            .to_err()
106            .with_detail(detail)
107            .with_source(self)
108    }
109}
110
111impl From<OrionSecReason> for RunReason {
112    fn from(value: OrionSecReason) -> Self {
113        match value {
114            OrionSecReason::Sec(_) => Self::core_conf(),
115            OrionSecReason::General(uvs_reason) => Self::Uvs(uvs_reason),
116        }
117    }
118}
119
120pub trait RunErrorOwe<T> {
121    fn owe_sink(self) -> RunResult<T>;
122    fn owe_source(self) -> RunResult<T>;
123}
124
125impl<T, E> RunErrorOwe<T> for Result<T, E>
126where
127    E: std::fmt::Display,
128{
129    fn owe_sink(self) -> RunResult<T> {
130        match self {
131            Ok(v) => Ok(v),
132            Err(e) => Err(RunReason::Dist(DistFocus::SinkError("sink fail".into()))
133                .to_err()
134                .with_detail(e.to_string())),
135        }
136    }
137    fn owe_source(self) -> RunResult<T> {
138        match self {
139            Ok(v) => Ok(v),
140            Err(e) => Err(
141                RunReason::Source(SourceFocus::SupplierError("source fail".into()))
142                    .to_err()
143                    .with_detail(e.to_string()),
144            ),
145        }
146    }
147}
148
149/*
150
151pub trait Option2RunResult<T> {
152    fn owe_logic(self, msg: &str) -> RunResult<T>;
153}
154
155impl<T> Option2RunResult<T> for Option<T> {
156    fn owe_logic(self, msg: &str) -> RunResult<T> {
157        self.ok_or(RunError::from(UnifiedReason::from_logic(msg.to_string())))
158    }
159}
160
161*/
162impl From<SourceReason> for RunReason {
163    fn from(e: SourceReason) -> Self {
164        match e {
165            SourceReason::NotData => Self::Source(SourceFocus::NoData),
166            SourceReason::EOF => Self::Source(SourceFocus::Eof),
167            SourceReason::SupplierError => Self::Source(SourceFocus::SupplierError(String::new())),
168            SourceReason::Disconnect => Self::Source(SourceFocus::Disconnect(String::new())),
169            SourceReason::Other => Self::Source(SourceFocus::Other(String::new())),
170            SourceReason::Uvs(uvs) => Self::Uvs(uvs),
171        }
172        //Self::Domain(RunReason::Source(e))
173    }
174}
175
176impl From<SinkReason> for RunReason {
177    fn from(e: SinkReason) -> Self {
178        match e {
179            SinkReason::Sink => Self::Dist(DistFocus::SinkError(String::new())),
180            // Map mock to stage control path for now (no panic in production paths)
181            SinkReason::Mock => Self::Dist(DistFocus::StgCtrl),
182            SinkReason::StgCtrl => Self::Dist(DistFocus::StgCtrl),
183            SinkReason::Uvs(uvs) => Self::Uvs(uvs),
184        }
185    }
186}
187use orion_conf::ToStructError as _;
188use orion_conf::error::ConfIOReason;
189use wp_connector_api::{SinkReason, SourceReason};
190impl From<ConfIOReason> for RunReason {
191    fn from(value: ConfIOReason) -> Self {
192        match value {
193            ConfIOReason::Other(_) => RunReason::core_conf(),
194            ConfIOReason::General(uvs) => RunReason::Uvs(uvs),
195            ConfIOReason::NoFormatEnabled => RunReason::core_conf(),
196        }
197    }
198}
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203    use crate::parse_error::OMLCodeReason;
204
205    #[test]
206    fn into_run_err_preserves_syntax_detail() {
207        let err = OMLCodeReason::Syntax(":oml code parse fail!\n[where]: line 5".into()).to_err();
208        let run = err.into_run_err();
209        assert!(
210            run.detail()
211                .as_deref()
212                .is_some_and(|d| d.contains("line 5")),
213            "detail should carry the OML inner message, got {:?}",
214            run.detail()
215        );
216    }
217
218    #[test]
219    fn into_run_err_keeps_config_reason() {
220        let err = OMLCodeReason::NotFound("missing file".into()).to_err();
221        let run = err.into_run_err();
222        assert!(
223            format!("{}", run.reason()).contains("config"),
224            "reason should stay a config error, got {}",
225            run.reason()
226        );
227    }
228
229    #[test]
230    fn conf_into_run_err_preserves_syntax_detail() {
231        let err = ConfReason::<ConfCore>::Syntax("invalid [sources] block".into()).to_err();
232        let run = err.into_run_err();
233        assert!(
234            run.detail()
235                .as_deref()
236                .is_some_and(|d| d.contains("invalid [sources] block")),
237            "ConfError detail should carry the inner message, got {:?}",
238            run.detail()
239        );
240    }
241
242    #[test]
243    fn conf_io_into_run_err_preserves_other_detail() {
244        use orion_conf::ConfIOReason;
245        let err = ConfIOReason::Other("expected `]` at line 2".into()).to_err();
246        let run = err.into_run_err();
247        assert!(
248            run.detail()
249                .as_deref()
250                .is_some_and(|d| d.contains("expected `]`")),
251            "ConfIOError detail should carry the toml error, got {:?}",
252            run.detail()
253        );
254    }
255}