canvas_lms_api/resources/
module.rs1use crate::{
2 error::{CanvasError, Result},
3 http::Requester,
4 pagination::PageStream,
5 params::wrap_params,
6};
7use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9use std::sync::Arc;
10
11#[derive(Debug, Default, Clone, Serialize)]
13pub struct UpdateModuleParams {
14 #[serde(skip_serializing_if = "Option::is_none")]
15 pub name: Option<String>,
16 #[serde(skip_serializing_if = "Option::is_none")]
17 pub position: Option<u64>,
18 #[serde(skip_serializing_if = "Option::is_none")]
19 pub unlock_at: Option<DateTime<Utc>>,
20 #[serde(skip_serializing_if = "Option::is_none")]
21 pub require_sequential_progress: Option<bool>,
22 #[serde(skip_serializing_if = "Option::is_none")]
23 pub publish_final_grade: Option<bool>,
24 #[serde(skip_serializing_if = "Option::is_none")]
25 pub published: Option<bool>,
26}
27
28pub type CreateModuleParams = UpdateModuleParams;
30
31#[derive(Debug, Clone, Serialize)]
33pub struct CreateModuleItemParams {
34 #[serde(rename = "type")]
35 pub item_type: String,
36 #[serde(skip_serializing_if = "Option::is_none")]
37 pub content_id: Option<u64>,
38 #[serde(skip_serializing_if = "Option::is_none")]
39 pub position: Option<u64>,
40 #[serde(skip_serializing_if = "Option::is_none")]
41 pub indent: Option<u64>,
42 #[serde(skip_serializing_if = "Option::is_none")]
43 pub page_url: Option<String>,
44 #[serde(skip_serializing_if = "Option::is_none")]
45 pub external_url: Option<String>,
46 #[serde(skip_serializing_if = "Option::is_none")]
47 pub new_tab: Option<bool>,
48 #[serde(skip_serializing_if = "Option::is_none")]
49 pub published: Option<bool>,
50}
51
52#[derive(Debug, Default, Clone, Serialize)]
54pub struct UpdateModuleItemParams {
55 #[serde(skip_serializing_if = "Option::is_none")]
56 pub title: Option<String>,
57 #[serde(skip_serializing_if = "Option::is_none")]
58 pub position: Option<u64>,
59 #[serde(skip_serializing_if = "Option::is_none")]
60 pub indent: Option<u64>,
61 #[serde(skip_serializing_if = "Option::is_none")]
62 pub external_url: Option<String>,
63 #[serde(skip_serializing_if = "Option::is_none")]
64 pub new_tab: Option<bool>,
65 #[serde(skip_serializing_if = "Option::is_none")]
66 pub published: Option<bool>,
67}
68
69#[derive(Debug, Clone, Deserialize, Serialize, canvas_lms_api_derive::CanvasResource)]
71pub struct Module {
72 pub id: u64,
73 pub course_id: Option<u64>,
74 pub name: Option<String>,
75 pub position: Option<u64>,
76 pub unlock_at: Option<DateTime<Utc>>,
77 pub require_sequential_progress: Option<bool>,
78 pub prerequisite_module_ids: Option<Vec<u64>>,
79 pub items_count: Option<u64>,
80 pub items_url: Option<String>,
81 pub state: Option<String>,
82 pub completed_at: Option<DateTime<Utc>>,
83 pub publish_final_grade: Option<bool>,
84 pub published: Option<bool>,
85
86 #[serde(skip)]
87 pub(crate) requester: Option<Arc<Requester>>,
88}
89
90impl Module {
91 fn course_id_or_err(&self) -> Result<u64> {
92 self.course_id.ok_or_else(|| CanvasError::BadRequest {
93 message: "Module does not have a course_id".to_string(),
94 errors: vec![],
95 })
96 }
97
98 fn propagate(&self, m: &mut Module) {
99 m.requester = self.requester.clone();
100 m.course_id = self.course_id;
101 }
102
103 fn propagate_item(&self, item: &mut ModuleItem) {
104 item.requester = self.requester.clone();
105 item.course_id = self.course_id;
106 item.module_id = Some(self.id);
107 }
108
109 pub async fn edit(&self, params: UpdateModuleParams) -> Result<Module> {
114 let course_id = self.course_id_or_err()?;
115 let form = wrap_params("module", ¶ms);
116 let mut m: Module = self
117 .req()
118 .put(&format!("courses/{course_id}/modules/{}", self.id), &form)
119 .await?;
120 self.propagate(&mut m);
121 Ok(m)
122 }
123
124 pub async fn delete(&self) -> Result<Module> {
129 let course_id = self.course_id_or_err()?;
130 let mut m: Module = self
131 .req()
132 .delete(&format!("courses/{course_id}/modules/{}", self.id), &[])
133 .await?;
134 self.propagate(&mut m);
135 Ok(m)
136 }
137
138 pub async fn relock(&self) -> Result<Module> {
143 let course_id = self.course_id_or_err()?;
144 let mut m: Module = self
145 .req()
146 .put(
147 &format!("courses/{course_id}/modules/{}/relock", self.id),
148 &[],
149 )
150 .await?;
151 self.propagate(&mut m);
152 Ok(m)
153 }
154
155 pub fn get_module_items(&self) -> PageStream<ModuleItem> {
160 let course_id = self.course_id.unwrap_or(0);
161 let module_id = self.id;
162 PageStream::new_with_injector(
163 Arc::clone(self.req()),
164 &format!("courses/{course_id}/modules/{module_id}/items"),
165 vec![],
166 move |mut item: ModuleItem, req| {
167 item.requester = Some(Arc::clone(&req));
168 item.course_id = Some(course_id);
169 item.module_id = Some(module_id);
170 item
171 },
172 )
173 }
174
175 pub async fn get_module_item(&self, item_id: u64) -> Result<ModuleItem> {
180 let course_id = self.course_id_or_err()?;
181 let mut item: ModuleItem = self
182 .req()
183 .get(
184 &format!("courses/{course_id}/modules/{}/items/{item_id}", self.id),
185 &[],
186 )
187 .await?;
188 self.propagate_item(&mut item);
189 Ok(item)
190 }
191
192 pub async fn create_module_item(&self, params: CreateModuleItemParams) -> Result<ModuleItem> {
197 let course_id = self.course_id_or_err()?;
198 if params.item_type != "SubHeader" && params.content_id.is_none() {
200 return Err(CanvasError::BadRequest {
201 message: "content_id is required for this module item type".to_string(),
202 errors: vec![],
203 });
204 }
205 let form = wrap_params("module_item", ¶ms);
206 let mut item: ModuleItem = self
207 .req()
208 .post(
209 &format!("courses/{course_id}/modules/{}/items", self.id),
210 &form,
211 )
212 .await?;
213 self.propagate_item(&mut item);
214 Ok(item)
215 }
216}
217
218#[derive(Debug, Clone, Deserialize, Serialize, canvas_lms_api_derive::CanvasResource)]
220pub struct ModuleItem {
221 pub id: u64,
222 pub module_id: Option<u64>,
223 pub position: Option<u64>,
224 pub title: Option<String>,
225 pub indent: Option<u64>,
226 #[serde(rename = "type")]
227 pub item_type: Option<String>,
228 pub content_id: Option<u64>,
229 pub html_url: Option<String>,
230 pub url: Option<String>,
231 pub page_url: Option<String>,
232 pub external_url: Option<String>,
233 pub completion_requirement: Option<CompletionRequirement>,
234 pub published: Option<bool>,
235
236 #[serde(skip)]
237 pub(crate) requester: Option<Arc<Requester>>,
238 #[serde(skip)]
239 pub course_id: Option<u64>,
240}
241
242impl ModuleItem {
243 fn course_module_prefix(&self) -> Result<String> {
244 let course_id = self.course_id.ok_or_else(|| CanvasError::BadRequest {
245 message: "ModuleItem does not have a course_id".to_string(),
246 errors: vec![],
247 })?;
248 let module_id = self.module_id.ok_or_else(|| CanvasError::BadRequest {
249 message: "ModuleItem does not have a module_id".to_string(),
250 errors: vec![],
251 })?;
252 Ok(format!("courses/{course_id}/modules/{module_id}"))
253 }
254
255 fn propagate(&self, item: &mut ModuleItem) {
256 item.requester = self.requester.clone();
257 item.course_id = self.course_id;
258 item.module_id = self.module_id;
259 }
260
261 pub async fn edit(&self, params: UpdateModuleItemParams) -> Result<ModuleItem> {
266 let prefix = self.course_module_prefix()?;
267 let form = wrap_params("module_item", ¶ms);
268 let mut item: ModuleItem = self
269 .req()
270 .put(&format!("{prefix}/items/{}", self.id), &form)
271 .await?;
272 self.propagate(&mut item);
273 Ok(item)
274 }
275
276 pub async fn delete(&self) -> Result<ModuleItem> {
281 let prefix = self.course_module_prefix()?;
282 let mut item: ModuleItem = self
283 .req()
284 .delete(&format!("{prefix}/items/{}", self.id), &[])
285 .await?;
286 self.propagate(&mut item);
287 Ok(item)
288 }
289
290 pub async fn complete(&self) -> Result<ModuleItem> {
295 let prefix = self.course_module_prefix()?;
296 let mut item: ModuleItem = self
297 .req()
298 .put(&format!("{prefix}/items/{}/done", self.id), &[])
299 .await?;
300 self.propagate(&mut item);
301 Ok(item)
302 }
303
304 pub async fn uncomplete(&self) -> Result<ModuleItem> {
309 let prefix = self.course_module_prefix()?;
310 let mut item: ModuleItem = self
311 .req()
312 .delete(&format!("{prefix}/items/{}/done", self.id), &[])
313 .await?;
314 self.propagate(&mut item);
315 Ok(item)
316 }
317}
318
319#[derive(Debug, Clone, Deserialize, Serialize)]
321pub struct CompletionRequirement {
322 #[serde(rename = "type")]
323 pub requirement_type: Option<String>,
324 pub min_score: Option<f64>,
325 pub completed: Option<bool>,
326}