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}