io_oauth/2.0/
refresh-access-token.rs1use std::{borrow::Cow, collections::HashSet};
6
7use http::{header::CONTENT_TYPE, request};
8use io_http::v1_1::coroutines::send::{SendHttp, SendHttpError, SendHttpResult};
9use io_stream::io::StreamIo;
10use secrecy::{ExposeSecret, SecretString};
11use thiserror::Error;
12use url::form_urlencoded::Serializer;
13
14use super::issue_access_token::{
15 AccessTokenResponse, IssueAccessTokenErrorParams, IssueAccessTokenSuccessParams,
16};
17
18#[derive(Debug, Error)]
20pub enum RefreshOauth2AccessTokenError {
21 #[error(transparent)]
22 SendHttpRefresh(#[from] SendHttpError),
23 #[error(transparent)]
24 ParseHttpResponse(#[from] serde_json::Error),
25}
26
27#[derive(Debug)]
29pub enum RefreshOauth2AccessTokenResult {
30 Ok(AccessTokenResponse),
32 Io(StreamIo),
34 Err(RefreshOauth2AccessTokenError),
36}
37
38pub struct RefreshOauth2AccessToken(SendHttp);
46
47impl RefreshOauth2AccessToken {
48 pub fn new(
50 request: request::Builder,
51 body: RefreshAccessTokenParams<'_>,
52 ) -> http::Result<Self> {
53 let request = request
54 .header(CONTENT_TYPE, "application/x-www-form-urlencoded")
55 .body(body.to_string().into_bytes())?;
56
57 let send = SendHttp::new(request);
58 Ok(Self(send))
59 }
60
61 pub fn resume(&mut self, arg: Option<StreamIo>) -> RefreshOauth2AccessTokenResult {
63 let response = match self.0.resume(arg) {
64 SendHttpResult::Ok(result) => result.response,
65 SendHttpResult::Io(io) => return RefreshOauth2AccessTokenResult::Io(io),
66 SendHttpResult::Err(err) => return RefreshOauth2AccessTokenResult::Err(err.into()),
67 };
68
69 let body = response.body().as_slice();
70
71 if !response.status().is_success() {
72 return match IssueAccessTokenErrorParams::try_from(body) {
73 Ok(res) => RefreshOauth2AccessTokenResult::Ok(Err(res)),
74 Err(err) => RefreshOauth2AccessTokenResult::Err(err.into()),
75 };
76 }
77
78 match IssueAccessTokenSuccessParams::try_from(body) {
79 Ok(res) => RefreshOauth2AccessTokenResult::Ok(Ok(res)),
80 Err(err) => RefreshOauth2AccessTokenResult::Err(err.into()),
81 }
82 }
83}
84
85#[derive(Debug)]
95pub struct RefreshAccessTokenParams<'a> {
96 pub client_id: String,
97 pub refresh_token: SecretString,
98 pub scopes: HashSet<Cow<'a, str>>,
99}
100
101impl<'a> RefreshAccessTokenParams<'a> {
102 pub fn new(client_id: impl ToString, refresh_token: impl Into<SecretString>) -> Self {
103 Self {
104 client_id: client_id.to_string(),
105 refresh_token: refresh_token.into(),
106 scopes: HashSet::new(),
107 }
108 }
109
110 pub fn to_serializer(&self) -> Serializer<'a, String> {
111 let mut serializer = Serializer::new(String::new());
112
113 serializer.append_pair("grant_type", "refresh_token");
114 serializer.append_pair("client_id", &self.client_id);
115 serializer.append_pair("refresh_token", &self.refresh_token.expose_secret());
116
117 if !self.scopes.is_empty() {
118 let mut scope = String::new();
119 let mut glue = "";
120
121 for token in &self.scopes {
122 scope.push_str(glue);
123 scope.push_str(token);
124 glue = " ";
125 }
126
127 serializer.append_pair("scope", &scope);
128 }
129
130 serializer
131 }
132}
133
134impl ToString for RefreshAccessTokenParams<'_> {
135 fn to_string(&self) -> String {
136 self.to_serializer().finish()
137 }
138}