Skip to main content

elizaos_plugin_github/
config.rs

1#![allow(missing_docs)]
2
3use serde::{Deserialize, Serialize};
4
5use crate::error::{GitHubError, Result};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct GitHubConfig {
9    pub api_token: String,
10    pub owner: Option<String>,
11    pub repo: Option<String>,
12    #[serde(default = "default_branch")]
13    pub branch: String,
14    pub webhook_secret: Option<String>,
15    pub app_id: Option<String>,
16    pub app_private_key: Option<String>,
17    pub installation_id: Option<String>,
18}
19
20fn default_branch() -> String {
21    "main".to_string()
22}
23
24impl GitHubConfig {
25    pub fn new(api_token: String) -> Self {
26        Self {
27            api_token,
28            owner: None,
29            repo: None,
30            branch: default_branch(),
31            webhook_secret: None,
32            app_id: None,
33            app_private_key: None,
34            installation_id: None,
35        }
36    }
37
38    pub fn from_env() -> Result<Self> {
39        dotenvy::dotenv().ok();
40
41        let api_token = std::env::var("GITHUB_API_TOKEN")
42            .map_err(|_| GitHubError::MissingSetting("GITHUB_API_TOKEN".to_string()))?;
43
44        if api_token.is_empty() {
45            return Err(GitHubError::ConfigError(
46                "GITHUB_API_TOKEN cannot be empty".to_string(),
47            ));
48        }
49
50        Ok(Self {
51            api_token,
52            owner: std::env::var("GITHUB_OWNER").ok(),
53            repo: std::env::var("GITHUB_REPO").ok(),
54            branch: std::env::var("GITHUB_BRANCH").unwrap_or_else(|_| default_branch()),
55            webhook_secret: std::env::var("GITHUB_WEBHOOK_SECRET").ok(),
56            app_id: std::env::var("GITHUB_APP_ID").ok(),
57            app_private_key: std::env::var("GITHUB_APP_PRIVATE_KEY").ok(),
58            installation_id: std::env::var("GITHUB_INSTALLATION_ID").ok(),
59        })
60    }
61
62    pub fn with_owner(mut self, owner: String) -> Self {
63        self.owner = Some(owner);
64        self
65    }
66
67    pub fn with_repo(mut self, repo: String) -> Self {
68        self.repo = Some(repo);
69        self
70    }
71
72    pub fn with_branch(mut self, branch: String) -> Self {
73        self.branch = branch;
74        self
75    }
76
77    pub fn with_webhook_secret(mut self, secret: String) -> Self {
78        self.webhook_secret = Some(secret);
79        self
80    }
81
82    pub fn get_repository_ref(
83        &self,
84        owner: Option<&str>,
85        repo: Option<&str>,
86    ) -> Result<(String, String)> {
87        let resolved_owner = owner
88            .map(|s| s.to_string())
89            .or_else(|| self.owner.clone())
90            .ok_or_else(|| GitHubError::MissingSetting("owner (GITHUB_OWNER)".to_string()))?;
91
92        let resolved_repo = repo
93            .map(|s| s.to_string())
94            .or_else(|| self.repo.clone())
95            .ok_or_else(|| GitHubError::MissingSetting("repo (GITHUB_REPO)".to_string()))?;
96
97        Ok((resolved_owner, resolved_repo))
98    }
99
100    pub fn has_app_auth(&self) -> bool {
101        self.app_id.is_some() && self.app_private_key.is_some()
102    }
103
104    pub fn validate(&self) -> Result<()> {
105        if self.api_token.is_empty() {
106            return Err(GitHubError::ConfigError(
107                "API token cannot be empty".to_string(),
108            ));
109        }
110
111        if self.has_app_auth() && self.installation_id.is_none() {
112            return Err(GitHubError::ConfigError(
113                "GITHUB_INSTALLATION_ID is required when using GitHub App authentication"
114                    .to_string(),
115            ));
116        }
117
118        Ok(())
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn test_config_new() {
128        let config = GitHubConfig::new("test_token".to_string());
129        assert_eq!(config.api_token, "test_token");
130        assert_eq!(config.branch, "main");
131        assert!(config.owner.is_none());
132    }
133
134    #[test]
135    fn test_config_builder_pattern() {
136        let config = GitHubConfig::new("test_token".to_string())
137            .with_owner("my-org".to_string())
138            .with_repo("my-repo".to_string())
139            .with_branch("develop".to_string());
140
141        assert_eq!(config.owner, Some("my-org".to_string()));
142        assert_eq!(config.repo, Some("my-repo".to_string()));
143        assert_eq!(config.branch, "develop");
144    }
145
146    #[test]
147    fn test_get_repository_ref() {
148        let config = GitHubConfig::new("token".to_string())
149            .with_owner("default-owner".to_string())
150            .with_repo("default-repo".to_string());
151
152        // Use defaults
153        let (owner, repo) = config.get_repository_ref(None, None).unwrap();
154        assert_eq!(owner, "default-owner");
155        assert_eq!(repo, "default-repo");
156
157        // Override
158        let (owner, repo) = config
159            .get_repository_ref(Some("other-owner"), Some("other-repo"))
160            .unwrap();
161        assert_eq!(owner, "other-owner");
162        assert_eq!(repo, "other-repo");
163    }
164
165    #[test]
166    fn test_validate_empty_token() {
167        let config = GitHubConfig::new("".to_string());
168        assert!(config.validate().is_err());
169    }
170
171    #[test]
172    fn test_validate_app_auth_without_installation() {
173        let mut config = GitHubConfig::new("token".to_string());
174        config.app_id = Some("123".to_string());
175        config.app_private_key = Some("key".to_string());
176        assert!(config.validate().is_err());
177    }
178}