manta_shared/common/
mod.rs

1//! Behavioural helpers shared by `manta-cli` and `manta-server`.
2//!
3//! The three submodules are intentionally narrow:
4//!
5//! - [`config`] — locates and parses `cli.toml` / `server.toml`,
6//!   honouring `MANTA_CLI_CONFIG` / `MANTA_SERVER_CONFIG` and merging
7//!   `MANTA_*`-prefixed environment overrides. Returns an untyped
8//!   `::config::Config` so each binary owns its own typed schema.
9//! - [`error`] — the [`error::MantaError`] enum returned by every
10//!   fallible helper in this crate; the server bridges it to its
11//!   `BackendError` at call sites.
12//! - [`log_ops`] — single `configure(...)` entry point both binaries
13//!   call once at startup to install the tracing subscriber.
14
15/// Date-time format string used for displaying timestamps
16/// throughout the application (e.g. "04/03/2026 14:30:00").
17pub const DATETIME_FORMAT: &str = "%d/%m/%Y %H:%M:%S";
18
19/// Parse an IMS `created` timestamp into a comparable value.
20///
21/// CSM returns `created` in more than one shape, so try
22/// [`chrono::NaiveDateTime`] first, then [`chrono::DateTime<Local>`],
23/// normalising a zoned timestamp to local naive time. Returns `None`
24/// when neither parses.
25///
26/// A zoned timestamp is converted into **this process's** local
27/// timezone, so the same input yields a different naive value on hosts
28/// in different zones. Callers that compare across the client/server
29/// boundary (the CLI renderer vs. the server's date filter) agree on
30/// which strings parse, not necessarily on the wall-clock value of a
31/// zoned one.
32///
33/// Lives here rather than in either binary because the server filters
34/// on this value (`service::image::get_images`) while the CLI renders
35/// it (`output::image`). If the two disagreed on what parses, a row
36/// could display a creation date that the filter silently drops.
37///
38/// ```
39/// use manta_shared::common::parse_ims_timestamp;
40///
41/// // Naive, offset, and the `Z`/fractional-second shapes IMS emits.
42/// assert!(parse_ims_timestamp("2026-06-04T12:30:00").is_some());
43/// assert!(parse_ims_timestamp("2026-06-04T12:30:00+00:00").is_some());
44/// assert!(parse_ims_timestamp("2026-06-04T12:30:00Z").is_some());
45/// assert!(parse_ims_timestamp("2026-06-04T12:30:00.643891Z").is_some());
46/// assert!(parse_ims_timestamp("not-a-real-date").is_none());
47/// ```
48#[must_use]
49pub fn parse_ims_timestamp(raw: &str) -> Option<chrono::NaiveDateTime> {
50  if let Ok(v) = raw.parse::<chrono::NaiveDateTime>() {
51    return Some(v);
52  }
53  if let Ok(v) = raw.parse::<chrono::DateTime<chrono::Local>>() {
54    return Some(v.naive_local());
55  }
56  None
57}
58
59pub mod config;
60pub mod error;
61pub mod jwt_ops;
62pub mod log_ops;