Skip to main content

sharepoint_cli/commands/
drives.rs

1//! `sharepoint drives list <site-ref>`
2
3use crate::auth::AuthContext;
4use crate::cli::{DrivesCmd, Runtime};
5use crate::error::{CliError, Result};
6use crate::graph::{GraphClient, drives, sites};
7use crate::reference::SiteRef;
8
9pub async fn run(rt: &Runtime, cmd: DrivesCmd) -> Result<()> {
10    match cmd {
11        DrivesCmd::List {
12            site,
13            limit,
14            all,
15            fields,
16        } => list(rt, &site, limit, all, &fields).await,
17    }
18}
19
20async fn list(
21    rt: &Runtime,
22    site_input: &str,
23    limit: usize,
24    all: bool,
25    fields: &[String],
26) -> Result<()> {
27    let auth = AuthContext::new(rt.cfg.clone(), rt.cache_path.clone());
28    let graph = GraphClient::new(auth);
29
30    // The site argument can be a URL, an alias name, "default", or spo://Site.
31    let site_ref = if site_input == "default" {
32        SiteRef::Default
33    } else if site_input.starts_with("http://") || site_input.starts_with("https://") {
34        SiteRef::Url(site_input.to_string())
35    } else if let Some(rest) = site_input.strip_prefix("spo://") {
36        // Accept bare spo://SiteName (and spo://SiteName/... with trailing segments ignored).
37        let name = rest
38            .split('/')
39            .next()
40            .filter(|s| !s.is_empty())
41            .ok_or_else(|| {
42                CliError::Input(
43                    "spo:// URI is missing a site name (expected spo://SiteName)".into(),
44                )
45            })?;
46        SiteRef::Name(name.to_string())
47    } else {
48        SiteRef::Name(site_input.to_string())
49    };
50
51    let site = sites::resolve(
52        &graph,
53        &site_ref,
54        &rt.cfg.site_aliases,
55        rt.cfg.default_site.as_deref(),
56    )
57    .await?;
58    let mut all_drives = drives::list_drives(&graph, &site.id).await?;
59    let total = all_drives.len();
60    if !all && all_drives.len() > limit {
61        all_drives.truncate(limit);
62    }
63
64    if rt.out.json {
65        let items: Vec<_> = all_drives
66            .iter()
67            .map(|d| {
68                filter_fields(
69                    serde_json::json!({
70                        "id": d.id,
71                        "name": d.name,
72                        "drive_type": d.drive_type,
73                        "site": {"id": site.id, "name": site.display_name, "url": site.web_url},
74                    }),
75                    fields,
76                )
77            })
78            .collect();
79        rt.out.print_json(&serde_json::json!({
80            "total": total,
81            "next": null,
82            "items": items,
83        }));
84    } else {
85        for d in &all_drives {
86            rt.out
87                .print_data(&format!("{:30}  {:18}  {}", d.name, d.drive_type, d.id));
88        }
89        rt.out
90            .print_message(&format!("({total} drive(s) on {})", site.display_name));
91    }
92    Ok(())
93}
94
95/// Filter a JSON object to only the specified fields; pass through if `fields` is empty.
96fn filter_fields(mut value: serde_json::Value, fields: &[String]) -> serde_json::Value {
97    if fields.is_empty() {
98        return value;
99    }
100    if let serde_json::Value::Object(ref mut map) = value {
101        map.retain(|k, _| fields.iter().any(|f| f == k));
102    }
103    value
104}