Skip to main content

rill_core/
error.rs

1//! # Система ошибок Rill Core
2//!
3//! Централизованная система обработки ошибок для всей экосистемы Rill.
4//! Предоставляет иерархию типов ошибок с контекстом и возможностью
5//! преобразования между различными уровнями.
6
7use std::error::Error as StdError;
8use std::fmt;
9
10// =============================================================================
11// Основные типы ошибок
12// =============================================================================
13
14/// Основной тип ошибки для всей экосистемы Rill
15#[derive(Debug, Clone)]
16pub struct Error {
17    /// Категория ошибки
18    pub category: ErrorCategory,
19    /// Код ошибки (для машинной обработки)
20    pub code: ErrorCode,
21    /// Человекочитаемое сообщение
22    pub message: String,
23    /// Причина (опционально)
24    pub cause: Option<Box<Error>>,
25    /// Место возникновения (файл, строка)
26    pub location: Option<ErrorLocation>,
27}
28
29/// Категория ошибки
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum ErrorCategory {
32    /// Ошибки ядра (буферы, очереди, базовые типы)
33    Core,
34    /// Ошибки DSP (фильтры, эффекты, генераторы)
35    Dsp,
36    /// Ошибки графа (соединения, топология)
37    Graph,
38    /// Ошибки ввода-вывода (ALSA, JACK, PipeWire)
39    Io,
40    /// Ошибки управления (MIDI, OSC, автоматизация)
41    Control,
42    /// Ошибки конфигурации
43    Config,
44    /// Ошибки времени выполнения
45    Runtime,
46    /// Внутренние ошибки (не должны возникать)
47    Internal,
48}
49
50impl ErrorCategory {
51    /// Получить строковое представление
52    pub fn as_str(&self) -> &'static str {
53        match self {
54            ErrorCategory::Core => "core",
55            ErrorCategory::Dsp => "dsp",
56            ErrorCategory::Graph => "graph",
57            ErrorCategory::Io => "io",
58            ErrorCategory::Control => "control",
59            ErrorCategory::Config => "config",
60            ErrorCategory::Runtime => "runtime",
61            ErrorCategory::Internal => "internal",
62        }
63    }
64}
65
66impl fmt::Display for ErrorCategory {
67    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        write!(f, "{}", self.as_str())
69    }
70}
71
72/// Код ошибки (для машинной обработки)
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub enum ErrorCode {
75    // =======================================================================
76    // Core errors (0-99)
77    // =======================================================================
78    /// Неизвестная ошибка
79    Unknown = 0,
80    /// Неверный параметр
81    InvalidParameter = 1,
82    /// Неверное состояние
83    InvalidState = 2,
84    /// Неподдерживаемая операция
85    Unsupported = 3,
86    /// Не реализовано
87    NotImplemented = 4,
88    /// Таймаут
89    Timeout = 5,
90
91    // =======================================================================
92    // Buffer errors (100-119)
93    // =======================================================================
94    /// Переполнение буфера
95    BufferFull = 100,
96    /// Буфер пуст
97    BufferEmpty = 101,
98    /// Неверный размер буфера
99    InvalidBufferSize = 102,
100    /// Неверное выравнивание буфера
101    BufferMisaligned = 103,
102    /// Буфер не инициализирован
103    BufferNotInitialized = 104,
104
105    // =======================================================================
106    // Queue errors (120-139)
107    // =======================================================================
108    /// Очередь переполнена
109    QueueFull = 120,
110    /// Очередь пуста
111    QueueEmpty = 121,
112    /// Очередь закрыта
113    QueueClosed = 122,
114    /// Неверный индекс очереди
115    InvalidQueueIndex = 123,
116
117    // =======================================================================
118    // Graph errors (200-299)
119    // =======================================================================
120    /// Узел не найден
121    NodeNotFound = 200,
122    /// Порт не найден
123    PortNotFound = 201,
124    /// Неверное соединение
125    InvalidConnection = 202,
126    /// Цикл в графе
127    CycleDetected = 203,
128    /// Узел уже существует
129    NodeAlreadyExists = 204,
130    /// Порт уже подключён
131    PortAlreadyConnected = 205,
132
133    // =======================================================================
134    // IO errors (300-399)
135    // =======================================================================
136    /// Устройство не найдено
137    DeviceNotFound = 300,
138    /// Устройство занято
139    DeviceBusy = 301,
140    /// Ошибка ALSA
141    AlsaError = 310,
142    /// Ошибка JACK
143    JackError = 311,
144    /// Ошибка PipeWire
145    PipeWireError = 312,
146    /// XRun (переполнение/опустошение буфера)
147    XRun = 320,
148
149    // =======================================================================
150    // Control errors (400-499)
151    // =======================================================================
152    /// MIDI ошибка
153    MidiError = 400,
154    /// OSC ошибка
155    OscError = 401,
156    /// Маппинг не найден
157    MappingNotFound = 402,
158    /// Автомат не найден
159    AutomatonNotFound = 403,
160    /// Неверное значение параметра
161    InvalidParameterValue = 404,
162
163    // =======================================================================
164    // Config errors (500-599)
165    // =======================================================================
166    /// Конфигурация не найдена
167    ConfigNotFound = 500,
168    /// Неверный формат конфигурации
169    InvalidConfigFormat = 501,
170    /// Отсутствует обязательное поле
171    MissingField = 502,
172
173    // =======================================================================
174    // Runtime errors (600-699)
175    // =======================================================================
176    /// Ошибка в real-time потоке
177    RealtimeViolation = 600,
178    /// Приоритет потока не может быть установлен
179    PriorityError = 601,
180    /// Поток уже запущен
181    AlreadyRunning = 602,
182    /// Поток не запущен
183    NotRunning = 603,
184}
185
186impl ErrorCode {
187    /// Получить категорию ошибки
188    pub fn category(&self) -> ErrorCategory {
189        match *self {
190            ErrorCode::Unknown
191            | ErrorCode::InvalidParameter
192            | ErrorCode::InvalidState
193            | ErrorCode::Unsupported
194            | ErrorCode::NotImplemented
195            | ErrorCode::Timeout
196            | ErrorCode::BufferFull
197            | ErrorCode::BufferEmpty
198            | ErrorCode::InvalidBufferSize
199            | ErrorCode::BufferMisaligned
200            | ErrorCode::BufferNotInitialized
201            | ErrorCode::QueueFull
202            | ErrorCode::QueueEmpty
203            | ErrorCode::QueueClosed
204            | ErrorCode::InvalidQueueIndex => ErrorCategory::Core,
205
206            ErrorCode::NodeNotFound
207            | ErrorCode::PortNotFound
208            | ErrorCode::InvalidConnection
209            | ErrorCode::CycleDetected
210            | ErrorCode::NodeAlreadyExists
211            | ErrorCode::PortAlreadyConnected => ErrorCategory::Graph,
212
213            ErrorCode::DeviceNotFound
214            | ErrorCode::DeviceBusy
215            | ErrorCode::AlsaError
216            | ErrorCode::JackError
217            | ErrorCode::PipeWireError
218            | ErrorCode::XRun => ErrorCategory::Io,
219
220            ErrorCode::MidiError
221            | ErrorCode::OscError
222            | ErrorCode::MappingNotFound
223            | ErrorCode::AutomatonNotFound
224            | ErrorCode::InvalidParameterValue => ErrorCategory::Control,
225
226            ErrorCode::ConfigNotFound
227            | ErrorCode::InvalidConfigFormat
228            | ErrorCode::MissingField => ErrorCategory::Config,
229
230            ErrorCode::RealtimeViolation
231            | ErrorCode::PriorityError
232            | ErrorCode::AlreadyRunning
233            | ErrorCode::NotRunning => ErrorCategory::Runtime,
234        }
235    }
236
237    /// Получить описание ошибки
238    pub fn description(&self) -> &'static str {
239        match self {
240            ErrorCode::Unknown => "Unknown error",
241            ErrorCode::InvalidParameter => "Invalid parameter",
242            ErrorCode::InvalidState => "Invalid state",
243            ErrorCode::Unsupported => "Unsupported operation",
244            ErrorCode::NotImplemented => "Not implemented",
245            ErrorCode::Timeout => "Operation timed out",
246
247            ErrorCode::BufferFull => "Buffer is full",
248            ErrorCode::BufferEmpty => "Buffer is empty",
249            ErrorCode::InvalidBufferSize => "Invalid buffer size",
250            ErrorCode::BufferMisaligned => "Buffer is misaligned for SIMD operations",
251            ErrorCode::BufferNotInitialized => "Buffer not initialized",
252
253            ErrorCode::QueueFull => "Queue is full",
254            ErrorCode::QueueEmpty => "Queue is empty",
255            ErrorCode::QueueClosed => "Queue is closed",
256            ErrorCode::InvalidQueueIndex => "Invalid queue index",
257
258            ErrorCode::NodeNotFound => "Node not found",
259            ErrorCode::PortNotFound => "Port not found",
260            ErrorCode::InvalidConnection => "Invalid connection",
261            ErrorCode::CycleDetected => "Cycle detected in graph",
262            ErrorCode::NodeAlreadyExists => "Node already exists",
263            ErrorCode::PortAlreadyConnected => "Port already connected",
264
265            ErrorCode::DeviceNotFound => "Device not found",
266            ErrorCode::DeviceBusy => "Device is busy",
267            ErrorCode::AlsaError => "ALSA error",
268            ErrorCode::JackError => "JACK error",
269            ErrorCode::PipeWireError => "PipeWire error",
270            ErrorCode::XRun => "Buffer underrun/overrun detected",
271
272            ErrorCode::MidiError => "MIDI error",
273            ErrorCode::OscError => "OSC error",
274            ErrorCode::MappingNotFound => "Mapping not found",
275            ErrorCode::AutomatonNotFound => "Automaton not found",
276            ErrorCode::InvalidParameterValue => "Invalid parameter value",
277
278            ErrorCode::ConfigNotFound => "Configuration not found",
279            ErrorCode::InvalidConfigFormat => "Invalid configuration format",
280            ErrorCode::MissingField => "Missing required field",
281
282            ErrorCode::RealtimeViolation => "Real-time violation detected",
283            ErrorCode::PriorityError => "Failed to set thread priority",
284            ErrorCode::AlreadyRunning => "Already running",
285            ErrorCode::NotRunning => "Not running",
286        }
287    }
288}
289
290/// Место возникновения ошибки
291#[derive(Debug, Clone)]
292pub struct ErrorLocation {
293    /// Файл
294    pub file: &'static str,
295    /// Строка
296    pub line: u32,
297    /// Колонка
298    pub column: u32,
299}
300
301impl fmt::Display for ErrorLocation {
302    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
303        write!(f, "{}:{}:{}", self.file, self.line, self.column)
304    }
305}
306
307// =============================================================================
308// Реализация Error
309// =============================================================================
310
311impl Error {
312    /// Создать новую ошибку
313    pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
314        Self {
315            category: code.category(),
316            code,
317            message: message.into(),
318            cause: None,
319            location: None,
320        }
321    }
322
323    /// Создать ошибку с причиной
324    pub fn with_cause(mut self, cause: Error) -> Self {
325        self.cause = Some(Box::new(cause));
326        self
327    }
328
329    /// Добавить информацию о месте возникновения
330    pub fn at(mut self, file: &'static str, line: u32, column: u32) -> Self {
331        self.location = Some(ErrorLocation { file, line, column });
332        self
333    }
334
335    /// Получить корневую причину
336    pub fn root_cause(&self) -> &Error {
337        let mut current = self;
338        while let Some(cause) = &current.cause {
339            current = cause;
340        }
341        current
342    }
343
344    /// Проверить, является ли ошибка фатальной для RT-потока
345    pub fn is_realtime_critical(&self) -> bool {
346        matches!(
347            self.code,
348            ErrorCode::RealtimeViolation
349                | ErrorCode::PriorityError
350                | ErrorCode::BufferFull
351                | ErrorCode::XRun
352        )
353    }
354
355    /// Проверить, является ли ошибка recoverable
356    pub fn is_recoverable(&self) -> bool {
357        !self.is_realtime_critical()
358    }
359}
360
361impl fmt::Display for Error {
362    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
363        if let Some(loc) = &self.location {
364            write!(
365                f,
366                "[{}] at {}: {} ({})",
367                self.category,
368                loc,
369                self.message,
370                self.code.description()
371            )?;
372        } else {
373            write!(
374                f,
375                "[{}]: {} ({})",
376                self.category,
377                self.message,
378                self.code.description()
379            )?;
380        }
381
382        if let Some(cause) = &self.cause {
383            write!(f, "\n  caused by: {}", cause)?;
384        }
385
386        Ok(())
387    }
388}
389
390impl StdError for Error {
391    fn source(&self) -> Option<&(dyn StdError + 'static)> {
392        self.cause.as_ref().map(|c| c as &dyn StdError)
393    }
394}
395
396// =============================================================================
397// Результат операций
398// =============================================================================
399
400/// Результат операций в Rill Core
401pub type Result<T> = std::result::Result<T, Error>;
402
403// =============================================================================
404// Конвертация из стандартных ошибок
405// =============================================================================
406
407impl From<std::io::Error> for Error {
408    fn from(err: std::io::Error) -> Self {
409        Error::new(ErrorCode::Unknown, err.to_string())
410    }
411}
412
413impl From<std::num::ParseIntError> for Error {
414    fn from(err: std::num::ParseIntError) -> Self {
415        Error::new(ErrorCode::InvalidParameter, err.to_string())
416    }
417}
418
419impl From<std::num::ParseFloatError> for Error {
420    fn from(err: std::num::ParseFloatError) -> Self {
421        Error::new(ErrorCode::InvalidParameter, err.to_string())
422    }
423}
424
425impl From<std::str::Utf8Error> for Error {
426    fn from(err: std::str::Utf8Error) -> Self {
427        Error::new(ErrorCode::InvalidParameter, err.to_string())
428    }
429}
430
431// =============================================================================
432// Макросы для удобного создания ошибок
433// =============================================================================
434
435/// Создать ошибку с кодом и сообщением
436#[macro_export]
437macro_rules! error {
438    ($code:expr, $msg:expr) => {
439        $crate::error::Error::new($code, $msg)
440    };
441    ($code:expr, $fmt:expr, $($arg:tt)*) => {
442        $crate::error::Error::new($code, format!($fmt, $($arg)*))
443    };
444}
445
446/// Создать ошибку с местом возникновения
447#[macro_export]
448macro_rules! error_at {
449    ($code:expr, $msg:expr) => {
450        $crate::error::Error::new($code, $msg).at(file!(), line!(), column!())
451    };
452    ($code:expr, $fmt:expr, $($arg:tt)*) => {
453        $crate::error::Error::new($code, format!($fmt, $($arg)*))
454            .at(file!(), line!(), column!())
455    };
456}
457
458/// Возврат ошибки с контекстом
459#[macro_export]
460macro_rules! bail {
461    ($code:expr, $msg:expr) => {
462        return Err($crate::error::Error::new($code, $msg))
463    };
464    ($code:expr, $fmt:expr, $($arg:tt)*) => {
465        return Err($crate::error::Error::new($code, format!($fmt, $($arg)*)))
466    };
467}
468
469/// Преобразование Result с добавлением контекста
470#[macro_export]
471macro_rules! context {
472    ($expr:expr, $code:expr, $msg:expr) => {
473        $expr.map_err(|e| $crate::error::Error::new($code, $msg).with_cause(e))
474    };
475    ($expr:expr, $code:expr, $fmt:expr, $($arg:tt)*) => {
476        $expr.map_err(|e| $crate::error::Error::new($code, format!($fmt, $($arg)*)).with_cause(e))
477    };
478}
479
480// =============================================================================
481// Специализированные типы ошибок для разных компонентов
482// =============================================================================
483
484/// Ошибки буферов
485pub mod buffer {
486    use super::*;
487    #[allow(dead_code)]
488    pub fn full() -> Error {
489        Error::new(ErrorCode::BufferFull, "Buffer is full")
490    }
491    #[allow(dead_code)]
492    pub fn empty() -> Error {
493        Error::new(ErrorCode::BufferEmpty, "Buffer is empty")
494    }
495    #[allow(dead_code)]
496    pub fn invalid_size(expected: usize, got: usize) -> Error {
497        error!(
498            ErrorCode::InvalidBufferSize,
499            "Invalid buffer size: expected {}, got {}", expected, got
500        )
501    }
502    #[allow(dead_code)]
503    pub fn misaligned(required: usize, actual: usize) -> Error {
504        error!(
505            ErrorCode::BufferMisaligned,
506            "Buffer misaligned: required {} byte alignment, actual {}", required, actual
507        )
508    }
509    #[allow(dead_code)]
510    pub fn not_initialized() -> Error {
511        Error::new(ErrorCode::BufferNotInitialized, "Buffer not initialized")
512    }
513}
514
515/// Ошибки очередей
516pub mod queue {
517    use super::*;
518
519    pub fn full() -> Error {
520        Error::new(ErrorCode::QueueFull, "Queue is full")
521    }
522
523    pub fn empty() -> Error {
524        Error::new(ErrorCode::QueueEmpty, "Queue is empty")
525    }
526
527    pub fn closed() -> Error {
528        Error::new(ErrorCode::QueueClosed, "Queue is closed")
529    }
530
531    pub fn invalid_index(idx: usize, max: usize) -> Error {
532        error!(
533            ErrorCode::InvalidQueueIndex,
534            "Invalid queue index: {} (max {})", idx, max
535        )
536    }
537}
538
539/// Ошибки графа
540pub mod graph {
541    use super::*;
542    use crate::traits::NodeId;
543    use crate::traits::PortId;
544
545    pub fn node_not_found(id: NodeId) -> Error {
546        error!(ErrorCode::NodeNotFound, "Node not found: {}", id)
547    }
548
549    pub fn port_not_found(id: PortId) -> Error {
550        error!(ErrorCode::PortNotFound, "Port not found: {}", id)
551    }
552
553    pub fn invalid_connection(from: PortId, to: PortId) -> Error {
554        error!(
555            ErrorCode::InvalidConnection,
556            "Invalid connection: {} -> {}", from, to
557        )
558    }
559
560    pub fn cycle_detected() -> Error {
561        Error::new(ErrorCode::CycleDetected, "Cycle detected in graph")
562    }
563
564    pub fn node_already_exists(id: NodeId) -> Error {
565        error!(ErrorCode::NodeAlreadyExists, "Node already exists: {}", id)
566    }
567
568    pub fn port_already_connected(port: PortId) -> Error {
569        error!(
570            ErrorCode::PortAlreadyConnected,
571            "Port already connected: {}", port
572        )
573    }
574}
575
576/// Ошибки ввода-вывода
577pub mod io {
578    use super::*;
579
580    pub fn device_not_found(name: &str) -> Error {
581        error!(ErrorCode::DeviceNotFound, "Device not found: {}", name)
582    }
583
584    pub fn device_busy(name: &str) -> Error {
585        error!(ErrorCode::DeviceBusy, "Device is busy: {}", name)
586    }
587
588    pub fn alsa_error(desc: &str) -> Error {
589        error!(ErrorCode::AlsaError, "ALSA error: {}", desc)
590    }
591
592    pub fn jack_error(desc: &str) -> Error {
593        error!(ErrorCode::JackError, "JACK error: {}", desc)
594    }
595
596    pub fn pipewire_error(desc: &str) -> Error {
597        error!(ErrorCode::PipeWireError, "PipeWire error: {}", desc)
598    }
599
600    pub fn xrun() -> Error {
601        Error::new(ErrorCode::XRun, "Buffer underrun/overrun detected")
602    }
603}
604
605/// Ошибки управления
606pub mod control {
607    use super::*;
608
609    pub fn midi_error(desc: &str) -> Error {
610        error!(ErrorCode::MidiError, "MIDI error: {}", desc)
611    }
612
613    pub fn osc_error(desc: &str) -> Error {
614        error!(ErrorCode::OscError, "OSC error: {}", desc)
615    }
616
617    pub fn mapping_not_found(id: &str) -> Error {
618        error!(ErrorCode::MappingNotFound, "Mapping not found: {}", id)
619    }
620
621    pub fn automaton_not_found(id: &str) -> Error {
622        error!(ErrorCode::AutomatonNotFound, "Automaton not found: {}", id)
623    }
624
625    pub fn invalid_parameter_value(param: &str, value: f64, min: f64, max: f64) -> Error {
626        error!(
627            ErrorCode::InvalidParameterValue,
628            "Invalid value for parameter {}: {} (allowed range: {} - {})", param, value, min, max
629        )
630    }
631}
632
633/// Ошибки конфигурации
634pub mod config {
635    use super::*;
636
637    pub fn not_found(path: &str) -> Error {
638        error!(
639            ErrorCode::ConfigNotFound,
640            "Configuration not found: {}", path
641        )
642    }
643
644    pub fn invalid_format(details: &str) -> Error {
645        error!(
646            ErrorCode::InvalidConfigFormat,
647            "Invalid configuration format: {}", details
648        )
649    }
650
651    pub fn missing_field(field: &str) -> Error {
652        error!(ErrorCode::MissingField, "Missing required field: {}", field)
653    }
654}
655
656/// Ошибки времени выполнения
657pub mod runtime {
658    use super::*;
659
660    pub fn realtime_violation(details: &str) -> Error {
661        error!(
662            ErrorCode::RealtimeViolation,
663            "Real-time violation: {}", details
664        )
665    }
666
667    pub fn priority_error(details: &str) -> Error {
668        error!(
669            ErrorCode::PriorityError,
670            "Failed to set thread priority: {}", details
671        )
672    }
673
674    pub fn already_running() -> Error {
675        Error::new(ErrorCode::AlreadyRunning, "Already running")
676    }
677
678    pub fn not_running() -> Error {
679        Error::new(ErrorCode::NotRunning, "Not running")
680    }
681}
682
683// =============================================================================
684// Тесты
685// =============================================================================
686
687#[cfg(test)]
688mod tests {
689    use super::*;
690    use crate::prelude::NodeId;
691
692    #[test]
693    fn test_error_creation() {
694        let err = Error::new(ErrorCode::BufferFull, "Test error");
695        assert_eq!(err.code, ErrorCode::BufferFull);
696        assert_eq!(err.message, "Test error");
697        assert_eq!(err.category, ErrorCategory::Core);
698    }
699
700    #[test]
701    fn test_error_with_cause() {
702        let cause = Error::new(ErrorCode::BufferEmpty, "Cause");
703        let err = Error::new(ErrorCode::BufferFull, "Main error").with_cause(cause);
704
705        assert!(err.cause.is_some());
706        assert_eq!(err.root_cause().code, ErrorCode::BufferEmpty);
707    }
708
709    #[test]
710    fn test_error_macros() {
711        let err = error!(ErrorCode::BufferFull, "Buffer is full");
712        assert_eq!(err.code, ErrorCode::BufferFull);
713
714        let err = error!(ErrorCode::BufferFull, "Buffer {} is full", "test");
715        assert_eq!(err.message, "Buffer test is full");
716    }
717
718    #[test]
719    fn test_specialized_errors() {
720        let err = buffer::full();
721        assert_eq!(err.code, ErrorCode::BufferFull);
722
723        let err = graph::node_not_found(NodeId(42));
724        assert_eq!(err.code, ErrorCode::NodeNotFound);
725        assert!(err.message.contains("42"));
726
727        let err = io::device_not_found("hw:0");
728        assert_eq!(err.code, ErrorCode::DeviceNotFound);
729        assert!(err.message.contains("hw:0"));
730    }
731
732    #[test]
733    fn test_error_category() {
734        assert_eq!(ErrorCode::BufferFull.category(), ErrorCategory::Core);
735        assert_eq!(ErrorCode::NodeNotFound.category(), ErrorCategory::Graph);
736        assert_eq!(ErrorCode::AlsaError.category(), ErrorCategory::Io);
737        assert_eq!(ErrorCode::MidiError.category(), ErrorCategory::Control);
738        assert_eq!(ErrorCode::ConfigNotFound.category(), ErrorCategory::Config);
739        assert_eq!(
740            ErrorCode::RealtimeViolation.category(),
741            ErrorCategory::Runtime
742        );
743    }
744
745    #[test]
746    fn test_realtime_critical() {
747        assert!(buffer::full().is_realtime_critical());
748        assert!(io::xrun().is_realtime_critical());
749        assert!(runtime::realtime_violation("test").is_realtime_critical());
750
751        assert!(!graph::node_not_found(NodeId(1)).is_realtime_critical());
752        assert!(!config::not_found("test").is_realtime_critical());
753    }
754}