agentlink_domain/
config.rs1use serde::{Deserialize, Serialize};
4
5pub const CONFIG_VERSION: u32 = 1;
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(deny_unknown_fields)]
10pub struct Config {
11 pub version: u32,
12
13 #[serde(default, skip_serializing_if = "Option::is_none")]
16 pub providers: Option<Vec<String>>,
17
18 #[serde(default)]
19 pub gitignore: Gitignore,
20}
21
22impl Default for Config {
23 fn default() -> Self {
24 Self {
25 version: CONFIG_VERSION,
26 providers: None,
27 gitignore: Gitignore::default(),
28 }
29 }
30}
31
32#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
40#[serde(default, deny_unknown_fields)]
41pub struct Gitignore {
42 pub manage: bool,
45}
46
47impl Default for Gitignore {
48 fn default() -> Self {
49 Self { manage: true }
50 }
51}
52
53#[derive(Debug, thiserror::Error)]
54pub enum ConfigError {
55 #[error("could not parse .agentlink/config.toml: {0}")]
56 Syntax(String),
57 #[error(
58 "unsupported config version {0} (this build understands {CONFIG_VERSION}); \
59 upgrade agentlink"
60 )]
61 Version(u32),
62}
63
64impl Config {
65 pub fn parse(text: &str) -> Result<Self, ConfigError> {
66 let config: Config =
67 toml::from_str(text).map_err(|err| ConfigError::Syntax(err.to_string()))?;
68 if config.version != CONFIG_VERSION {
69 return Err(ConfigError::Version(config.version));
70 }
71 Ok(config)
72 }
73
74 pub fn render(&self) -> String {
75 let body = toml::to_string_pretty(self).expect("config is always serialisable");
76 format!(
77 "# agentlink configuration — https://github.com/fialhosoft/agentlink\n\
78 #\n\
79 # `providers` lists the agents to serve. Remove the key entirely to\n\
80 # serve every agent agentlink knows about, including ones added by\n\
81 # future releases.\n\n{body}"
82 )
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn round_trips_through_toml() {
92 let config = Config {
93 version: CONFIG_VERSION,
94 providers: Some(vec!["claude-code".into(), "cursor".into()]),
95 gitignore: Gitignore { manage: false },
96 };
97 assert_eq!(Config::parse(&config.render()).unwrap(), config);
98 }
99
100 #[test]
101 fn defaults_round_trip() {
102 let config = Config::default();
103 assert_eq!(Config::parse(&config.render()).unwrap(), config);
104 }
105
106 #[test]
107 fn an_omitted_provider_list_means_every_provider() {
108 let config = Config::parse("version = 1").unwrap();
109 assert_eq!(config.providers, None);
110 assert!(config.gitignore.manage);
111 }
112
113 #[test]
114 fn rejects_unknown_keys_rather_than_ignoring_a_typo() {
115 let err = Config::parse("version = 1\nproviderz = []").unwrap_err();
116 assert!(matches!(err, ConfigError::Syntax(_)));
117 }
118
119 #[test]
120 fn rejects_a_config_written_by_a_newer_agentlink() {
121 assert!(matches!(
122 Config::parse("version = 42").unwrap_err(),
123 ConfigError::Version(42)
124 ));
125 }
126}