stack_overflow_client/client/
mod.rs

1use reqwest::Client;
2
3use crate::{
4    answers::Answer,
5    common::{ApiVersion, Info, Response, StackSite, STACK_APP_API},
6    questions::Question,
7};
8
9#[derive(Debug)]
10pub struct StackClient {
11    client: Client,
12    api_version: ApiVersion,
13    stack_site: StackSite,
14}
15
16/// StackClient is a client for the Stack Exchange API
17///
18/// Construct a default client for StackOverflow site, API version 2.3 (https://meta.stackexchange.com/questions/366977/api-2-3-release)
19/// ```rust
20/// use stack_overflow_client::client::StackClient;
21/// let client = StackClient::new();
22/// ```
23///
24/// Construct a e client for a specific site and API version
25/// ```rust
26/// use stack_overflow_client::client::StackClientBuilder;
27/// use stack_overflow_client::common::{ApiVersion, StackSite};
28/// let client = StackClientBuilder::new()
29///                 .stack_site(StackSite::StackOverflow)
30///                 .version(ApiVersion::V2_3)
31///                 .build();
32/// ```
33impl StackClient {
34    /// StackClient defaults to StackOverflow as the site and api version 2.3
35    pub fn new() -> Self {
36        StackClientBuilder::new()
37            .version(ApiVersion::V2_3)
38            .stack_site(StackSite::StackOverflow)
39            .build()
40    }
41
42    /// Get info about the client's Stack site. Returns a collection of statistics about the site.
43    /// API Docs: https://api.stackexchange.com/docs/info
44    pub async fn get_info(
45        &self,
46    ) -> Result<Response<Info>, Box<dyn std::error::Error>> {
47        let url = format!(
48            "{}/{}/info?site={}",
49            STACK_APP_API,
50            self.api_version.to_string(),
51            self.stack_site.to_string()
52        );
53        let resp = self.client.get(&url).send().await?.text().await?;
54        let deserialized: Response<Info> = serde_json::from_str(&resp.as_str())
55            .expect("unable to deserialize response");
56        Ok(deserialized)
57    }
58
59    /// Get featured questions for a tag.
60    /// Note that this only retrieves the first page of results.
61    /// API Docs: https://api.stackexchange.com/docs/featured-questions
62    pub async fn get_featured_questions(
63        &self,
64        tag: &str,
65    ) -> Result<Response<Question>, Box<dyn std::error::Error>> {
66        self.get_featured_questions_paginated(tag, 1).await
67    }
68
69    /// Get featured questions for a tag and explicitly provide a page.
70    /// Note that pagination is not supported beyond 25 pages because authentication is required.
71    /// API Docs: https://api.stackexchange.com/docs/featured-questions
72    pub async fn get_featured_questions_paginated(
73        &self,
74        tag: &str,
75        page: u32,
76    ) -> Result<Response<Question>, Box<dyn std::error::Error>> {
77        let url = format!(
78            "{}/{}/questions/featured?tagged={}&site={}&page={}",
79            STACK_APP_API,
80            self.api_version.to_string(),
81            tag,
82            self.stack_site.to_string(),
83            page
84        );
85
86        let resp = self.client.get(&url).send().await?.text().await?;
87        let deserialized: Response<Question> = serde_json::from_str(
88            &resp.as_str(),
89        )
90        .expect(format!("Failed to deserialized response: {}", resp).as_str());
91        Ok(deserialized)
92    }
93
94    /// Get answers provided by a user
95    /// API Docs: https://api.stackexchange.com/docs/answers-on-users
96    pub async fn get_answers_for_user(
97        &self,
98        user_id: u32,
99        page: Option<u32>,
100    ) -> Result<Response<Answer>, Box<dyn std::error::Error>> {
101        let url: String = format!(
102            "{}/{}/users/{}/answers?order=desc&sort=activity&site={}&page={}",
103            STACK_APP_API,
104            self.api_version.to_string(),
105            user_id.to_string(),
106            self.stack_site.to_string(),
107            page.unwrap_or(1),
108        );
109
110        let resp = self.client.get(&url).send().await?.text().await?;
111        let deserialized: Response<Answer> = serde_json::from_str(
112            resp.as_str(),
113        )
114        .expect(format!("Failed to deserialize response: {}", resp).as_str());
115        Ok(deserialized)
116    }
117}
118pub struct StackClientBuilder {
119    api_version: Option<ApiVersion>,
120    stack_site: Option<StackSite>,
121}
122
123impl StackClientBuilder {
124    pub fn new() -> Self {
125        StackClientBuilder {
126            api_version: None,
127            stack_site: None,
128        }
129    }
130
131    pub fn version(mut self, version: ApiVersion) -> Self {
132        self.api_version = Some(version);
133        self
134    }
135
136    pub fn stack_site(mut self, site: StackSite) -> Self {
137        self.stack_site = Some(site);
138        self
139    }
140
141    pub fn build(self) -> StackClient {
142        StackClient {
143            api_version: self.api_version.unwrap_or(ApiVersion::V2_3),
144            stack_site: self.stack_site.unwrap_or(StackSite::StackOverflow),
145            client: reqwest::Client::builder()
146                .gzip(true) // Stack api responses are gzip encoded: https://api.stackexchange.com/docs/compression
147                .build()
148                .expect("unable to create reqwest http client"),
149        }
150    }
151}