1use crate::{
2 error::Result,
3 http::Requester,
4 pagination::PageStream,
5 params::wrap_params,
6 resources::{
7 account::Account,
8 course::Course,
9 params::{course_params::CreateCourseParams, user_params::CreateUserParams},
10 user::{CurrentUser, User, UserId},
11 },
12};
13use reqwest::Client;
14use std::sync::Arc;
15use url::Url;
16
17pub struct Canvas {
28 pub(crate) requester: Arc<Requester>,
29}
30
31impl Canvas {
32 pub fn new(base_url: &str, access_token: &str) -> Result<Self> {
37 Self::with_client(base_url, access_token, Client::new())
38 }
39
40 pub fn with_client(base_url: &str, access_token: &str, client: Client) -> Result<Self> {
42 let base_url = validate_base_url(base_url)?;
43 let api_url = base_url.join("api/v1/")?;
44 Ok(Self {
45 requester: Arc::new(Requester::new(
46 api_url,
47 access_token.trim().to_string(),
48 client,
49 )),
50 })
51 }
52
53 pub async fn get_course(&self, course_id: u64) -> Result<Course> {
62 let mut course: Course = self
63 .requester
64 .get(&format!("courses/{course_id}"), &[])
65 .await?;
66 course.requester = Some(Arc::clone(&self.requester));
67 Ok(course)
68 }
69
70 pub fn get_courses(&self) -> PageStream<Course> {
75 PageStream::new_with_injector(
76 Arc::clone(&self.requester),
77 "courses",
78 vec![],
79 |mut c: Course, req| {
80 c.requester = Some(Arc::clone(&req));
81 c
82 },
83 )
84 }
85
86 pub async fn create_course(
91 &self,
92 account_id: u64,
93 params: CreateCourseParams,
94 ) -> Result<Course> {
95 let form = wrap_params("course", ¶ms);
96 let mut course: Course = self
97 .requester
98 .post(&format!("accounts/{account_id}/courses"), &form)
99 .await?;
100 course.requester = Some(Arc::clone(&self.requester));
101 Ok(course)
102 }
103
104 pub async fn delete_course(&self, course_id: u64) -> Result<Course> {
109 let params = vec![("event".to_string(), "delete".to_string())];
110 let mut course: Course = self
111 .requester
112 .delete(&format!("courses/{course_id}"), ¶ms)
113 .await?;
114 course.requester = Some(Arc::clone(&self.requester));
115 Ok(course)
116 }
117
118 pub async fn get_user(&self, user_id: UserId) -> Result<User> {
127 let id = user_id.to_path_segment();
128 let mut user: User = self.requester.get(&format!("users/{id}"), &[]).await?;
129 user.requester = Some(Arc::clone(&self.requester));
130 Ok(user)
131 }
132
133 pub async fn get_current_user(&self) -> Result<CurrentUser> {
138 self.requester.get("users/self", &[]).await
139 }
140
141 pub async fn create_user(&self, account_id: u64, params: CreateUserParams) -> Result<User> {
146 let form = wrap_params("user", ¶ms);
147 let mut user: User = self
148 .requester
149 .post(&format!("accounts/{account_id}/users"), &form)
150 .await?;
151 user.requester = Some(Arc::clone(&self.requester));
152 Ok(user)
153 }
154
155 pub async fn get_account(&self, account_id: u64) -> Result<Account> {
164 self.requester
165 .get(&format!("accounts/{account_id}"), &[])
166 .await
167 }
168
169 pub fn get_accounts(&self) -> PageStream<Account> {
174 PageStream::new(Arc::clone(&self.requester), "accounts", vec![])
175 }
176}
177
178fn validate_base_url(raw: &str) -> Result<Url> {
179 let trimmed = raw.trim().trim_end_matches('/');
180 if trimmed.contains("/api/v1") {
181 return Err(crate::error::CanvasError::ApiError {
182 status: 0,
183 message: "base_url should not include /api/v1".into(),
184 });
185 }
186 Ok(Url::parse(&format!("{trimmed}/"))?)
187}