genius_rust/
auth.rs

1use reqwest::{Client, Url};
2use serde::{Deserialize, Serialize};
3
4#[cfg(test)]
5mod test {
6    use crate::auth::auth_url;
7    #[test]
8    fn auth_url_test() {
9        let url = auth_url("my_client_id", "code", None, Some("me vote"), None);
10        assert_eq!("https://api.genius.com/oauth/authorize?client_id=my_client_id&response_type=code&scope=me+vote", url.as_str());
11    }
12}
13
14/// Authentication by login.
15pub mod login;
16
17#[derive(Serialize)]
18struct AuthRequest {
19    code: String,
20    client_secret: String,
21    client_id: String,
22    redirect_uri: String,
23    response_type: String,
24    grant_type: String,
25}
26
27/// Authentication response.
28#[derive(Serialize, Deserialize, Debug)]
29pub struct AuthResponse {
30    pub access_token: Option<String>,
31    pub token_type: Option<String>,
32    pub error: Option<String>,
33    pub error_description: Option<String>,
34}
35
36/// Format genius authentication URL, the result is a URL. `client_id` and `redirect_uri` ​​are found at <https://genius.com/api-clients>.
37///
38/// Only `response_type` and `client_id` is required, `response_type` can be `token` or `code`.
39/// When the response type is `token` the redirect URL will contain an `access_token` parameter with the token.
40/// When the response type is `code` The redirect URL will contain a `code` parameter with a code that using the [`authenticate`] function will return a token.
41/// > If you are creating something server-side, the `code` works great and this library has a method to handle it: [`authenticate`].
42///
43/// The state will be a value that be passed when redirected.
44///
45/// The scope will define what permissions your application will have. Available scopes are `me`, `create_annotation`, `manage_annotation` and `vote`.
46/// #### Examples
47/// Basic usage:
48/// ```
49/// use genius_rs::auth::auth_url;
50///
51/// let auth_url = auth_url("my_client_id", "code", None, Some("me vote"), None);
52/// ```
53#[must_use]
54pub fn auth_url(
55    client_id: &str,
56    response_type: &str,
57    redirect_uri: Option<&str>,
58    scope: Option<&str>,
59    state: Option<&str>,
60) -> Url {
61    let mut params = vec![("client_id", client_id), ("response_type", response_type)];
62    if let Some(redirect_uri) = redirect_uri {
63        params.push(("redirect_uri", redirect_uri));
64    }
65    if let Some(scope) = scope {
66        params.push(("scope", scope));
67    }
68    if let Some(state) = state {
69        params.push(("state", state));
70    }
71    Url::parse_with_params("https://api.genius.com/oauth/authorize", params)
72        .expect("Can't parse authentication URL.")
73}
74
75/// Transform the `code` in a token, the result is [`AuthResponse`]. `code` expires so be very light on this operation. The response token will be level `client`.
76///
77/// `client_secret`, `client_id` and `redirect_uri` are found at <https://genius.com/api-clients>.
78///
79/// # Errors
80///
81/// If the code is not valid or the request fails.
82pub async fn authenticate(
83    code: String,
84    client_secret: String,
85    client_id: String,
86    redirect_uri: String,
87) -> Result<AuthResponse, reqwest::Error> {
88    let auth_req = AuthRequest {
89        code,
90        client_secret,
91        client_id,
92        redirect_uri,
93        response_type: "code".to_owned(),
94        grant_type: "authorization_code".to_owned(),
95    };
96    let url = Url::parse("https://api.genius.com/oauth/token")
97        .expect("Could not parse valid URL from login_with_username input.");
98    let client = Client::new();
99    let request = client.post(url).json(&auth_req).send().await?;
100    let result = request.json::<AuthResponse>().await?;
101    Ok(result)
102}