Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 2 additions & 11 deletions src/commands/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use dialoguer::{Confirm, Input, theme::ColorfulTheme};
use indicatif::{ProgressBar, ProgressStyle};

use crate::api::{self, ApiError, CreateModuleInput};
use crate::credentials::{self, LoadError};
use crate::credentials;

use super::{
DEFAULT_API_BASE, DEFAULT_APPS_API_BASE, DEFAULT_WEB_BASE, ENV_API_URL, ENV_APPS_API_URL,
Expand Down Expand Up @@ -64,16 +64,7 @@ pub fn run(args: ModuleArgs) -> Result<()> {
fn init(args: InitArgs) -> Result<()> {
let theme = ColorfulTheme::default();

let creds = match credentials::load() {
Ok(c) => c,
Err(LoadError::NotFound) => {
return Err(anyhow!(
"not signed in. Run `mirrorstack login` to sign in."
));
}
Err(e) => return Err(e.into()),
};

let creds = credentials::load_or_login_hint()?;
let api_base = resolve_base(ENV_API_URL, DEFAULT_API_BASE);
let apps_base = resolve_base(ENV_APPS_API_URL, DEFAULT_APPS_API_BASE);
let web_base = resolve_base(ENV_WEB_URL, DEFAULT_WEB_BASE);
Expand Down
13 changes: 2 additions & 11 deletions src/commands/whoami.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,15 @@ use anyhow::{Result, anyhow};
use clap::Args;

use crate::api::{self, ApiError};
use crate::credentials::{self, LoadError};
use crate::credentials;

use super::{DEFAULT_API_BASE, ENV_API_URL, resolve_base};

#[derive(Args)]
pub struct WhoamiArgs {}

pub fn run(_args: WhoamiArgs) -> Result<()> {
let creds = match credentials::load() {
Ok(c) => c,
Err(LoadError::NotFound) => {
return Err(anyhow!(
"not signed in. Run `mirrorstack login` to sign in."
));
}
Err(e) => return Err(e.into()),
};

let creds = credentials::load_or_login_hint()?;
let api_base = resolve_base(ENV_API_URL, DEFAULT_API_BASE);

match api::me(&api_base, &creds.access_token) {
Expand Down
13 changes: 13 additions & 0 deletions src/credentials/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,19 @@ pub fn load() -> Result<Credentials, LoadError> {
}
}

/// Wrap [`load`] with the auth-required command idiom: a missing file
/// becomes a "run `mirrorstack login`" hint instead of the raw error.
/// Used by every command that needs an authenticated session.
pub fn load_or_login_hint() -> Result<Credentials> {
match load() {
Ok(c) => Ok(c),
Err(LoadError::NotFound) => Err(anyhow::anyhow!(
"not signed in. Run `mirrorstack login` to sign in."
)),
Err(e) => Err(e.into()),
}
}

/// Encode `SystemTime` as RFC 3339 in JSON. Avoids pulling chrono just
/// for this; serde_json's default for SystemTime is a tagged struct
/// which is ugly on disk.
Expand Down
11 changes: 11 additions & 0 deletions src/credentials/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ fn credentials_lifecycle() {
// load() before save() returns NotFound.
assert!(matches!(load(), Err(LoadError::NotFound)));

// load_or_login_hint() before save() surfaces the login hint.
let hint_err = load_or_login_hint().unwrap_err();
assert!(
hint_err.to_string().contains("mirrorstack login"),
"expected login hint, got: {hint_err}"
);

// save → load roundtrip preserves token strings + expires_at.
let want = Credentials {
access_token: "AT".into(),
Expand All @@ -41,6 +48,10 @@ fn credentials_lifecycle() {
};
save(&want).expect("save");
let got = load().expect("load");

// load_or_login_hint() after save() returns the same data as load().
let got_hint = load_or_login_hint().expect("load_or_login_hint");
assert_eq!(got_hint, got);
assert_eq!(got.access_token, want.access_token);
assert_eq!(got.refresh_token, want.refresh_token);
let skew = got
Expand Down
Loading