Skip to main content

urushi_prompt/runtime/
error.rs

1//! Errors exposed by prompt construction and execution.
2
3use std::{fmt, io};
4
5/// The I/O boundary at which a prompt run failed.
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7#[non_exhaustive]
8pub enum IoOperation {
9    /// Entering raw-mode terminal handling failed.
10    EnterTerminal,
11    /// Reading the next input event failed.
12    ReadEvent,
13    /// Drawing or finishing the inline prompt failed.
14    Render,
15    /// Cleanup after an otherwise successful or cancelled run failed.
16    Cleanup,
17}
18
19impl fmt::Display for IoOperation {
20    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
21        formatter.write_str(match self {
22            Self::EnterTerminal => "enter terminal session",
23            Self::ReadEvent => "read terminal event",
24            Self::Render => "render prompt",
25            Self::Cleanup => "clean up terminal session",
26        })
27    }
28}
29
30/// An error that prevents a form from completing normally.
31#[derive(Debug)]
32#[non_exhaustive]
33pub enum RunError {
34    /// Standard input and standard error are not both interactive terminals.
35    NotInteractive,
36    /// The terminal was resized while an inline prompt configured with
37    /// [`crate::InlineResizePolicy::ReturnError`] was running.
38    Resized {
39        /// A terminal restoration failure, if cleanup was attempted and failed.
40        cleanup: Option<io::Error>,
41    },
42    /// A runtime operation failed; cleanup is retained separately when it also failed.
43    Io {
44        /// The primary operation that failed.
45        operation: IoOperation,
46        /// The primary I/O failure.
47        source: io::Error,
48        /// The first cleanup failure, if cleanup was attempted after `source`.
49        cleanup: Option<io::Error>,
50    },
51}
52
53impl fmt::Display for RunError {
54    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
55        match self {
56            Self::NotInteractive => formatter
57                .write_str("standard input and standard error must be interactive terminals"),
58            Self::Resized {
59                cleanup: Some(cleanup),
60            } => write!(
61                formatter,
62                "terminal resized during inline prompt; terminal cleanup also failed: {cleanup}"
63            ),
64            Self::Resized { cleanup: None } => {
65                formatter.write_str("terminal resized during inline prompt")
66            }
67            Self::Io {
68                operation,
69                source,
70                cleanup: Some(cleanup),
71            } => write!(
72                formatter,
73                "failed to {operation}: {source}; terminal cleanup also failed: {cleanup}"
74            ),
75            Self::Io {
76                operation,
77                source,
78                cleanup: None,
79            } => write!(formatter, "failed to {operation}: {source}"),
80        }
81    }
82}
83
84impl std::error::Error for RunError {
85    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
86        match self {
87            Self::NotInteractive => None,
88            Self::Resized { cleanup } => cleanup
89                .as_ref()
90                .map(|error| error as &(dyn std::error::Error + 'static)),
91            Self::Io { source, .. } => Some(source),
92        }
93    }
94}
95
96/// An invalid top-level form configuration.
97#[derive(Debug, Clone, PartialEq, Eq)]
98#[non_exhaustive]
99pub enum FormBuildError {
100    /// A form must contain at least one group.
101    EmptyForm,
102    /// Field names must be unique across all groups.
103    DuplicateFieldName(String),
104}
105
106impl fmt::Display for FormBuildError {
107    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
108        match self {
109            Self::EmptyForm => formatter.write_str("a form must contain at least one group"),
110            Self::DuplicateFieldName(name) => {
111                write!(formatter, "field name `{name}` is duplicated in the form")
112            }
113        }
114    }
115}
116
117impl std::error::Error for FormBuildError {}
118
119/// An invalid group configuration.
120#[derive(Debug, Clone, PartialEq, Eq)]
121#[non_exhaustive]
122pub enum GroupBuildError {
123    /// A group must contain at least one field.
124    EmptyGroup,
125}
126
127impl fmt::Display for GroupBuildError {
128    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
129        formatter.write_str("a prompt group must contain at least one field")
130    }
131}
132
133impl std::error::Error for GroupBuildError {}
134
135/// A field-specific configuration error reserved for concrete field controls.
136#[derive(Debug, Clone, PartialEq, Eq)]
137#[non_exhaustive]
138pub enum FieldConfigError {
139    /// A field name cannot be empty.
140    EmptyName,
141    /// A select-like field must contain an option.
142    EmptyOptions,
143}
144
145impl fmt::Display for FieldConfigError {
146    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
147        formatter.write_str(match self {
148            Self::EmptyName => "a prompt field name cannot be empty",
149            Self::EmptyOptions => "a select field must contain at least one option",
150        })
151    }
152}
153
154impl std::error::Error for FieldConfigError {}