1use crate::{
2 error::{CanvasError, Result},
3 http::Requester,
4 pagination::PageStream,
5 params::wrap_params,
6 resources::{progress::Progress, submission::Submission, user::User},
7};
8use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10use std::sync::Arc;
11
12use crate::resources::types::{SubmissionType, WorkflowState};
13
14#[derive(Debug, Default, Clone, Serialize)]
16pub struct AssignmentParams {
17 #[serde(skip_serializing_if = "Option::is_none")]
18 pub name: Option<String>,
19 #[serde(skip_serializing_if = "Option::is_none")]
20 pub position: Option<u64>,
21 #[serde(skip_serializing_if = "Option::is_none")]
22 pub submission_types: Option<Vec<String>>,
23 #[serde(skip_serializing_if = "Option::is_none")]
24 pub allowed_extensions: Option<Vec<String>>,
25 #[serde(skip_serializing_if = "Option::is_none")]
26 pub turnitin_enabled: Option<bool>,
27 #[serde(skip_serializing_if = "Option::is_none")]
28 pub allowed_attempts: Option<i64>,
29 #[serde(skip_serializing_if = "Option::is_none")]
30 pub points_possible: Option<f64>,
31 #[serde(skip_serializing_if = "Option::is_none")]
32 pub grading_type: Option<String>,
33 #[serde(skip_serializing_if = "Option::is_none")]
34 pub due_at: Option<DateTime<Utc>>,
35 #[serde(skip_serializing_if = "Option::is_none")]
36 pub lock_at: Option<DateTime<Utc>>,
37 #[serde(skip_serializing_if = "Option::is_none")]
38 pub unlock_at: Option<DateTime<Utc>>,
39 #[serde(skip_serializing_if = "Option::is_none")]
40 pub description: Option<String>,
41 #[serde(skip_serializing_if = "Option::is_none")]
42 pub published: Option<bool>,
43 #[serde(skip_serializing_if = "Option::is_none")]
44 pub omit_from_final_grade: Option<bool>,
45 #[serde(skip_serializing_if = "Option::is_none")]
46 pub assignment_group_id: Option<u64>,
47}
48
49#[derive(Debug, Default, Clone, Serialize)]
51pub struct SubmitAssignmentParams {
52 pub submission_type: String,
53 #[serde(skip_serializing_if = "Option::is_none")]
54 pub body: Option<String>,
55 #[serde(skip_serializing_if = "Option::is_none")]
56 pub url: Option<String>,
57 #[serde(skip_serializing_if = "Option::is_none")]
58 pub file_ids: Option<Vec<u64>>,
59 #[serde(skip_serializing_if = "Option::is_none")]
60 pub media_comment_id: Option<String>,
61 #[serde(skip_serializing_if = "Option::is_none")]
62 pub media_comment_type: Option<String>,
63}
64
65#[derive(Debug, Default, Clone, Serialize)]
67pub struct AssignmentOverrideParams {
68 #[serde(skip_serializing_if = "Option::is_none")]
69 pub student_ids: Option<Vec<u64>>,
70 #[serde(skip_serializing_if = "Option::is_none")]
71 pub group_id: Option<u64>,
72 #[serde(skip_serializing_if = "Option::is_none")]
73 pub course_section_id: Option<u64>,
74 #[serde(skip_serializing_if = "Option::is_none")]
75 pub title: Option<String>,
76 #[serde(skip_serializing_if = "Option::is_none")]
77 pub due_at: Option<DateTime<Utc>>,
78 #[serde(skip_serializing_if = "Option::is_none")]
79 pub unlock_at: Option<DateTime<Utc>>,
80 #[serde(skip_serializing_if = "Option::is_none")]
81 pub lock_at: Option<DateTime<Utc>>,
82}
83
84#[derive(Debug, Default, Clone, Serialize)]
86pub struct AssignmentGroupParams {
87 #[serde(skip_serializing_if = "Option::is_none")]
88 pub name: Option<String>,
89 #[serde(skip_serializing_if = "Option::is_none")]
90 pub group_weight: Option<f64>,
91 #[serde(skip_serializing_if = "Option::is_none")]
92 pub position: Option<u64>,
93}
94
95#[derive(Debug, Clone, Deserialize, Serialize, canvas_lms_api_derive::CanvasResource)]
97pub struct Assignment {
98 pub id: u64,
99 pub course_id: Option<u64>,
100 pub name: Option<String>,
101 pub description: Option<String>,
102 pub due_at: Option<DateTime<Utc>>,
103 pub unlock_at: Option<DateTime<Utc>>,
104 pub lock_at: Option<DateTime<Utc>>,
105 pub points_possible: Option<f64>,
106 pub grading_type: Option<String>,
107 pub assignment_group_id: Option<u64>,
108 pub workflow_state: Option<WorkflowState>,
109 pub submission_types: Option<Vec<SubmissionType>>,
110 pub published: Option<bool>,
111 pub muted: Option<bool>,
112 pub html_url: Option<String>,
113 pub has_overrides: Option<bool>,
114 pub needs_grading_count: Option<u64>,
115 pub position: Option<u64>,
116 pub omit_from_final_grade: Option<bool>,
117 pub locked_for_user: Option<bool>,
118
119 #[serde(skip)]
120 pub(crate) requester: Option<Arc<Requester>>,
121}
122
123impl Assignment {
124 fn course_prefix(&self) -> Result<String> {
125 let course_id = self.course_id.ok_or_else(|| CanvasError::BadRequest {
126 message: "Assignment has no course_id".to_string(),
127 errors: vec![],
128 })?;
129 Ok(format!("courses/{course_id}/assignments/{}", self.id))
130 }
131
132 fn propagate(&self, a: &mut Assignment) {
133 a.requester = self.requester.clone();
134 a.course_id = self.course_id;
135 }
136
137 fn propagate_sub(&self, s: &mut Submission) {
138 s.requester = self.requester.clone();
139 s.course_id = self.course_id;
140 }
141
142 fn propagate_override(&self, o: &mut AssignmentOverride) {
143 o.requester = self.requester.clone();
144 o.course_id = self.course_id;
145 }
146
147 pub async fn edit(&self, params: AssignmentParams) -> Result<Assignment> {
152 let prefix = self.course_prefix()?;
153 let form = wrap_params("assignment", ¶ms);
154 let mut a: Assignment = self.req().put(&prefix, &form).await?;
155 self.propagate(&mut a);
156 Ok(a)
157 }
158
159 pub async fn delete(&self) -> Result<Assignment> {
164 let prefix = self.course_prefix()?;
165 let mut a: Assignment = self.req().delete(&prefix, &[]).await?;
166 self.propagate(&mut a);
167 Ok(a)
168 }
169
170 pub fn get_submissions(&self) -> PageStream<Submission> {
175 let course_id = self.course_id.unwrap_or(0);
176 let assignment_id = self.id;
177 PageStream::new_with_injector(
178 Arc::clone(self.req()),
179 &format!("courses/{course_id}/assignments/{assignment_id}/submissions"),
180 vec![],
181 move |mut s: Submission, req| {
182 s.requester = Some(Arc::clone(&req));
183 s.course_id = Some(course_id);
184 s
185 },
186 )
187 }
188
189 pub async fn get_submission(&self, user_id: u64) -> Result<Submission> {
194 let course_id = self.course_id.ok_or_else(|| CanvasError::BadRequest {
195 message: "Assignment has no course_id".to_string(),
196 errors: vec![],
197 })?;
198 let mut s: Submission = self
199 .req()
200 .get(
201 &format!(
202 "courses/{course_id}/assignments/{}/submissions/{user_id}",
203 self.id
204 ),
205 &[],
206 )
207 .await?;
208 self.propagate_sub(&mut s);
209 Ok(s)
210 }
211
212 pub async fn submit(&self, params: SubmitAssignmentParams) -> Result<Submission> {
217 let course_id = self.course_id.ok_or_else(|| CanvasError::BadRequest {
218 message: "Assignment has no course_id".to_string(),
219 errors: vec![],
220 })?;
221 let form = wrap_params("submission", ¶ms);
222 let mut s: Submission = self
223 .req()
224 .post(
225 &format!("courses/{course_id}/assignments/{}/submissions", self.id),
226 &form,
227 )
228 .await?;
229 self.propagate_sub(&mut s);
230 Ok(s)
231 }
232
233 pub fn get_overrides(&self) -> PageStream<AssignmentOverride> {
238 let course_id = self.course_id.unwrap_or(0);
239 let assignment_id = self.id;
240 PageStream::new_with_injector(
241 Arc::clone(self.req()),
242 &format!("courses/{course_id}/assignments/{assignment_id}/overrides"),
243 vec![],
244 move |mut o: AssignmentOverride, req| {
245 o.requester = Some(Arc::clone(&req));
246 o.course_id = Some(course_id);
247 o
248 },
249 )
250 }
251
252 pub async fn get_override(&self, override_id: u64) -> Result<AssignmentOverride> {
257 let course_id = self.course_id.ok_or_else(|| CanvasError::BadRequest {
258 message: "Assignment has no course_id".to_string(),
259 errors: vec![],
260 })?;
261 let mut o: AssignmentOverride = self
262 .req()
263 .get(
264 &format!(
265 "courses/{course_id}/assignments/{}/overrides/{override_id}",
266 self.id
267 ),
268 &[],
269 )
270 .await?;
271 self.propagate_override(&mut o);
272 Ok(o)
273 }
274
275 pub async fn create_override(
280 &self,
281 params: AssignmentOverrideParams,
282 ) -> Result<AssignmentOverride> {
283 let course_id = self.course_id.ok_or_else(|| CanvasError::BadRequest {
284 message: "Assignment has no course_id".to_string(),
285 errors: vec![],
286 })?;
287 let form = wrap_params("assignment_override", ¶ms);
288 let mut o: AssignmentOverride = self
289 .req()
290 .post(
291 &format!("courses/{course_id}/assignments/{}/overrides", self.id),
292 &form,
293 )
294 .await?;
295 self.propagate_override(&mut o);
296 Ok(o)
297 }
298
299 pub fn get_peer_reviews(&self) -> PageStream<serde_json::Value> {
304 let course_id = self.course_id.unwrap_or(0);
305 PageStream::new(
306 Arc::clone(self.req()),
307 &format!("courses/{course_id}/assignments/{}/peer_reviews", self.id),
308 vec![],
309 )
310 }
311
312 pub fn get_gradeable_students(&self) -> PageStream<User> {
317 let course_id = self.course_id.unwrap_or(0);
318 PageStream::new(
319 Arc::clone(self.req()),
320 &format!(
321 "courses/{course_id}/assignments/{}/gradeable_students",
322 self.id
323 ),
324 vec![],
325 )
326 }
327
328 pub async fn set_extensions(&self, params: &[(String, String)]) -> Result<serde_json::Value> {
333 let course_id = self.course_id.ok_or_else(|| CanvasError::BadRequest {
334 message: "Assignment has no course_id".to_string(),
335 errors: vec![],
336 })?;
337 self.req()
338 .post(
339 &format!("courses/{course_id}/assignments/{}/extensions", self.id),
340 params,
341 )
342 .await
343 }
344
345 pub fn get_grade_change_events(&self) -> PageStream<serde_json::Value> {
350 PageStream::new(
351 Arc::clone(self.req()),
352 &format!("audit/grade_change/assignments/{}", self.id),
353 vec![],
354 )
355 }
356
357 pub fn get_students_selected_for_moderation(&self) -> PageStream<User> {
362 let course_id = self.course_id.unwrap_or(0);
363 PageStream::new(
364 Arc::clone(self.req()),
365 &format!(
366 "courses/{course_id}/assignments/{}/moderated_students",
367 self.id
368 ),
369 vec![],
370 )
371 }
372
373 pub async fn select_students_for_moderation(
378 &self,
379 student_ids: &[u64],
380 ) -> Result<Vec<serde_json::Value>> {
381 let course_id = self.course_id.ok_or_else(|| CanvasError::BadRequest {
382 message: "Assignment has no course_id".to_string(),
383 errors: vec![],
384 })?;
385 let params: Vec<(String, String)> = student_ids
386 .iter()
387 .map(|id| ("student_ids[]".to_string(), id.to_string()))
388 .collect();
389 self.req()
390 .post(
391 &format!(
392 "courses/{course_id}/assignments/{}/moderated_students",
393 self.id
394 ),
395 ¶ms,
396 )
397 .await
398 }
399
400 pub async fn get_provisional_grades_status(
405 &self,
406 student_id: u64,
407 ) -> Result<serde_json::Value> {
408 let course_id = self.course_id.ok_or_else(|| CanvasError::BadRequest {
409 message: "Assignment has no course_id".to_string(),
410 errors: vec![],
411 })?;
412 let params = vec![("student_id".to_string(), student_id.to_string())];
413 self.req()
414 .get(
415 &format!(
416 "courses/{course_id}/assignments/{}/provisional_grades/status",
417 self.id
418 ),
419 ¶ms,
420 )
421 .await
422 }
423
424 pub async fn selected_provisional_grade(
429 &self,
430 provisional_grade_id: u64,
431 ) -> Result<serde_json::Value> {
432 let course_id = self.course_id.ok_or_else(|| CanvasError::BadRequest {
433 message: "Assignment has no course_id".to_string(),
434 errors: vec![],
435 })?;
436 self.req()
437 .put(
438 &format!(
439 "courses/{course_id}/assignments/{}/provisional_grades/{provisional_grade_id}/select",
440 self.id
441 ),
442 &[],
443 )
444 .await
445 }
446
447 pub async fn publish_provisional_grades(&self) -> Result<serde_json::Value> {
452 let course_id = self.course_id.ok_or_else(|| CanvasError::BadRequest {
453 message: "Assignment has no course_id".to_string(),
454 errors: vec![],
455 })?;
456 self.req()
457 .post(
458 &format!(
459 "courses/{course_id}/assignments/{}/provisional_grades/publish",
460 self.id
461 ),
462 &[],
463 )
464 .await
465 }
466
467 pub async fn show_provisional_grades_for_student(
472 &self,
473 anonymous_id: u64,
474 ) -> Result<serde_json::Value> {
475 let course_id = self.course_id.ok_or_else(|| CanvasError::BadRequest {
476 message: "Assignment has no course_id".to_string(),
477 errors: vec![],
478 })?;
479 let params = vec![("anonymous_id".to_string(), anonymous_id.to_string())];
480 self.req()
481 .get(
482 &format!(
483 "courses/{course_id}/assignments/{}/anonymous_provisional_grades/status",
484 self.id
485 ),
486 ¶ms,
487 )
488 .await
489 }
490
491 pub async fn submissions_bulk_update(&self, params: &[(String, String)]) -> Result<Progress> {
496 let course_id = self.course_id.ok_or_else(|| CanvasError::BadRequest {
497 message: "Assignment has no course_id".to_string(),
498 errors: vec![],
499 })?;
500 let mut p: Progress = self
501 .req()
502 .post(
503 &format!(
504 "courses/{course_id}/assignments/{}/submissions/update_grades",
505 self.id
506 ),
507 params,
508 )
509 .await?;
510 p.requester = self.requester.clone();
511 Ok(p)
512 }
513}
514
515#[derive(Debug, Clone, Deserialize, Serialize, canvas_lms_api_derive::CanvasResource)]
517pub struct AssignmentGroup {
518 pub id: u64,
519 pub name: Option<String>,
520 pub group_weight: Option<f64>,
521 pub position: Option<u64>,
522 pub rules: Option<serde_json::Value>,
523 pub assignments: Option<Vec<serde_json::Value>>,
524
525 #[serde(skip)]
526 pub(crate) requester: Option<Arc<Requester>>,
527 #[serde(skip)]
528 pub course_id: Option<u64>,
529}
530
531impl AssignmentGroup {
532 pub async fn edit(&self, params: AssignmentGroupParams) -> Result<AssignmentGroup> {
537 let course_id = self.course_id.ok_or_else(|| CanvasError::BadRequest {
538 message: "AssignmentGroup has no course_id".to_string(),
539 errors: vec![],
540 })?;
541 let form = wrap_params("assignment_group", ¶ms);
542 let mut g: AssignmentGroup = self
543 .req()
544 .put(
545 &format!("courses/{course_id}/assignment_groups/{}", self.id),
546 &form,
547 )
548 .await?;
549 g.requester = self.requester.clone();
550 g.course_id = self.course_id;
551 Ok(g)
552 }
553
554 pub async fn delete(&self) -> Result<AssignmentGroup> {
559 let course_id = self.course_id.ok_or_else(|| CanvasError::BadRequest {
560 message: "AssignmentGroup has no course_id".to_string(),
561 errors: vec![],
562 })?;
563 let mut g: AssignmentGroup = self
564 .req()
565 .delete(
566 &format!("courses/{course_id}/assignment_groups/{}", self.id),
567 &[],
568 )
569 .await?;
570 g.requester = self.requester.clone();
571 g.course_id = self.course_id;
572 Ok(g)
573 }
574}
575
576#[derive(Debug, Clone, Deserialize, Serialize, canvas_lms_api_derive::CanvasResource)]
578pub struct AssignmentOverride {
579 pub id: u64,
580 pub assignment_id: Option<u64>,
581 pub student_ids: Option<Vec<u64>>,
582 pub group_id: Option<u64>,
583 pub course_section_id: Option<u64>,
584 pub title: Option<String>,
585 pub due_at: Option<DateTime<Utc>>,
586 pub unlock_at: Option<DateTime<Utc>>,
587 pub lock_at: Option<DateTime<Utc>>,
588
589 #[serde(skip)]
590 pub(crate) requester: Option<Arc<Requester>>,
591 #[serde(skip)]
592 pub course_id: Option<u64>,
593}
594
595impl AssignmentOverride {
596 fn prefix(&self) -> Result<String> {
597 let course_id = self.course_id.ok_or_else(|| CanvasError::BadRequest {
598 message: "AssignmentOverride has no course_id".to_string(),
599 errors: vec![],
600 })?;
601 let assignment_id = self.assignment_id.ok_or_else(|| CanvasError::BadRequest {
602 message: "AssignmentOverride has no assignment_id".to_string(),
603 errors: vec![],
604 })?;
605 Ok(format!(
606 "courses/{course_id}/assignments/{assignment_id}/overrides/{}",
607 self.id
608 ))
609 }
610
611 fn propagate(&self, o: &mut AssignmentOverride) {
612 o.requester = self.requester.clone();
613 o.course_id = self.course_id;
614 }
615
616 pub async fn edit(&self, params: AssignmentOverrideParams) -> Result<AssignmentOverride> {
621 let prefix = self.prefix()?;
622 let form = wrap_params("assignment_override", ¶ms);
623 let mut o: AssignmentOverride = self.req().put(&prefix, &form).await?;
624 self.propagate(&mut o);
625 Ok(o)
626 }
627
628 pub async fn delete(&self) -> Result<AssignmentOverride> {
633 let prefix = self.prefix()?;
634 let mut o: AssignmentOverride = self.req().delete(&prefix, &[]).await?;
635 self.propagate(&mut o);
636 Ok(o)
637 }
638}