opencode_cloud_core/config/
schema.rs

1//! Configuration schema for opencode-cloud
2//!
3//! Defines the structure and defaults for the config.json file.
4
5use serde::{Deserialize, Serialize};
6
7/// Main configuration structure for opencode-cloud
8///
9/// Serialized to/from `~/.config/opencode-cloud/config.json`
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
11#[serde(deny_unknown_fields)]
12pub struct Config {
13    /// Config file version for migrations
14    pub version: u32,
15
16    /// Port for the opencode web UI (default: 8080)
17    #[serde(default = "default_port")]
18    pub port: u16,
19
20    /// Bind address (default: "localhost")
21    /// Use "localhost" for local-only access (secure default)
22    /// Use "0.0.0.0" for network access (requires explicit opt-in)
23    #[serde(default = "default_bind")]
24    pub bind: String,
25
26    /// Auto-restart service on crash (default: true)
27    #[serde(default = "default_auto_restart")]
28    pub auto_restart: bool,
29}
30
31fn default_port() -> u16 {
32    8080
33}
34
35fn default_bind() -> String {
36    "localhost".to_string()
37}
38
39fn default_auto_restart() -> bool {
40    true
41}
42
43impl Default for Config {
44    fn default() -> Self {
45        Self {
46            version: 1,
47            port: default_port(),
48            bind: default_bind(),
49            auto_restart: default_auto_restart(),
50        }
51    }
52}
53
54impl Config {
55    /// Create a new Config with default values
56    pub fn new() -> Self {
57        Self::default()
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn test_default_config() {
67        let config = Config::default();
68        assert_eq!(config.version, 1);
69        assert_eq!(config.port, 8080);
70        assert_eq!(config.bind, "localhost");
71        assert!(config.auto_restart);
72    }
73
74    #[test]
75    fn test_serialize_deserialize_roundtrip() {
76        let config = Config::default();
77        let json = serde_json::to_string(&config).unwrap();
78        let parsed: Config = serde_json::from_str(&json).unwrap();
79        assert_eq!(config, parsed);
80    }
81
82    #[test]
83    fn test_deserialize_with_missing_optional_fields() {
84        let json = r#"{"version": 1}"#;
85        let config: Config = serde_json::from_str(json).unwrap();
86        assert_eq!(config.version, 1);
87        assert_eq!(config.port, 8080);
88        assert_eq!(config.bind, "localhost");
89        assert!(config.auto_restart);
90    }
91
92    #[test]
93    fn test_reject_unknown_fields() {
94        let json = r#"{"version": 1, "unknown_field": "value"}"#;
95        let result: Result<Config, _> = serde_json::from_str(json);
96        assert!(result.is_err());
97    }
98}