elizaos_plugin_github/
config.rs1#![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 let (owner, repo) = config.get_repository_ref(None, None).unwrap();
154 assert_eq!(owner, "default-owner");
155 assert_eq!(repo, "default-repo");
156
157 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}