Skip to main content

oino_settings/
error.rs

1//! Settings scopes and errors.
2
3use std::fmt;
4use std::path::PathBuf;
5
6use serde::{Deserialize, Serialize};
7
8/// The settings layer a read or write targets.
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[serde(rename_all = "lowercase")]
11pub enum SettingsScope {
12    /// `<agent>/settings.json`.
13    Global,
14    /// `<cwd>/.oino/settings.json` (only when the project is trusted).
15    Project,
16}
17
18impl fmt::Display for SettingsScope {
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        f.write_str(match self {
21            Self::Global => "global",
22            Self::Project => "project",
23        })
24    }
25}
26
27/// What went wrong with a settings file.
28#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
29#[non_exhaustive]
30pub enum SettingsErrorKind {
31    /// The file could not be read.
32    #[error("{0}")]
33    Read(String),
34    /// The file is not valid JSON.
35    #[error("{0}")]
36    Parse(String),
37    /// The file is valid JSON but not an object.
38    #[error("settings must be a JSON object")]
39    NotAnObject,
40    /// The lock file could not be created or locked.
41    #[error("failed to lock settings: {0}")]
42    Lock(String),
43    /// The file could not be written.
44    #[error("{0}")]
45    Write(String),
46    /// A project write was attempted while the project is untrusted.
47    #[error("Project is not trusted; refusing to write project settings")]
48    ProjectUntrusted,
49}
50
51/// A settings diagnostic; collected by the manager and
52/// returned by [`crate::SettingsManager::drain_errors`], or returned directly by writes.
53#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
54pub struct SettingsError {
55    /// Layer the error belongs to.
56    pub scope: SettingsScope,
57    /// File path when file-backed.
58    pub path: Option<PathBuf>,
59    /// Cause.
60    pub kind: SettingsErrorKind,
61}
62
63impl SettingsError {
64    /// The human-readable message (the error's own message, without the path prefix).
65    #[must_use]
66    pub fn message(&self) -> String {
67        self.kind.to_string()
68    }
69}
70
71impl fmt::Display for SettingsError {
72    /// The diagnostics wording.
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        match &self.path {
75            Some(path) => write!(f, "Invalid settings file {}: {}", path.display(), self.kind),
76            None => write!(f, "Invalid {} settings: {}", self.scope, self.kind),
77        }
78    }
79}
80
81/// A setting value that a validating getter rejects.
82#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
83#[error("{message}")]
84pub struct InvalidSettingError {
85    /// The exact message, e.g. `Invalid httpIdleTimeoutMs setting: abc`.
86    pub message: String,
87}
88
89impl InvalidSettingError {
90    pub(crate) fn new(message: impl Into<String>) -> Self {
91        Self {
92            message: message.into(),
93        }
94    }
95}