Skip to main content

reovim_kernel/core/option/
spec.rs

1//! Option specification.
2
3use std::borrow::Cow;
4
5use {
6    super::{
7        constraint::OptionConstraint, error::OptionError, scope::OptionScope, value::OptionValue,
8    },
9    crate::api::ModuleId,
10};
11
12/// Complete specification for an editor option.
13///
14/// This is the **MECHANISM** type - it defines the structure of options.
15/// **POLICY** (which options exist and what they do) is in modules.
16///
17/// # Example
18///
19/// ```ignore
20/// use reovim_kernel::api::v1::*;
21///
22/// let spec = OptionSpec::new("tabwidth", "Tab width in spaces", OptionValue::int(4))
23///     .with_short("tw")
24///     .with_constraint(OptionConstraint::range(1, 32))
25///     .with_scope(OptionScope::Buffer);
26/// ```
27#[derive(Debug, Clone)]
28pub struct OptionSpec {
29    /// Full option name (e.g., "number", "tabwidth").
30    pub name: Cow<'static, str>,
31    /// Short alias (e.g., "nu" for "number").
32    pub short_form: Option<Cow<'static, str>>,
33    /// Human-readable description.
34    pub description: Cow<'static, str>,
35    /// Default value (also defines the type).
36    pub default: OptionValue,
37    /// Constraints for validation.
38    pub constraint: OptionConstraint,
39    /// Scope (global, buffer-local, window-local).
40    pub scope: OptionScope,
41    /// Module that registered this option (for lifecycle cleanup).
42    pub owner: Option<ModuleId>,
43}
44
45impl OptionSpec {
46    /// Create a new option specification.
47    #[must_use]
48    pub fn new(
49        name: impl Into<Cow<'static, str>>,
50        description: impl Into<Cow<'static, str>>,
51        default: OptionValue,
52    ) -> Self {
53        Self {
54            name: name.into(),
55            description: description.into(),
56            default,
57            short_form: None,
58            constraint: OptionConstraint::none(),
59            scope: OptionScope::default(),
60            owner: None,
61        }
62    }
63
64    /// Add a short alias.
65    #[must_use]
66    pub fn with_short(mut self, short: impl Into<Cow<'static, str>>) -> Self {
67        self.short_form = Some(short.into());
68        self
69    }
70
71    /// Add validation constraints.
72    #[must_use]
73    pub const fn with_constraint(mut self, constraint: OptionConstraint) -> Self {
74        self.constraint = constraint;
75        self
76    }
77
78    /// Set the scope.
79    #[must_use]
80    pub const fn with_scope(mut self, scope: OptionScope) -> Self {
81        self.scope = scope;
82        self
83    }
84
85    /// Set the owning module.
86    #[must_use]
87    pub fn with_owner(mut self, owner: ModuleId) -> Self {
88        self.owner = Some(owner);
89        self
90    }
91
92    /// Get the owning module, if set.
93    #[must_use]
94    pub const fn owner(&self) -> Option<&ModuleId> {
95        self.owner.as_ref()
96    }
97
98    /// Check if this option matches a name (full name or alias).
99    #[must_use]
100    pub fn matches_name(&self, query: &str) -> bool {
101        self.name == query
102            || self
103                .short_form
104                .as_ref()
105                .is_some_and(|s: &Cow<'static, str>| s.as_ref() == query)
106    }
107
108    /// Validate a value against this option's type and constraints.
109    ///
110    /// # Errors
111    ///
112    /// Returns error if value type doesn't match or violates constraints.
113    pub fn validate(&self, value: &OptionValue) -> Result<(), OptionError> {
114        // Type check
115        if !self.default.same_type(value) {
116            return Err(OptionError::TypeMismatch {
117                name: self.name.to_string(),
118                expected: self.default.type_name(),
119                got: value.type_name(),
120            });
121        }
122
123        // Constraint check
124        self.constraint
125            .validate(value)
126            .map_err(|e| OptionError::ValidationFailed {
127                name: self.name.to_string(),
128                reason: e.to_string(),
129            })
130    }
131}