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 self.requester.get(&format!("files/{file_id}"), &[]).await
247 }
248
249 pub async fn get_folder(&self, folder_id: u64) -> Result<Folder> {
254 self.requester
255 .get(&format!("folders/{folder_id}"), &[])
256 .await
257 }
258
259 pub async fn get_progress(&self, progress_id: u64) -> Result<Progress> {
264 self.requester
265 .get(&format!("progress/{progress_id}"), &[])
266 .await
267 }
268
269 pub async fn get_conversation(&self, conversation_id: u64) -> Result<Conversation> {
278 let mut c: Conversation = self
279 .requester
280 .get(&format!("conversations/{conversation_id}"), &[])
281 .await?;
282 c.requester = Some(Arc::clone(&self.requester));
283 Ok(c)
284 }
285
286 pub fn get_conversations(&self) -> PageStream<Conversation> {
291 PageStream::new_with_injector(
292 Arc::clone(&self.requester),
293 "conversations",
294 vec![],
295 |mut c: Conversation, req| {
296 c.requester = Some(Arc::clone(&req));
297 c
298 },
299 )
300 }
301
302 pub async fn create_conversation(
307 &self,
308 recipients: &[&str],
309 body: &str,
310 params: ConversationParams,
311 ) -> Result<Conversation> {
312 let mut form: Vec<(String, String)> = recipients
313 .iter()
314 .map(|r| ("recipients[]".into(), r.to_string()))
315 .collect();
316 form.push(("body".into(), body.to_string()));
317 if let Some(subject) = params.subject {
318 form.push(("subject".into(), subject));
319 }
320 if let Some(fg) = params.force_new {
321 form.push(("force_new".into(), fg.to_string()));
322 }
323 if let Some(gc) = params.group_conversation {
324 form.push(("group_conversation".into(), gc.to_string()));
325 }
326 if let Some(ctx) = params.context_code {
327 form.push(("context_code".into(), ctx));
328 }
329 let result: serde_json::Value = self.requester.post("conversations", &form).await?;
331 let first = result
332 .as_array()
333 .and_then(|a| a.first())
334 .cloned()
335 .unwrap_or(result);
336 let mut c: Conversation = serde_json::from_value(first)?;
337 c.requester = Some(Arc::clone(&self.requester));
338 Ok(c)
339 }
340
341 pub async fn get_calendar_event(&self, event_id: u64) -> Result<CalendarEvent> {
350 let mut e: CalendarEvent = self
351 .requester
352 .get(&format!("calendar_events/{event_id}"), &[])
353 .await?;
354 e.requester = Some(Arc::clone(&self.requester));
355 Ok(e)
356 }
357
358 pub fn get_calendar_events(&self) -> PageStream<CalendarEvent> {
363 PageStream::new_with_injector(
364 Arc::clone(&self.requester),
365 "calendar_events",
366 vec![],
367 |mut e: CalendarEvent, req| {
368 e.requester = Some(Arc::clone(&req));
369 e
370 },
371 )
372 }
373
374 pub async fn create_calendar_event(
379 &self,
380 context_code: &str,
381 params: CalendarEventParams,
382 ) -> Result<CalendarEvent> {
383 let body = serde_json::to_value(¶ms).unwrap_or_default();
384 let mut form = wrap_params("calendar_event", &body);
385 form.push((
386 "calendar_event[context_code]".into(),
387 context_code.to_string(),
388 ));
389 let mut e: CalendarEvent = self.requester.post("calendar_events", &form).await?;
390 e.requester = Some(Arc::clone(&self.requester));
391 Ok(e)
392 }
393
394 pub async fn get_planner_note(&self, note_id: u64) -> Result<PlannerNote> {
403 let mut n: PlannerNote = self
404 .requester
405 .get(&format!("planner_notes/{note_id}"), &[])
406 .await?;
407 n.requester = Some(Arc::clone(&self.requester));
408 Ok(n)
409 }
410
411 pub fn get_planner_notes(&self) -> PageStream<PlannerNote> {
416 PageStream::new_with_injector(
417 Arc::clone(&self.requester),
418 "planner_notes",
419 vec![],
420 |mut n: PlannerNote, req| {
421 n.requester = Some(Arc::clone(&req));
422 n
423 },
424 )
425 }
426
427 pub async fn create_planner_note(&self, params: PlannerNoteParams) -> Result<PlannerNote> {
432 let flat: Vec<(String, String)> = serde_json::to_value(¶ms)
433 .unwrap_or_default()
434 .as_object()
435 .into_iter()
436 .flatten()
437 .filter_map(|(k, v)| {
438 v.as_str()
439 .map(|s| (k.clone(), s.to_string()))
440 .or_else(|| v.as_u64().map(|n| (k.clone(), n.to_string())))
441 })
442 .collect();
443 let mut n: PlannerNote = self.requester.post("planner_notes", &flat).await?;
444 n.requester = Some(Arc::clone(&self.requester));
445 Ok(n)
446 }
447
448 pub async fn get_planner_override(&self, override_id: u64) -> Result<PlannerOverride> {
453 let mut o: PlannerOverride = self
454 .requester
455 .get(&format!("planner/overrides/{override_id}"), &[])
456 .await?;
457 o.requester = Some(Arc::clone(&self.requester));
458 Ok(o)
459 }
460
461 pub fn get_planner_overrides(&self) -> PageStream<PlannerOverride> {
466 PageStream::new_with_injector(
467 Arc::clone(&self.requester),
468 "planner/overrides",
469 vec![],
470 |mut o: PlannerOverride, req| {
471 o.requester = Some(Arc::clone(&req));
472 o
473 },
474 )
475 }
476
477 pub async fn create_planner_override(
482 &self,
483 plannable_type: &str,
484 plannable_id: u64,
485 ) -> Result<PlannerOverride> {
486 let form = vec![
487 ("plannable_type".into(), plannable_type.to_string()),
488 ("plannable_id".into(), plannable_id.to_string()),
489 ];
490 let mut o: PlannerOverride = self.requester.post("planner/overrides", &form).await?;
491 o.requester = Some(Arc::clone(&self.requester));
492 Ok(o)
493 }
494
495 pub async fn get_eportfolio(&self, eportfolio_id: u64) -> Result<EPortfolio> {
504 let mut p: EPortfolio = self
505 .requester
506 .get(&format!("eportfolios/{eportfolio_id}"), &[])
507 .await?;
508 p.requester = Some(Arc::clone(&self.requester));
509 Ok(p)
510 }
511
512 pub async fn get_appointment_group(&self, group_id: u64) -> Result<AppointmentGroup> {
521 let mut a: AppointmentGroup = self
522 .requester
523 .get(&format!("appointment_groups/{group_id}"), &[])
524 .await?;
525 a.requester = Some(Arc::clone(&self.requester));
526 Ok(a)
527 }
528
529 pub fn get_appointment_groups(&self) -> PageStream<AppointmentGroup> {
534 PageStream::new_with_injector(
535 Arc::clone(&self.requester),
536 "appointment_groups",
537 vec![],
538 |mut a: AppointmentGroup, req| {
539 a.requester = Some(Arc::clone(&req));
540 a
541 },
542 )
543 }
544
545 pub async fn create_appointment_group(
550 &self,
551 params: AppointmentGroupParams,
552 ) -> Result<AppointmentGroup> {
553 let body = serde_json::to_value(¶ms).unwrap_or_default();
554 let form = wrap_params("appointment_group", &body);
555 let mut a: AppointmentGroup = self.requester.post("appointment_groups", &form).await?;
556 a.requester = Some(Arc::clone(&self.requester));
557 Ok(a)
558 }
559
560 #[cfg(feature = "graphql")]
575 pub fn graphql(&self) -> crate::graphql::GraphQL {
576 crate::graphql::GraphQL {
577 requester: Arc::clone(&self.requester),
578 }
579 }
580
581 pub async fn get_poll(&self, poll_id: u64) -> Result<Poll> {
594 let val: serde_json::Value = self.requester.get(&format!("polls/{poll_id}"), &[]).await?;
595 let mut poll: Poll = serde_json::from_value(val["polls"][0].clone())?;
596 poll.requester = Some(Arc::clone(&self.requester));
597 Ok(poll)
598 }
599
600 pub fn get_polls(&self) -> PageStream<Poll> {
605 PageStream::new_with_injector(
606 Arc::clone(&self.requester),
607 "polls",
608 vec![],
609 |mut p: Poll, req| {
610 p.requester = Some(Arc::clone(&req));
611 p
612 },
613 )
614 }
615
616 pub async fn create_poll(&self, params: CreatePollParams) -> Result<Poll> {
621 let form = wrap_params("polls[]", ¶ms);
622 let val: serde_json::Value = self.requester.post("polls", &form).await?;
623 let mut poll: Poll = serde_json::from_value(val["polls"][0].clone())?;
624 poll.requester = Some(Arc::clone(&self.requester));
625 Ok(poll)
626 }
627
628 pub async fn create_jwt(&self) -> Result<CanvasJwt> {
635 self.requester.post("jwts", &[]).await
636 }
637
638 pub async fn refresh_jwt(&self, token: &str) -> Result<CanvasJwt> {
643 let params = vec![("jwt".into(), token.to_string())];
644 self.requester.post("jwts/refresh", ¶ms).await
645 }
646}
647
648fn validate_base_url(raw: &str) -> Result<Url> {
649 let trimmed = raw.trim().trim_end_matches('/');
650 if trimmed.contains("/api/v1") {
651 return Err(crate::error::CanvasError::ApiError {
652 status: 0,
653 message: "base_url should not include /api/v1".into(),
654 });
655 }
656 Ok(Url::parse(&format!("{trimmed}/"))?)
657}