Skip to main content

canvas_lms_api/resources/
user.rs

1use crate::{
2    error::Result,
3    http::Requester,
4    pagination::PageStream,
5    params::wrap_params,
6    resources::{
7        communication_channel::CommunicationChannel,
8        content_migration::{ContentMigration, Migrator},
9        course::Course,
10        enrollment::Enrollment,
11        file::File,
12        folder::Folder,
13    },
14};
15
16/// Parameters for editing a Canvas user.
17#[derive(Debug, Default, Clone, serde::Serialize)]
18pub struct EditUserParams {
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub name: Option<String>,
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub short_name: Option<String>,
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub sortable_name: Option<String>,
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub time_zone: Option<String>,
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub email: Option<String>,
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub locale: Option<String>,
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub avatar_url: Option<String>,
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub bio: Option<String>,
35}
36use chrono::{DateTime, Utc};
37use serde::{Deserialize, Serialize};
38use std::sync::Arc;
39
40/// A Canvas user.
41#[derive(Debug, Clone, Deserialize, Serialize, canvas_lms_api_derive::CanvasResource)]
42pub struct User {
43    pub id: u64,
44    pub name: Option<String>,
45    pub sortable_name: Option<String>,
46    pub short_name: Option<String>,
47    pub sis_user_id: Option<String>,
48    pub login_id: Option<String>,
49    pub email: Option<String>,
50    pub avatar_url: Option<String>,
51    pub locale: Option<String>,
52    pub last_login: Option<DateTime<Utc>>,
53    pub time_zone: Option<String>,
54    pub bio: Option<String>,
55
56    #[serde(skip)]
57    pub(crate) requester: Option<Arc<Requester>>,
58}
59
60impl User {
61    /// Stream all courses for this user.
62    ///
63    /// # Canvas API
64    /// `GET /api/v1/users/:id/courses`
65    pub fn get_courses(&self) -> PageStream<Course> {
66        PageStream::new(
67            Arc::clone(self.req()),
68            &format!("users/{}/courses", self.id),
69            vec![],
70        )
71    }
72
73    /// Stream all enrollments for this user.
74    ///
75    /// # Canvas API
76    /// `GET /api/v1/users/:id/enrollments`
77    pub fn get_enrollments(&self) -> PageStream<Enrollment> {
78        PageStream::new(
79            Arc::clone(self.req()),
80            &format!("users/{}/enrollments", self.id),
81            vec![],
82        )
83    }
84
85    /// Stream all communication channels for this user.
86    ///
87    /// # Canvas API
88    /// `GET /api/v1/users/:id/communication_channels`
89    pub fn get_communication_channels(&self) -> PageStream<CommunicationChannel> {
90        let user_id = self.id;
91        PageStream::new_with_injector(
92            Arc::clone(self.req()),
93            &format!("users/{user_id}/communication_channels"),
94            vec![],
95            |mut c: CommunicationChannel, req| {
96                c.requester = Some(Arc::clone(&req));
97                c
98            },
99        )
100    }
101
102    fn propagate(&self, u: &mut User) {
103        u.requester = self.requester.clone();
104    }
105
106    /// Edit this user's profile.
107    ///
108    /// # Canvas API
109    /// `PUT /api/v1/users/:id`
110    pub async fn edit(&self, params: EditUserParams) -> Result<User> {
111        let form = wrap_params("user", &params);
112        let mut u: User = self.req().put(&format!("users/{}", self.id), &form).await?;
113        self.propagate(&mut u);
114        Ok(u)
115    }
116
117    /// Get this user's profile.
118    ///
119    /// # Canvas API
120    /// `GET /api/v1/users/:id/profile`
121    pub async fn get_profile(&self) -> Result<serde_json::Value> {
122        self.req()
123            .get(&format!("users/{}/profile", self.id), &[])
124            .await
125    }
126
127    /// Terminate all sessions for this user.
128    ///
129    /// # Canvas API
130    /// `DELETE /api/v1/users/:id/sessions`
131    pub async fn terminate_sessions(&self) -> Result<()> {
132        self.req()
133            .delete_void(&format!("users/{}/sessions", self.id))
134            .await
135    }
136
137    /// Merge this user into another user.
138    ///
139    /// # Canvas API
140    /// `PUT /api/v1/users/:id/merge_into/:destination_user_id`
141    pub async fn merge_into(&self, destination_user_id: u64) -> Result<User> {
142        let mut u: User = self
143            .req()
144            .put(
145                &format!("users/{}/merge_into/{destination_user_id}", self.id),
146                &[],
147            )
148            .await?;
149        self.propagate(&mut u);
150        Ok(u)
151    }
152
153    /// Stream all avatar options for this user.
154    ///
155    /// # Canvas API
156    /// `GET /api/v1/users/:id/avatars`
157    pub fn get_avatars(&self) -> PageStream<serde_json::Value> {
158        PageStream::new(
159            Arc::clone(self.req()),
160            &format!("users/{}/avatars", self.id),
161            vec![],
162        )
163    }
164
165    /// Stream page views for this user.
166    ///
167    /// # Canvas API
168    /// `GET /api/v1/users/:id/page_views`
169    pub fn get_page_views(&self) -> PageStream<serde_json::Value> {
170        PageStream::new(
171            Arc::clone(self.req()),
172            &format!("users/{}/page_views", self.id),
173            vec![],
174        )
175    }
176
177    /// Stream all observees for this user.
178    ///
179    /// # Canvas API
180    /// `GET /api/v1/users/:id/observees`
181    pub fn get_observees(&self) -> PageStream<User> {
182        PageStream::new(
183            Arc::clone(self.req()),
184            &format!("users/{}/observees", self.id),
185            vec![],
186        )
187    }
188
189    /// Add an observee by user ID.
190    ///
191    /// # Canvas API
192    /// `PUT /api/v1/users/:id/observees/:observee_id`
193    pub async fn add_observee(&self, observee_id: u64) -> Result<User> {
194        let mut u: User = self
195            .req()
196            .put(&format!("users/{}/observees/{observee_id}", self.id), &[])
197            .await?;
198        self.propagate(&mut u);
199        Ok(u)
200    }
201
202    /// Remove an observee.
203    ///
204    /// # Canvas API
205    /// `DELETE /api/v1/users/:id/observees/:observee_id`
206    pub async fn remove_observee(&self, observee_id: u64) -> Result<User> {
207        let mut u: User = self
208            .req()
209            .delete(&format!("users/{}/observees/{observee_id}", self.id), &[])
210            .await?;
211        self.propagate(&mut u);
212        Ok(u)
213    }
214
215    /// Fetch a single observee.
216    ///
217    /// # Canvas API
218    /// `GET /api/v1/users/:id/observees/:observee_id`
219    pub async fn show_observee(&self, observee_id: u64) -> Result<User> {
220        let mut u: User = self
221            .req()
222            .get(&format!("users/{}/observees/{observee_id}", self.id), &[])
223            .await?;
224        self.propagate(&mut u);
225        Ok(u)
226    }
227
228    /// Stream all observers of this user.
229    ///
230    /// # Canvas API
231    /// `GET /api/v1/users/:id/observers`
232    pub fn get_observers(&self) -> PageStream<User> {
233        PageStream::new(
234            Arc::clone(self.req()),
235            &format!("users/{}/observers", self.id),
236            vec![],
237        )
238    }
239
240    /// Get all custom colors for this user.
241    ///
242    /// # Canvas API
243    /// `GET /api/v1/users/:id/colors`
244    pub async fn get_colors(&self) -> Result<serde_json::Value> {
245        self.req()
246            .get(&format!("users/{}/colors", self.id), &[])
247            .await
248    }
249
250    /// Get the color for a specific asset.
251    ///
252    /// # Canvas API
253    /// `GET /api/v1/users/:id/colors/:asset_string`
254    pub async fn get_color(&self, asset_string: &str) -> Result<serde_json::Value> {
255        self.req()
256            .get(&format!("users/{}/colors/{asset_string}", self.id), &[])
257            .await
258    }
259
260    /// Update the color for a specific asset.
261    ///
262    /// # Canvas API
263    /// `PUT /api/v1/users/:id/colors/:asset_string`
264    pub async fn update_color(
265        &self,
266        asset_string: &str,
267        hexcode: &str,
268    ) -> Result<serde_json::Value> {
269        let params = vec![("hexcode".to_string(), hexcode.to_string())];
270        self.req()
271            .put(&format!("users/{}/colors/{asset_string}", self.id), &params)
272            .await
273    }
274
275    /// Stream missing submissions for this user.
276    ///
277    /// # Canvas API
278    /// `GET /api/v1/users/:id/missing_submissions`
279    pub fn get_missing_submissions(&self) -> PageStream<serde_json::Value> {
280        PageStream::new(
281            Arc::clone(self.req()),
282            &format!("users/{}/missing_submissions", self.id),
283            vec![],
284        )
285    }
286
287    /// Stream all files for this user.
288    ///
289    /// # Canvas API
290    /// `GET /api/v1/users/:id/files`
291    pub fn get_files(&self) -> PageStream<File> {
292        PageStream::new_with_injector(
293            Arc::clone(self.req()),
294            &format!("users/{}/files", self.id),
295            vec![],
296            move |mut f: File, req| {
297                f.requester = Some(Arc::clone(&req));
298                f
299            },
300        )
301    }
302
303    /// Stream all folders for this user.
304    ///
305    /// # Canvas API
306    /// `GET /api/v1/users/:id/folders`
307    pub fn get_folders(&self) -> PageStream<Folder> {
308        PageStream::new_with_injector(
309            Arc::clone(self.req()),
310            &format!("users/{}/folders", self.id),
311            vec![],
312            move |mut f: Folder, req| {
313                f.requester = Some(Arc::clone(&req));
314                f
315            },
316        )
317    }
318
319    /// Create a folder for this user.
320    ///
321    /// # Canvas API
322    /// `POST /api/v1/users/:id/folders`
323    pub async fn create_folder(&self, name: &str) -> Result<Folder> {
324        let params = vec![("name".to_string(), name.to_string())];
325        let mut f: Folder = self
326            .req()
327            .post(&format!("users/{}/folders", self.id), &params)
328            .await?;
329        f.requester = self.requester.clone();
330        Ok(f)
331    }
332
333    /// Get the file storage quota for this user.
334    ///
335    /// # Canvas API
336    /// `GET /api/v1/users/:id/files/quota`
337    pub async fn get_file_quota(&self) -> Result<serde_json::Value> {
338        self.req()
339            .get(&format!("users/{}/files/quota", self.id), &[])
340            .await
341    }
342
343    /// Stream login information for this user.
344    ///
345    /// # Canvas API
346    /// `GET /api/v1/users/:id/logins`
347    pub fn get_user_logins(&self) -> PageStream<serde_json::Value> {
348        PageStream::new(
349            Arc::clone(self.req()),
350            &format!("users/{}/logins", self.id),
351            vec![],
352        )
353    }
354
355    /// Get profile settings for this user.
356    ///
357    /// # Canvas API
358    /// `GET /api/v1/users/:id/settings`
359    pub async fn get_settings(&self) -> Result<serde_json::Value> {
360        self.req()
361            .get(&format!("users/{}/settings", self.id), &[])
362            .await
363    }
364
365    /// Update profile settings for this user.
366    ///
367    /// # Canvas API
368    /// `PUT /api/v1/users/:id/settings`
369    pub async fn update_settings(&self, params: &[(String, String)]) -> Result<serde_json::Value> {
370        self.req()
371            .put(&format!("users/{}/settings", self.id), params)
372            .await
373    }
374
375    /// Create a communication channel for this user.
376    ///
377    /// `address` is the email address, phone number, etc.
378    /// `channel_type` is `"email"`, `"sms"`, `"push"`, etc.
379    ///
380    /// # Canvas API
381    /// `POST /api/v1/users/:id/communication_channels`
382    pub async fn create_communication_channel(
383        &self,
384        address: &str,
385        channel_type: &str,
386    ) -> Result<CommunicationChannel> {
387        let params = vec![
388            (
389                "communication_channel[address]".to_string(),
390                address.to_string(),
391            ),
392            (
393                "communication_channel[type]".to_string(),
394                channel_type.to_string(),
395            ),
396        ];
397        let mut channel: CommunicationChannel = self
398            .req()
399            .post(
400                &format!("users/{}/communication_channels", self.id),
401                &params,
402            )
403            .await?;
404        channel.requester = self.requester.clone();
405        Ok(channel)
406    }
407
408    /// Create an observer pairing code for this user.
409    ///
410    /// # Canvas API
411    /// `POST /api/v1/users/:id/observer_pairing_codes`
412    pub async fn create_pairing_code(&self) -> Result<serde_json::Value> {
413        self.req()
414            .post(&format!("users/{}/observer_pairing_codes", self.id), &[])
415            .await
416    }
417
418    /// Stream authentication events (login/logout log) for this user.
419    ///
420    /// # Canvas API
421    /// `GET /api/v1/audit/authentication/users/:id`
422    pub fn get_authentication_events(&self) -> PageStream<serde_json::Value> {
423        PageStream::new(
424            Arc::clone(self.req()),
425            &format!("audit/authentication/users/{}", self.id),
426            vec![],
427        )
428    }
429
430    /// Stream all feature flags for this user.
431    ///
432    /// # Canvas API
433    /// `GET /api/v1/users/:id/features`
434    pub fn get_features(&self) -> PageStream<serde_json::Value> {
435        PageStream::new(
436            Arc::clone(self.req()),
437            &format!("users/{}/features", self.id),
438            vec![],
439        )
440    }
441
442    /// List enabled features for this user.
443    ///
444    /// # Canvas API
445    /// `GET /api/v1/users/:id/features/enabled`
446    pub async fn get_enabled_features(&self) -> Result<Vec<String>> {
447        self.req()
448            .get(&format!("users/{}/features/enabled", self.id), &[])
449            .await
450    }
451
452    /// Export content for this user.
453    ///
454    /// # Canvas API
455    /// `POST /api/v1/users/:id/content_exports`
456    pub async fn export_content(&self, export_type: &str) -> Result<serde_json::Value> {
457        let params = vec![("export_type".to_string(), export_type.to_string())];
458        self.req()
459            .post(&format!("users/{}/content_exports", self.id), &params)
460            .await
461    }
462
463    /// Stream content exports for this user.
464    ///
465    /// # Canvas API
466    /// `GET /api/v1/users/:id/content_exports`
467    pub fn get_content_exports(&self) -> PageStream<serde_json::Value> {
468        PageStream::new(
469            Arc::clone(self.req()),
470            &format!("users/{}/content_exports", self.id),
471            vec![],
472        )
473    }
474
475    /// Stream ePortfolios for this user.
476    ///
477    /// # Canvas API
478    /// `GET /api/v1/users/:id/eportfolios`
479    pub fn get_eportfolios(&self) -> PageStream<serde_json::Value> {
480        PageStream::new(
481            Arc::clone(self.req()),
482            &format!("users/{}/eportfolios", self.id),
483            vec![],
484        )
485    }
486
487    /// Stream open poll sessions for this user.
488    ///
489    /// # Canvas API
490    /// `GET /api/v1/users/:id/poll_sessions/opened`
491    pub fn get_open_poll_sessions(&self) -> PageStream<serde_json::Value> {
492        PageStream::new(
493            Arc::clone(self.req()),
494            &format!("users/{}/poll_sessions/opened", self.id),
495            vec![],
496        )
497    }
498
499    /// Stream closed poll sessions for this user.
500    ///
501    /// # Canvas API
502    /// `GET /api/v1/users/:id/poll_sessions/closed`
503    pub fn get_closed_poll_sessions(&self) -> PageStream<serde_json::Value> {
504        PageStream::new(
505            Arc::clone(self.req()),
506            &format!("users/{}/poll_sessions/closed", self.id),
507            vec![],
508        )
509    }
510
511    /// Fetch a single file from this user's files.
512    ///
513    /// # Canvas API
514    /// `GET /api/v1/users/:id/files/:file_id`
515    pub async fn get_file(&self, file_id: u64) -> Result<File> {
516        let mut f: File = self
517            .req()
518            .get(&format!("users/{}/files/{file_id}", self.id), &[])
519            .await?;
520        f.requester = self.requester.clone();
521        Ok(f)
522    }
523
524    /// Fetch a single folder from this user's folders.
525    ///
526    /// # Canvas API
527    /// `GET /api/v1/users/:id/folders/:folder_id`
528    pub async fn get_folder(&self, folder_id: u64) -> Result<Folder> {
529        let mut f: Folder = self
530            .req()
531            .get(&format!("users/{}/folders/{folder_id}", self.id), &[])
532            .await?;
533        f.requester = self.requester.clone();
534        Ok(f)
535    }
536
537    /// Resolve a folder path under this user's files root.
538    ///
539    /// Pass `None` to get the root folder. Pass a path like
540    /// `"Folder_Level_1/Folder_Level_2"` to walk the tree.
541    ///
542    /// # Canvas API
543    /// `GET /api/v1/users/:id/folders/by_path/*full_path`
544    pub fn resolve_path(&self, full_path: Option<&str>) -> PageStream<Folder> {
545        let endpoint = match full_path {
546            Some(p) if !p.is_empty() => {
547                format!("users/{}/folders/by_path/{p}", self.id)
548            }
549            _ => format!("users/{}/folders/by_path", self.id),
550        };
551        PageStream::new_with_injector(
552            Arc::clone(self.req()),
553            &endpoint,
554            vec![],
555            |mut f: Folder, req| {
556                f.requester = Some(Arc::clone(&req));
557                f
558            },
559        )
560    }
561
562    /// Stream grade-change audit events where this user is a grader.
563    ///
564    /// # Canvas API
565    /// `GET /api/v1/audit/grade_change/graders/:id`
566    pub fn get_grade_change_events_for_grader(&self) -> PageStream<serde_json::Value> {
567        PageStream::new(
568            Arc::clone(self.req()),
569            &format!("audit/grade_change/graders/{}", self.id),
570            vec![],
571        )
572    }
573
574    /// Stream grade-change audit events where this user is a student.
575    ///
576    /// # Canvas API
577    /// `GET /api/v1/audit/grade_change/students/:id`
578    pub fn get_grade_change_events_for_student(&self) -> PageStream<serde_json::Value> {
579        PageStream::new(
580            Arc::clone(self.req()),
581            &format!("audit/grade_change/students/{}", self.id),
582            vec![],
583        )
584    }
585
586    /// Fetch a single content migration for this user.
587    ///
588    /// # Canvas API
589    /// `GET /api/v1/users/:id/content_migrations/:migration_id`
590    pub async fn get_content_migration(&self, migration_id: u64) -> Result<ContentMigration> {
591        let mut m: ContentMigration = self
592            .req()
593            .get(
594                &format!("users/{}/content_migrations/{migration_id}", self.id),
595                &[],
596            )
597            .await?;
598        m.requester = self.requester.clone();
599        Ok(m)
600    }
601
602    /// Stream all content migrations for this user.
603    ///
604    /// # Canvas API
605    /// `GET /api/v1/users/:id/content_migrations`
606    pub fn get_content_migrations(&self) -> PageStream<ContentMigration> {
607        let user_id = self.id;
608        PageStream::new_with_injector(
609            Arc::clone(self.req()),
610            &format!("users/{user_id}/content_migrations"),
611            vec![],
612            move |mut m: ContentMigration, req| {
613                m.requester = Some(Arc::clone(&req));
614                m.user_id = Some(user_id);
615                m
616            },
617        )
618    }
619
620    /// Create a content migration for this user.
621    ///
622    /// # Canvas API
623    /// `POST /api/v1/users/:id/content_migrations`
624    pub async fn create_content_migration(&self, migration_type: &str) -> Result<ContentMigration> {
625        let params = vec![("migration_type".to_string(), migration_type.to_string())];
626        let mut m: ContentMigration = self
627            .req()
628            .post(&format!("users/{}/content_migrations", self.id), &params)
629            .await?;
630        m.requester = self.requester.clone();
631        m.user_id = Some(self.id);
632        Ok(m)
633    }
634
635    /// Stream available migration systems (migrators) for this user.
636    ///
637    /// # Canvas API
638    /// `GET /api/v1/users/:id/content_migrations/migrators`
639    pub fn get_migration_systems(&self) -> PageStream<Migrator> {
640        PageStream::new(
641            Arc::clone(self.req()),
642            &format!("users/{}/content_migrations/migrators", self.id),
643            vec![],
644        )
645    }
646
647    /// Fetch a specific feature flag for this user.
648    ///
649    /// # Canvas API
650    /// `GET /api/v1/users/:id/features/flags/:feature`
651    pub async fn get_feature_flag(&self, feature: &str) -> Result<serde_json::Value> {
652        self.req()
653            .get(&format!("users/{}/features/flags/{feature}", self.id), &[])
654            .await
655    }
656
657    /// Upload a file to this user's file storage.
658    ///
659    /// Canvas uses a two-step upload: first POSTing metadata to obtain an upload URL,
660    /// then POSTing the file as multipart form data to that URL.
661    ///
662    /// # Canvas API
663    /// `POST /api/v1/users/:id/files`
664    pub async fn upload_file(
665        &self,
666        request: crate::upload::UploadRequest,
667        data: Vec<u8>,
668    ) -> Result<File> {
669        crate::upload::initiate_and_upload(
670            self.req(),
671            &format!("users/{}/files", self.id),
672            request,
673            data,
674        )
675        .await
676    }
677
678    /// Add an observee via credentials (same endpoint as add_observee but without observee_id).
679    ///
680    /// # Canvas API
681    /// `POST /api/v1/users/:id/observees`
682    pub async fn add_observee_with_credentials(&self, params: &[(String, String)]) -> Result<User> {
683        let mut u: User = self
684            .req()
685            .post(&format!("users/{}/observees", self.id), params)
686            .await?;
687        u.requester = self.requester.clone();
688        Ok(u)
689    }
690
691    /// Stream calendar events for this user.
692    ///
693    /// # Canvas API
694    /// `GET /api/v1/users/:id/calendar_events`
695    pub fn get_calendar_events(&self) -> PageStream<serde_json::Value> {
696        PageStream::new(
697            Arc::clone(self.req()),
698            &format!("users/{}/calendar_events", self.id),
699            vec![],
700        )
701    }
702
703    /// Fetch a single content export by ID.
704    ///
705    /// # Canvas API
706    /// `GET /api/v1/users/:id/content_exports/:id`
707    pub async fn get_content_export(&self, export_id: u64) -> Result<serde_json::Value> {
708        self.req()
709            .get(
710                &format!("users/{}/content_exports/{export_id}", self.id),
711                &[],
712            )
713            .await
714    }
715
716    /// Stream available content licenses for this user.
717    ///
718    /// # Canvas API
719    /// `GET /api/v1/users/:id/content_licenses`
720    pub fn get_licenses(&self) -> PageStream<serde_json::Value> {
721        PageStream::new(
722            Arc::clone(self.req()),
723            &format!("users/{}/content_licenses", self.id),
724            vec![],
725        )
726    }
727
728    /// Set usage rights on files for this user.
729    ///
730    /// # Canvas API
731    /// `PUT /api/v1/users/:id/usage_rights`
732    pub async fn set_usage_rights(&self, params: &[(String, String)]) -> Result<serde_json::Value> {
733        self.req()
734            .put(&format!("users/{}/usage_rights", self.id), params)
735            .await
736    }
737
738    /// Remove usage rights from files for this user.
739    ///
740    /// # Canvas API
741    /// `DELETE /api/v1/users/:id/usage_rights`
742    pub async fn remove_usage_rights(
743        &self,
744        params: &[(String, String)],
745    ) -> Result<serde_json::Value> {
746        self.req()
747            .delete(&format!("users/{}/usage_rights", self.id), params)
748            .await
749    }
750}
751
752/// The currently authenticated user (extends User with additional fields).
753#[derive(Debug, Clone, Deserialize, Serialize)]
754pub struct CurrentUser {
755    pub id: u64,
756    pub name: Option<String>,
757    pub sortable_name: Option<String>,
758    pub short_name: Option<String>,
759    pub sis_user_id: Option<String>,
760    pub login_id: Option<String>,
761    pub email: Option<String>,
762    pub avatar_url: Option<String>,
763    pub locale: Option<String>,
764    pub last_login: Option<DateTime<Utc>>,
765    pub time_zone: Option<String>,
766    pub bio: Option<String>,
767    pub effective_locale: Option<String>,
768}
769
770/// A user display stub (id + name only) used in nested contexts.
771#[derive(Debug, Clone, Deserialize, Serialize)]
772pub struct UserDisplay {
773    pub id: u64,
774    pub display_name: Option<String>,
775    pub avatar_image_url: Option<String>,
776    pub html_url: Option<String>,
777}
778
779/// Identifies a user by numeric ID or as the currently authenticated user.
780pub enum UserId {
781    Id(u64),
782    Current,
783}
784
785impl UserId {
786    pub(crate) fn to_path_segment(&self) -> String {
787        match self {
788            UserId::Id(id) => id.to_string(),
789            UserId::Current => "self".to_string(),
790        }
791    }
792}