1use crate::{
2 error::Result,
3 http::Requester,
4 pagination::PageStream,
5 params::wrap_params,
6 resources::{
7 account::Account,
8 appointment_group::{AppointmentGroup, AppointmentGroupParams},
9 calendar_event::{CalendarEvent, CalendarEventParams},
10 conversation::{Conversation, ConversationParams},
11 course::Course,
12 eportfolio::EPortfolio,
13 file::File,
14 folder::Folder,
15 group::Group,
16 jwt::CanvasJwt,
17 outcome::Outcome,
18 params::{course_params::CreateCourseParams, user_params::CreateUserParams},
19 planner::{PlannerNote, PlannerNoteParams, PlannerOverride},
20 poll::{CreatePollParams, Poll},
21 progress::Progress,
22 section::Section,
23 user::{CurrentUser, User, UserId},
24 },
25};
26use reqwest::Client;
27use std::sync::Arc;
28use url::Url;
29
30pub struct Canvas {
41 pub(crate) requester: Arc<Requester>,
42}
43
44impl Canvas {
45 pub fn new(base_url: &str, access_token: &str) -> Result<Self> {
50 Self::with_client(base_url, access_token, Client::new())
51 }
52
53 pub fn with_client(base_url: &str, access_token: &str, client: Client) -> Result<Self> {
55 let base_url = validate_base_url(base_url)?;
56 let api_url = base_url.join("api/v1/")?;
57 Ok(Self {
58 requester: Arc::new(Requester::new(
59 api_url,
60 access_token.trim().to_string(),
61 client,
62 )),
63 })
64 }
65
66 pub async fn get_course(&self, course_id: u64) -> Result<Course> {
75 let mut course: Course = self
76 .requester
77 .get(&format!("courses/{course_id}"), &[])
78 .await?;
79 course.requester = Some(Arc::clone(&self.requester));
80 Ok(course)
81 }
82
83 pub fn get_courses(&self) -> PageStream<Course> {
88 PageStream::new_with_injector(
89 Arc::clone(&self.requester),
90 "courses",
91 vec![],
92 |mut c: Course, req| {
93 c.requester = Some(Arc::clone(&req));
94 c
95 },
96 )
97 }
98
99 pub async fn create_course(
104 &self,
105 account_id: u64,
106 params: CreateCourseParams,
107 ) -> Result<Course> {
108 let form = wrap_params("course", ¶ms);
109 let mut course: Course = self
110 .requester
111 .post(&format!("accounts/{account_id}/courses"), &form)
112 .await?;
113 course.requester = Some(Arc::clone(&self.requester));
114 Ok(course)
115 }
116
117 pub async fn delete_course(&self, course_id: u64) -> Result<Course> {
122 let params = vec![("event".to_string(), "delete".to_string())];
123 let mut course: Course = self
124 .requester
125 .delete(&format!("courses/{course_id}"), ¶ms)
126 .await?;
127 course.requester = Some(Arc::clone(&self.requester));
128 Ok(course)
129 }
130
131 pub async fn get_user(&self, user_id: UserId) -> Result<User> {
140 let id = user_id.to_path_segment();
141 let mut user: User = self.requester.get(&format!("users/{id}"), &[]).await?;
142 user.requester = Some(Arc::clone(&self.requester));
143 Ok(user)
144 }
145
146 pub async fn get_current_user(&self) -> Result<CurrentUser> {
151 self.requester.get("users/self", &[]).await
152 }
153
154 pub async fn create_user(&self, account_id: u64, params: CreateUserParams) -> Result<User> {
159 let form = wrap_params("user", ¶ms);
160 let mut user: User = self
161 .requester
162 .post(&format!("accounts/{account_id}/users"), &form)
163 .await?;
164 user.requester = Some(Arc::clone(&self.requester));
165 Ok(user)
166 }
167
168 pub async fn get_account(&self, account_id: u64) -> Result<Account> {
177 let mut account: Account = self
178 .requester
179 .get(&format!("accounts/{account_id}"), &[])
180 .await?;
181 account.requester = Some(Arc::clone(&self.requester));
182 Ok(account)
183 }
184
185 pub async fn get_outcome(&self, outcome_id: u64) -> Result<Outcome> {
190 let mut outcome: Outcome = self
191 .requester
192 .get(&format!("outcomes/{outcome_id}"), &[])
193 .await?;
194 outcome.requester = Some(Arc::clone(&self.requester));
195 Ok(outcome)
196 }
197
198 pub fn get_accounts(&self) -> PageStream<Account> {
203 PageStream::new_with_injector(
204 Arc::clone(&self.requester),
205 "accounts",
206 vec![],
207 |mut a: Account, req| {
208 a.requester = Some(Arc::clone(&req));
209 a
210 },
211 )
212 }
213
214 pub async fn get_section(&self, section_id: u64) -> Result<Section> {
223 self.requester
224 .get(&format!("sections/{section_id}"), &[])
225 .await
226 }
227
228 pub async fn get_group(&self, group_id: u64) -> Result<Group> {
233 let mut g: Group = self
234 .requester
235 .get(&format!("groups/{group_id}"), &[])
236 .await?;
237 g.requester = Some(Arc::clone(&self.requester));
238 Ok(g)
239 }
240
241 pub async fn get_file(&self, file_id: u64) -> Result<File> {
246 let mut f: File = self.requester.get(&format!("files/{file_id}"), &[]).await?;
247 f.requester = Some(Arc::clone(&self.requester));
248 Ok(f)
249 }
250
251 pub async fn get_folder(&self, folder_id: u64) -> Result<Folder> {
256 let mut folder: Folder = self
257 .requester
258 .get(&format!("folders/{folder_id}"), &[])
259 .await?;
260 folder.requester = Some(Arc::clone(&self.requester));
261 Ok(folder)
262 }
263
264 pub async fn get_progress(&self, progress_id: u64) -> Result<Progress> {
269 let mut p: Progress = self
270 .requester
271 .get(&format!("progress/{progress_id}"), &[])
272 .await?;
273 p.requester = Some(Arc::clone(&self.requester));
274 Ok(p)
275 }
276
277 pub async fn get_conversation(&self, conversation_id: u64) -> Result<Conversation> {
286 let mut c: Conversation = self
287 .requester
288 .get(&format!("conversations/{conversation_id}"), &[])
289 .await?;
290 c.requester = Some(Arc::clone(&self.requester));
291 Ok(c)
292 }
293
294 pub fn get_conversations(&self) -> PageStream<Conversation> {
299 PageStream::new_with_injector(
300 Arc::clone(&self.requester),
301 "conversations",
302 vec![],
303 |mut c: Conversation, req| {
304 c.requester = Some(Arc::clone(&req));
305 c
306 },
307 )
308 }
309
310 pub async fn create_conversation(
315 &self,
316 recipients: &[&str],
317 body: &str,
318 params: ConversationParams,
319 ) -> Result<Conversation> {
320 let mut form: Vec<(String, String)> = recipients
321 .iter()
322 .map(|r| ("recipients[]".into(), r.to_string()))
323 .collect();
324 form.push(("body".into(), body.to_string()));
325 if let Some(subject) = params.subject {
326 form.push(("subject".into(), subject));
327 }
328 if let Some(fg) = params.force_new {
329 form.push(("force_new".into(), fg.to_string()));
330 }
331 if let Some(gc) = params.group_conversation {
332 form.push(("group_conversation".into(), gc.to_string()));
333 }
334 if let Some(ctx) = params.context_code {
335 form.push(("context_code".into(), ctx));
336 }
337 let result: serde_json::Value = self.requester.post("conversations", &form).await?;
339 let first = result
340 .as_array()
341 .and_then(|a| a.first())
342 .cloned()
343 .unwrap_or(result);
344 let mut c: Conversation = serde_json::from_value(first)?;
345 c.requester = Some(Arc::clone(&self.requester));
346 Ok(c)
347 }
348
349 pub async fn get_calendar_event(&self, event_id: u64) -> Result<CalendarEvent> {
358 let mut e: CalendarEvent = self
359 .requester
360 .get(&format!("calendar_events/{event_id}"), &[])
361 .await?;
362 e.requester = Some(Arc::clone(&self.requester));
363 Ok(e)
364 }
365
366 pub fn get_calendar_events(&self) -> PageStream<CalendarEvent> {
371 PageStream::new_with_injector(
372 Arc::clone(&self.requester),
373 "calendar_events",
374 vec![],
375 |mut e: CalendarEvent, req| {
376 e.requester = Some(Arc::clone(&req));
377 e
378 },
379 )
380 }
381
382 pub async fn create_calendar_event(
387 &self,
388 context_code: &str,
389 params: CalendarEventParams,
390 ) -> Result<CalendarEvent> {
391 let body = serde_json::to_value(¶ms).unwrap_or_default();
392 let mut form = wrap_params("calendar_event", &body);
393 form.push((
394 "calendar_event[context_code]".into(),
395 context_code.to_string(),
396 ));
397 let mut e: CalendarEvent = self.requester.post("calendar_events", &form).await?;
398 e.requester = Some(Arc::clone(&self.requester));
399 Ok(e)
400 }
401
402 pub async fn get_planner_note(&self, note_id: u64) -> Result<PlannerNote> {
411 let mut n: PlannerNote = self
412 .requester
413 .get(&format!("planner_notes/{note_id}"), &[])
414 .await?;
415 n.requester = Some(Arc::clone(&self.requester));
416 Ok(n)
417 }
418
419 pub fn get_planner_notes(&self) -> PageStream<PlannerNote> {
424 PageStream::new_with_injector(
425 Arc::clone(&self.requester),
426 "planner_notes",
427 vec![],
428 |mut n: PlannerNote, req| {
429 n.requester = Some(Arc::clone(&req));
430 n
431 },
432 )
433 }
434
435 pub async fn create_planner_note(&self, params: PlannerNoteParams) -> Result<PlannerNote> {
440 let flat: Vec<(String, String)> = serde_json::to_value(¶ms)
441 .unwrap_or_default()
442 .as_object()
443 .into_iter()
444 .flatten()
445 .filter_map(|(k, v)| {
446 v.as_str()
447 .map(|s| (k.clone(), s.to_string()))
448 .or_else(|| v.as_u64().map(|n| (k.clone(), n.to_string())))
449 })
450 .collect();
451 let mut n: PlannerNote = self.requester.post("planner_notes", &flat).await?;
452 n.requester = Some(Arc::clone(&self.requester));
453 Ok(n)
454 }
455
456 pub async fn get_planner_override(&self, override_id: u64) -> Result<PlannerOverride> {
461 let mut o: PlannerOverride = self
462 .requester
463 .get(&format!("planner/overrides/{override_id}"), &[])
464 .await?;
465 o.requester = Some(Arc::clone(&self.requester));
466 Ok(o)
467 }
468
469 pub fn get_planner_overrides(&self) -> PageStream<PlannerOverride> {
474 PageStream::new_with_injector(
475 Arc::clone(&self.requester),
476 "planner/overrides",
477 vec![],
478 |mut o: PlannerOverride, req| {
479 o.requester = Some(Arc::clone(&req));
480 o
481 },
482 )
483 }
484
485 pub async fn create_planner_override(
490 &self,
491 plannable_type: &str,
492 plannable_id: u64,
493 ) -> Result<PlannerOverride> {
494 let form = vec![
495 ("plannable_type".into(), plannable_type.to_string()),
496 ("plannable_id".into(), plannable_id.to_string()),
497 ];
498 let mut o: PlannerOverride = self.requester.post("planner/overrides", &form).await?;
499 o.requester = Some(Arc::clone(&self.requester));
500 Ok(o)
501 }
502
503 pub async fn get_eportfolio(&self, eportfolio_id: u64) -> Result<EPortfolio> {
512 let mut p: EPortfolio = self
513 .requester
514 .get(&format!("eportfolios/{eportfolio_id}"), &[])
515 .await?;
516 p.requester = Some(Arc::clone(&self.requester));
517 Ok(p)
518 }
519
520 pub async fn get_appointment_group(&self, group_id: u64) -> Result<AppointmentGroup> {
529 let mut a: AppointmentGroup = self
530 .requester
531 .get(&format!("appointment_groups/{group_id}"), &[])
532 .await?;
533 a.requester = Some(Arc::clone(&self.requester));
534 Ok(a)
535 }
536
537 pub fn get_appointment_groups(&self) -> PageStream<AppointmentGroup> {
542 PageStream::new_with_injector(
543 Arc::clone(&self.requester),
544 "appointment_groups",
545 vec![],
546 |mut a: AppointmentGroup, req| {
547 a.requester = Some(Arc::clone(&req));
548 a
549 },
550 )
551 }
552
553 pub async fn create_appointment_group(
558 &self,
559 params: AppointmentGroupParams,
560 ) -> Result<AppointmentGroup> {
561 let body = serde_json::to_value(¶ms).unwrap_or_default();
562 let form = wrap_params("appointment_group", &body);
563 let mut a: AppointmentGroup = self.requester.post("appointment_groups", &form).await?;
564 a.requester = Some(Arc::clone(&self.requester));
565 Ok(a)
566 }
567
568 #[cfg(feature = "graphql")]
583 pub fn graphql(&self) -> crate::graphql::GraphQL {
584 crate::graphql::GraphQL {
585 requester: Arc::clone(&self.requester),
586 }
587 }
588
589 pub async fn get_poll(&self, poll_id: u64) -> Result<Poll> {
602 let val: serde_json::Value = self.requester.get(&format!("polls/{poll_id}"), &[]).await?;
603 let mut poll: Poll = serde_json::from_value(val["polls"][0].clone())?;
604 poll.requester = Some(Arc::clone(&self.requester));
605 Ok(poll)
606 }
607
608 pub fn get_polls(&self) -> PageStream<Poll> {
613 PageStream::new_with_injector(
614 Arc::clone(&self.requester),
615 "polls",
616 vec![],
617 |mut p: Poll, req| {
618 p.requester = Some(Arc::clone(&req));
619 p
620 },
621 )
622 }
623
624 pub async fn create_poll(&self, params: CreatePollParams) -> Result<Poll> {
629 let form = wrap_params("polls[]", ¶ms);
630 let val: serde_json::Value = self.requester.post("polls", &form).await?;
631 let mut poll: Poll = serde_json::from_value(val["polls"][0].clone())?;
632 poll.requester = Some(Arc::clone(&self.requester));
633 Ok(poll)
634 }
635
636 pub async fn create_jwt(&self) -> Result<CanvasJwt> {
643 self.requester.post("jwts", &[]).await
644 }
645
646 pub async fn refresh_jwt(&self, token: &str) -> Result<CanvasJwt> {
651 let params = vec![("jwt".into(), token.to_string())];
652 self.requester.post("jwts/refresh", ¶ms).await
653 }
654}
655
656fn validate_base_url(raw: &str) -> Result<Url> {
657 let trimmed = raw.trim().trim_end_matches('/');
658 if trimmed.contains("/api/v1") {
659 return Err(crate::error::CanvasError::ApiError {
660 status: 0,
661 message: "base_url should not include /api/v1".into(),
662 });
663 }
664 Ok(Url::parse(&format!("{trimmed}/"))?)
665}