Skip to main content

gor/
host.rs

1//! Multi-host support for `gor`.
2//!
3//! Derives REST API base URLs for github.com and GitHub Enterprise Server
4//! instances. All URL construction flows through [`Host`] to ensure consistent
5//! host handling across the codebase.
6
7/// Represents a GitHub host (github.com or a GHES instance).
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct Host {
10    /// The hostname, e.g. `github.com` or `git.example.com`.
11    hostname: String,
12    /// The REST API base URL for this host.
13    api_base: String,
14}
15
16impl Host {
17    /// Create a new `Host` for the given hostname.
18    ///
19    /// For `github.com`, the API base is `https://api.github.com`.
20    /// For GHES instances, the API base is `https://<hostname>/api/v3`.
21    ///
22    /// # Examples
23    ///
24    /// ```
25    /// use gor::host::Host;
26    ///
27    /// let host = Host::new("github.com");
28    /// assert_eq!(host.api_base(), "https://api.github.com");
29    ///
30    /// let ghes = Host::new("git.example.com");
31    /// assert_eq!(ghes.api_base(), "https://git.example.com/api/v3");
32    /// ```
33    #[must_use]
34    pub fn new(hostname: &str) -> Self {
35        let api_base = if hostname == "github.com" {
36            "https://api.github.com".to_string()
37        } else {
38            format!("https://{hostname}/api/v3")
39        };
40        Self {
41            hostname: hostname.to_string(),
42            api_base,
43        }
44    }
45
46    /// Returns the hostname (e.g. `github.com`).
47    #[must_use]
48    pub fn hostname(&self) -> &str {
49        &self.hostname
50    }
51
52    /// Returns the REST API base URL (e.g. `https://api.github.com`).
53    #[must_use]
54    pub fn api_base(&self) -> &str {
55        &self.api_base
56    }
57
58    /// Build a full API URL by joining the API base with the given path.
59    ///
60    /// The path should start with a `/`, e.g. `/user` or `/repos/owner/repo`.
61    ///
62    /// # Examples
63    ///
64    /// ```
65    /// use gor::host::Host;
66    ///
67    /// let host = Host::new("github.com");
68    /// assert_eq!(host.api_url("/user"), "https://api.github.com/user");
69    /// ```
70    #[must_use]
71    pub fn api_url(&self, path: &str) -> String {
72        format!("{}{path}", self.api_base)
73    }
74
75    /// Build the OAuth device code URL for this host.
76    ///
77    /// # Examples
78    ///
79    /// ```
80    /// use gor::host::Host;
81    ///
82    /// let host = Host::new("github.com");
83    /// assert_eq!(
84    ///     host.device_code_url(),
85    ///     "https://github.com/login/device/code"
86    /// );
87    /// ```
88    #[must_use]
89    pub fn device_code_url(&self) -> String {
90        format!("https://{}/login/device/code", self.hostname)
91    }
92
93    /// Build the OAuth access token URL for this host.
94    ///
95    /// # Examples
96    ///
97    /// ```
98    /// use gor::host::Host;
99    ///
100    /// let host = Host::new("github.com");
101    /// assert_eq!(
102    ///     host.access_token_url(),
103    ///     "https://github.com/login/oauth/access_token"
104    /// );
105    /// ```
106    #[must_use]
107    pub fn access_token_url(&self) -> String {
108        format!("https://{}/login/oauth/access_token", self.hostname)
109    }
110
111    /// Build the device activation URL shown to the user.
112    ///
113    /// # Examples
114    ///
115    /// ```
116    /// use gor::host::Host;
117    ///
118    /// let host = Host::new("github.com");
119    /// assert_eq!(host.device_activation_url(), "https://github.com/login/device");
120    /// ```
121    #[must_use]
122    pub fn device_activation_url(&self) -> String {
123        format!("https://{}/login/device", self.hostname)
124    }
125}
126
127impl Default for Host {
128    fn default() -> Self {
129        Self::new("github.com")
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn default_is_github_dot_com() {
139        let host = Host::default();
140        assert_eq!(host.hostname(), "github.com");
141        assert_eq!(host.api_base(), "https://api.github.com");
142    }
143
144    #[test]
145    fn github_api_url() {
146        let host = Host::new("github.com");
147        assert_eq!(host.api_url("/user"), "https://api.github.com/user");
148        assert_eq!(
149            host.api_url("/repos/owner/repo"),
150            "https://api.github.com/repos/owner/repo"
151        );
152    }
153
154    #[test]
155    fn ghes_api_url() {
156        let host = Host::new("git.example.com");
157        assert_eq!(host.api_base(), "https://git.example.com/api/v3");
158        assert_eq!(host.api_url("/user"), "https://git.example.com/api/v3/user");
159    }
160
161    #[test]
162    fn device_code_url_github() {
163        let host = Host::new("github.com");
164        assert_eq!(
165            host.device_code_url(),
166            "https://github.com/login/device/code"
167        );
168    }
169
170    #[test]
171    fn device_code_url_ghes() {
172        let host = Host::new("git.example.com");
173        assert_eq!(
174            host.device_code_url(),
175            "https://git.example.com/login/device/code"
176        );
177    }
178
179    #[test]
180    fn access_token_url_github() {
181        let host = Host::new("github.com");
182        assert_eq!(
183            host.access_token_url(),
184            "https://github.com/login/oauth/access_token"
185        );
186    }
187
188    #[test]
189    fn device_activation_url() {
190        let host = Host::new("github.com");
191        assert_eq!(
192            host.device_activation_url(),
193            "https://github.com/login/device"
194        );
195    }
196}