canvas_lms_sync/canvas_api/
mod.rs1use once_cell::sync::Lazy;
2use regex::Regex;
3use reqwest::{header::HeaderValue, IntoUrl};
4use serde::{de::DeserializeOwned, Deserialize};
5
6pub mod files;
7pub mod modules;
8
9pub struct Client {
10 reqwest: reqwest::Client,
11 host: String,
12 auth_bearer: String,
13}
14
15#[derive(Debug, Deserialize)]
16pub struct ApiError {
17 pub errors: Vec<ApiErrorDetail>,
18}
19
20#[derive(Debug, Deserialize)]
21pub struct ApiErrorDetail {
22 pub message: String,
23}
24
25pub struct ResponsePagination {
26 current: String,
27 prev: Option<String>,
28 next: Option<String>,
29 first: String,
30 last: String,
31}
32
33impl From<&HeaderValue> for ResponsePagination {
34 fn from(value: &HeaderValue) -> Self {
35 static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r#"<(.+?)>; rel="(.+?)""#).unwrap());
36
37 RE.captures_iter(value.to_str().unwrap())
38 .map(|cap| (cap[2].to_string(), cap[1].to_string()))
39 .fold(
40 ResponsePagination {
41 current: String::new(),
42 prev: None,
43 next: None,
44 first: String::new(),
45 last: String::new(),
46 },
47 |mut acc, (rel, url)| {
48 match rel.as_str() {
49 "current" => acc.current = url,
50 "prev" => acc.prev = Some(url),
51 "next" => acc.next = Some(url),
52 "first" => acc.first = url,
53 "last" => acc.last = url,
54 _ => (),
55 }
56 acc
57 },
58 )
59 }
60}
61
62#[derive(Debug)]
63pub enum Error {
64 ApiError(ApiError),
65 ReqwestError(reqwest::Error),
66}
67
68pub type ApiResult<T> = Result<T, Error>;
69
70impl Client {
71 pub fn new(host: String, auth_bearer: String) -> Self {
72 Self {
73 reqwest: reqwest::Client::new(),
74 host,
75 auth_bearer,
76 }
77 }
78 pub fn build_url(&self, path: &str) -> String {
79 format!(
80 "{}/{}",
81 self.host.strip_suffix("/").unwrap(),
82 path.strip_prefix("/").unwrap()
83 )
84 }
85 pub async fn make_json_request<T: DeserializeOwned, U: IntoUrl>(
86 &self,
87 url: U,
88 ) -> ApiResult<(T, Option<ResponsePagination>)> {
89 let response = self
90 .reqwest
91 .get(url)
92 .bearer_auth(self.auth_bearer.clone())
93 .send()
94 .await
95 .map_err(Error::ReqwestError)?;
96
97 if response.status() != 200 {
98 let json = response.json().await.map_err(Error::ReqwestError)?;
99 return Err(Error::ApiError(json));
100 }
101 let pagination = response.headers().get("Link").map(|v| v.into());
102 let json = response.json().await.map_err(Error::ReqwestError)?;
103 Ok((json, pagination))
104 }
105}