use std::{cmp::Ordering, collections::HashSet};
use khive_types::VerbPresentationPolicy;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum NoteContentScope {
#[default]
None,
Record,
Items,
Notes,
}
impl NoteContentScope {
pub fn protect(self, mut value: Value, transform: impl FnOnce(Value) -> Value) -> Value {
if self == Self::None {
return transform(value);
}
fn take_content(record: &mut Value) -> Option<Value> {
record.as_object_mut()?.remove("content")
}
fn restore_content(record: &mut Value, content: Option<Value>) {
if let (Some(record), Some(content)) = (record.as_object_mut(), content) {
record.insert("content".to_string(), content);
}
}
if self == Self::Record {
let content = take_content(&mut value);
let mut value = transform(value);
restore_content(&mut value, content);
return value;
}
let key = if self == Self::Items {
"items"
} else {
"notes"
};
let contents: Vec<_> = value
.get_mut(key)
.and_then(Value::as_array_mut)
.into_iter()
.flatten()
.map(take_content)
.collect();
let mut value = transform(value);
if let Some(records) = value.get_mut(key).and_then(Value::as_array_mut) {
for (record, content) in records.iter_mut().zip(contents) {
restore_content(record, content);
}
}
value
}
fn stringify_table_content(self, value: &mut Value) {
fn stringify(record: &mut Value) {
if let Some(content) = record.get_mut("content") {
if content.is_object() || content.is_array() {
*content = Value::String(content.to_string());
}
}
}
match self {
Self::None => {}
Self::Record => stringify(value),
Self::Items | Self::Notes => {
let key = if self == Self::Items {
"items"
} else {
"notes"
};
if let Some(records) = value.get_mut(key).and_then(Value::as_array_mut) {
records.iter_mut().for_each(stringify);
}
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum OutputFormat {
#[default]
Json,
Auto,
Table,
}
const CELL_TRUNCATE: usize = 120;
const PROPERTY_HOIST_FIELDS: &[&str] = &["trigger_at", "due", "status"];
pub fn render_format(value: Value, format: OutputFormat, presentation: PresentationMode) -> String {
render_format_with_note_content(value, format, presentation, NoteContentScope::None)
}
pub fn render_format_with_note_content(
value: Value,
format: OutputFormat,
presentation: PresentationMode,
content_scope: NoteContentScope,
) -> String {
let value = prepare_format_value_with_note_content(value, format, presentation, content_scope);
match format {
OutputFormat::Json => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
OutputFormat::Auto if content_scope != NoteContentScope::None => {
serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string())
}
OutputFormat::Auto | OutputFormat::Table => {
render_auto(value, presentation != PresentationMode::Verbose)
}
}
}
pub fn prepare_format_value(
value: Value,
format: OutputFormat,
presentation: PresentationMode,
) -> Value {
let scope = match format {
OutputFormat::Json if presentation == PresentationMode::Agent => {
Some(RedundancyScope::Machine)
}
OutputFormat::Json => None,
OutputFormat::Auto | OutputFormat::Table if presentation != PresentationMode::Verbose => {
Some(RedundancyScope::View)
}
OutputFormat::Auto | OutputFormat::Table => None,
};
match scope {
Some(scope) => apply_redundancy_drop(value, scope),
None => value,
}
}
pub fn prepare_format_value_with_note_content(
value: Value,
format: OutputFormat,
presentation: PresentationMode,
content_scope: NoteContentScope,
) -> Value {
let mut value = content_scope.protect(value, |value| {
prepare_format_value(value, format, presentation)
});
if format == OutputFormat::Table {
content_scope.stringify_table_content(&mut value);
}
value
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RedundancyScope {
View,
Machine,
}
pub fn apply_redundancy_drop(value: Value, scope: RedundancyScope) -> Value {
match value {
Value::Object(_) => drop_record(value, scope),
Value::Array(arr) => Value::Array(
arr.into_iter()
.map(|v| {
if v.is_object() {
drop_record(v, scope)
} else {
v
}
})
.collect(),
),
other => other,
}
}
fn drop_record(value: Value, scope: RedundancyScope) -> Value {
let Value::Object(mut map) = value else {
return value;
};
if scope == RedundancyScope::View {
map.remove("full_id");
}
if map.get("namespace").and_then(Value::as_str) == Some("local") {
map.remove("namespace");
}
let props_val = map.remove("properties");
if let Some(Value::Object(props)) = props_val {
let mut new_props = Map::new();
for (k, v) in props {
let strict_round_trip = scope == RedundancyScope::Machine
&& (k == "full_id" || ROUND_TRIP_FULL_UUID_FIELDS.contains(&k.as_str()));
if !strict_round_trip && map.get(&k) == Some(&v) {
continue;
}
new_props.insert(k, v);
}
if !new_props.is_empty() {
map.insert("properties".to_string(), Value::Object(new_props));
}
} else if let Some(other) = props_val {
map.insert("properties".to_string(), other);
}
let stream_entry = is_stream_entry(&map);
let out: Map<String, Value> = map
.into_iter()
.map(|(k, v)| {
if stream_entry && k == "record" {
return (k, v);
}
let v = match v {
Value::Array(arr) => Value::Array(
arr.into_iter()
.map(|item| {
if item.is_object() {
drop_record(item, scope)
} else {
item
}
})
.collect(),
),
other => other,
};
(k, v)
})
.collect();
Value::Object(out)
}
struct RecordArray {
key: Option<String>,
records: Vec<Value>,
columns: Vec<String>,
}
fn render_auto(value: Value, truncate: bool) -> String {
match locate_record_array(&value) {
Some(found) => render_table_with_siblings(&value, &found, truncate),
None => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
}
}
fn locate_record_array(value: &Value) -> Option<RecordArray> {
let build = |key: Option<String>, arr: &[Value]| {
let records: Vec<Value> = arr.iter().cloned().map(hoist_table_scalars).collect();
let columns = collect_keys(&records);
RecordArray {
key,
records,
columns,
}
};
match value {
Value::Array(arr) if is_record_array(arr) => Some(build(None, arr)),
Value::Object(map) => map.iter().find_map(|(k, v)| match v {
Value::Array(arr) if is_record_array(arr) => Some(build(Some(k.clone()), arr)),
_ => None,
}),
_ => None,
}
}
fn hoist_table_scalars(record: Value) -> Value {
let Value::Object(mut map) = record else {
return record;
};
let mut props = match map.remove("properties") {
Some(Value::Object(props)) => props,
Some(other) => {
map.insert("properties".to_string(), other);
return Value::Object(map);
}
None => return Value::Object(map),
};
for field in PROPERTY_HOIST_FIELDS {
if map.contains_key(*field) {
continue;
}
if props
.get(*field)
.is_some_and(|v| !v.is_object() && !v.is_array())
{
let v = props.remove(*field).expect("checked above");
map.insert((*field).to_string(), v);
}
}
if !props.is_empty() {
map.insert("properties".to_string(), Value::Object(props));
}
Value::Object(map)
}
fn is_record_array(arr: &[Value]) -> bool {
arr.len() >= 2 && arr.iter().all(Value::is_object)
}
fn render_table_with_siblings(value: &Value, found: &RecordArray, truncate: bool) -> String {
let mut out = render_table(&found.records, &found.columns, truncate);
let (Value::Object(map), Some(array_key)) = (value, &found.key) else {
return out;
};
for (k, v) in map {
if k == array_key {
continue;
}
let text = match v {
Value::String(s) if !s.contains(['\n', '\r']) => s.clone(),
other => serde_json::to_string(other).unwrap_or_default(),
};
out.push_str(&format!("{k}: {text}\n"));
}
out
}
fn collect_keys(records: &[Value]) -> Vec<String> {
let mut seen = HashSet::new();
let mut keys = Vec::new();
for record in records {
if let Value::Object(map) = record {
for k in map.keys() {
if seen.insert(k.clone()) {
keys.push(k.clone());
}
}
}
}
keys
}
fn render_table(records: &[Value], keys: &[String], truncate: bool) -> String {
let mut out = String::new();
out.push('|');
for k in keys {
out.push(' ');
out.push_str(k);
out.push_str(" |");
}
out.push('\n');
out.push('|');
for _ in keys {
out.push_str("---|");
}
out.push('\n');
for record in records {
out.push('|');
for k in keys {
let cell = record.get(k).unwrap_or(&Value::Null);
let text = cell_text(k, cell, truncate);
out.push(' ');
out.push_str(&text);
out.push_str(" |");
}
out.push('\n');
}
out
}
fn truncation_exempt(key: &str) -> bool {
matches!(
key,
"id" | "kind"
| "status"
| "priority"
| "relation"
| "title"
| "name"
| "signature"
| "slug"
| "assignee"
| "from"
| "to"
| "due"
) || key.ends_with("_id")
|| key.ends_with("_at")
|| key.starts_with("due")
}
fn cell_text(key: &str, value: &Value, truncate: bool) -> String {
let raw = match value {
Value::Null => String::new(),
Value::Bool(b) => b.to_string(),
Value::Number(n) => n.to_string(),
Value::String(s) => s.clone(),
Value::Object(_) => return "{…}".to_string(),
Value::Array(arr) => {
if arr.iter().any(|v| v.is_object() || v.is_array()) {
return "[…]".to_string();
}
serde_json::to_string(value).unwrap_or_default()
}
};
let escaped = raw.replace('|', "\\|").replace(['\n', '\r'], " ");
if !truncate || truncation_exempt(key) {
return escaped;
}
let char_count = escaped.chars().count();
if char_count > CELL_TRUNCATE {
let truncated: String = escaped.chars().take(CELL_TRUNCATE).collect();
format!("{truncated}...")
} else {
escaped
}
}
pub fn format_utc_rfc3339_seconds<Tz: chrono::TimeZone>(value: &chrono::DateTime<Tz>) -> String {
value
.with_timezone(&chrono::Utc)
.to_rfc3339_opts(chrono::SecondsFormat::Secs, true)
}
pub fn micros_to_iso(micros: i64) -> String {
if let Some(dt) = chrono::DateTime::<chrono::Utc>::from_timestamp_micros(micros) {
return dt.to_rfc3339_opts(chrono::SecondsFormat::Micros, true);
}
const CYCLE_MICROS: i64 = 146_097 * 86_400 * 1_000_000;
let cycles = micros.div_euclid(CYCLE_MICROS);
let reduced =
chrono::DateTime::<chrono::Utc>::from_timestamp_micros(micros.rem_euclid(CYCLE_MICROS))
.expect("400-year remainder is within Chrono's range");
let year = i64::from(chrono::Datelike::year(&reduced)) + cycles * 400;
let rendered = reduced.to_rfc3339_opts(chrono::SecondsFormat::Micros, true);
format!("{year:+05}{}", &rendered[4..])
}
pub fn rfc3339_to_utc_micros(raw: &str) -> Result<i64, chrono::ParseError> {
chrono::DateTime::parse_from_rfc3339(raw.trim()).map(|dt| dt.timestamp_micros())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum PresentationMode {
#[default]
Agent,
Verbose,
Human,
}
const LIFECYCLE_NULL_PRESERVE: &[&str] = &[
"completed_at",
"deleted_at",
"due_at",
"read_at",
"started_at",
"superseded_at",
"applied_at",
"withdrawn_at",
"reviewed_at",
"parent_id",
"superseded_by",
"replaced_by",
"poll_interval_secs",
"stalled",
"last_success_at",
"last_poll_attempt_at",
"last_failure_at",
"last_error",
"consecutive_failures",
];
const EMPTY_ARRAY_PRESERVE: &[&str] = &[
"items",
"entities",
"notes",
"edges",
"results",
"entries",
"neighbors",
];
fn is_stable_list_envelope(map: &Map<String, Value>) -> bool {
map.contains_key("requested_limit")
&& map.contains_key("effective_limit")
&& map.contains_key("limit_clamped")
&& EMPTY_ARRAY_PRESERVE
.iter()
.any(|field| map.contains_key(*field))
}
fn is_keyset_cursor_envelope(map: &Map<String, Value>) -> bool {
map.contains_key("next_after")
&& (map.get("results").is_some_and(Value::is_array)
|| (map.get("head_seq").is_some_and(Value::is_number)
&& map.get("entries").is_some_and(Value::is_array)))
}
fn is_stream_entry(map: &Map<String, Value>) -> bool {
map.get("seq").is_some_and(Value::is_number)
&& map.contains_key("id")
&& map.contains_key("created_at")
&& map.contains_key("record")
}
const PAYLOAD_TIMESTAMP_FIELDS: &[&str] = &["trigger_at", "due"];
const ROUND_TRIP_FULL_UUID_FIELDS: &[&str] = &[
"context_entity_id",
"thread_id",
"outbound_ref",
"parent_id",
"session_id",
"project_id",
];
const SCORE_FIELDS: &[&str] = &[
"score",
"rank_score",
"vector_similarity",
"keyword_score",
"salience",
"decay_factor",
"rrf_score",
"similarity",
"cross_encoder_score",
"graph_proximity_score",
];
const UUID_CANONICAL_LEN: usize = 36;
fn should_shorten_uuid_field(key: &str) -> bool {
if key == "full_id" || key == "bridge_instance_id" || ROUND_TRIP_FULL_UUID_FIELDS.contains(&key)
{
return false;
}
key == "id" || key.ends_with("_id") || matches!(key, "superseded_by" | "replaced_by")
}
pub fn present(value: Value, mode: PresentationMode, now_unix_seconds: i64) -> Value {
present_with_policy(
value,
mode,
now_unix_seconds,
VerbPresentationPolicy::Standard,
)
}
pub fn present_with_policy(
value: Value,
mode: PresentationMode,
now_unix_seconds: i64,
policy: VerbPresentationPolicy,
) -> Value {
present_with_policy_at(value, mode, now_unix_seconds.into(), policy)
}
#[derive(Clone, Copy)]
pub struct PresentationNow {
seconds: i64,
nanoseconds: u32,
}
impl From<i64> for PresentationNow {
fn from(seconds: i64) -> Self {
Self {
seconds,
nanoseconds: 0,
}
}
}
impl From<i32> for PresentationNow {
fn from(seconds: i32) -> Self {
i64::from(seconds).into()
}
}
impl From<chrono::DateTime<chrono::Utc>> for PresentationNow {
fn from(now: chrono::DateTime<chrono::Utc>) -> Self {
Self {
seconds: now.timestamp(),
nanoseconds: now.timestamp_subsec_nanos(),
}
}
}
pub fn present_with_policy_at(
value: Value,
mode: PresentationMode,
now: PresentationNow,
policy: VerbPresentationPolicy,
) -> Value {
if policy == VerbPresentationPolicy::AlwaysVerbose {
return value;
}
match mode {
PresentationMode::Verbose | PresentationMode::Human => value,
PresentationMode::Agent => {
let mut value = value;
let agenda_next = (policy == VerbPresentationPolicy::AgendaContinuation)
.then(|| value.as_object_mut().and_then(|map| map.remove("next")))
.flatten();
let agenda_empty = policy == VerbPresentationPolicy::AgendaContinuation
&& value
.get("events")
.and_then(Value::as_array)
.is_some_and(Vec::is_empty);
let config = AgentTransformConfig {
preserved_nulls: LIFECYCLE_NULL_PRESERVE.iter().copied().collect(),
scores: SCORE_FIELDS.iter().copied().collect(),
payload_timestamps: PAYLOAD_TIMESTAMP_FIELDS.iter().copied().collect(),
now,
};
let mut value = transform_agent(
value,
&config,
AgentTreeContext {
inside_properties: false,
object_properties: false,
array_member: false,
receipt_context: match policy {
VerbPresentationPolicy::StreamAppendReceipt => ReceiptContext::AppendRoot,
VerbPresentationPolicy::StreamBatchReceipts => ReceiptContext::BatchRoot,
_ => ReceiptContext::None,
},
},
);
if let Some(map) = value.as_object_mut() {
if let Some(next) = agenda_next {
map.insert("next".into(), next);
}
if agenda_empty {
map.insert("events".into(), Value::Array(Vec::new()));
}
}
value
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum ReceiptContext {
None,
AppendRoot,
BatchRoot,
BatchResults,
BatchMember,
}
struct AgentTransformConfig {
preserved_nulls: HashSet<&'static str>,
scores: HashSet<&'static str>,
payload_timestamps: HashSet<&'static str>,
now: PresentationNow,
}
#[derive(Clone, Copy)]
struct AgentTreeContext {
inside_properties: bool,
object_properties: bool,
array_member: bool,
receipt_context: ReceiptContext,
}
fn transform_agent(
value: Value,
config: &AgentTransformConfig,
context: AgentTreeContext,
) -> Value {
match value {
Value::Object(map) => {
let preserve_list_envelope =
is_stable_list_envelope(&map) || is_keyset_cursor_envelope(&map);
let stream_entry = is_stream_entry(&map);
let relative_created_at = (context.array_member
&& !context.inside_properties
&& !map.contains_key("created_at_relative")
&& !matches!(
context.receipt_context,
ReceiptContext::AppendRoot | ReceiptContext::BatchMember
))
.then(|| map.get("created_at").and_then(Value::as_str))
.flatten()
.and_then(|raw| {
let (_, created_at) = exact_timestamp(raw)?;
let age = config.now.seconds.checked_sub(created_at)?;
age.checked_sub(i64::from(
timestamp_fraction_cmp(raw, config.now.nanoseconds) == Ordering::Greater,
))
})
.filter(|difference| *difference >= 0)
.map(relative_time);
let mut out = Map::new();
for (k, v) in map {
if stream_entry && k == "record" {
out.insert(k, v);
continue;
}
if v.is_string()
&& ((context.receipt_context == ReceiptContext::AppendRoot
&& k == "created_at")
|| (context.receipt_context == ReceiptContext::BatchMember
&& matches!(k.as_str(), "created_at" | "updated_at")))
{
out.insert(k, v);
continue;
}
let child_receipt_context = if context.receipt_context == ReceiptContext::BatchRoot
&& k == "results"
&& v.is_array()
{
ReceiptContext::BatchResults
} else {
ReceiptContext::None
};
let child_inside_properties = context.inside_properties || k == "properties";
let child_object_properties =
context.object_properties || (k == "properties" && v.is_object());
let transformed = transform_field_agent(
&k,
v,
config,
AgentFieldContext {
inside_properties: child_inside_properties,
object_properties: child_object_properties,
preserve_list_envelope,
receipt_context: child_receipt_context,
},
);
match transformed {
None => {} Some(tv) => {
out.insert(k, tv);
}
}
}
if let Some(relative) = relative_created_at {
out.insert("created_at_relative".to_string(), Value::String(relative));
}
Value::Object(out)
}
Value::Array(arr) => {
let items: Vec<Value> = arr
.into_iter()
.map(|v| {
transform_agent(
v,
config,
AgentTreeContext {
inside_properties: context.inside_properties,
object_properties: context.object_properties,
array_member: true,
receipt_context: if context.receipt_context
== ReceiptContext::BatchResults
{
ReceiptContext::BatchMember
} else {
ReceiptContext::None
},
},
)
})
.collect();
Value::Array(items)
}
other => other,
}
}
#[derive(Clone, Copy)]
struct AgentFieldContext {
inside_properties: bool,
object_properties: bool,
preserve_list_envelope: bool,
receipt_context: ReceiptContext,
}
fn transform_field_agent(
key: &str,
value: Value,
config: &AgentTransformConfig,
context: AgentFieldContext,
) -> Option<Value> {
match &value {
Value::Null => {
if config.preserved_nulls.contains(key)
|| (context.preserve_list_envelope && key == "next_after")
{
Some(value)
} else {
None
}
}
Value::Array(a)
if context.preserve_list_envelope
&& a.is_empty()
&& EMPTY_ARRAY_PRESERVE.contains(&key) =>
{
Some(value)
}
Value::String(s) if s.is_empty() && context.object_properties => Some(value),
Value::String(s) if s.is_empty() => None,
Value::Array(a) if a.is_empty() => None,
Value::Object(o) if o.is_empty() => None,
Value::Number(_) if config.scores.contains(key) => {
if let Some(f) = value.as_f64() {
Some(truncate_to_3_sig_figs(f))
} else {
Some(value)
}
}
Value::String(s) if is_canonical_uuid(s) && should_shorten_uuid_field(key) => {
Some(Value::String(s[..8].to_string()))
}
Value::String(s)
if !context.inside_properties
&& !config.payload_timestamps.contains(key)
&& looks_like_iso8601(s) =>
{
Some(Value::String(
exact_timestamp(s).map_or_else(|| s.clone(), |(rendered, _)| rendered),
))
}
Value::Object(_) | Value::Array(_) => Some(transform_agent(
value,
config,
AgentTreeContext {
inside_properties: context.inside_properties,
object_properties: context.object_properties,
array_member: false,
receipt_context: context.receipt_context,
},
)),
_ => Some(value),
}
}
fn is_canonical_uuid(s: &str) -> bool {
if s.len() != UUID_CANONICAL_LEN {
return false;
}
let b = s.as_bytes();
b[8] == b'-'
&& b[13] == b'-'
&& b[18] == b'-'
&& b[23] == b'-'
&& b[..8].iter().all(|c| c.is_ascii_hexdigit())
&& b[9..13].iter().all(|c| c.is_ascii_hexdigit())
&& b[14..18].iter().all(|c| c.is_ascii_hexdigit())
&& b[19..23].iter().all(|c| c.is_ascii_hexdigit())
&& b[24..].iter().all(|c| c.is_ascii_hexdigit())
}
fn looks_like_iso8601(s: &str) -> bool {
if s.len() < 16 {
return false;
}
let b = s.as_bytes();
b[4] == b'-'
&& b[7] == b'-'
&& b[10] == b'T'
&& b[13] == b':'
&& b[..4].iter().all(|c| c.is_ascii_digit())
&& b[5..7].iter().all(|c| c.is_ascii_digit())
&& b[8..10].iter().all(|c| c.is_ascii_digit())
&& b[11..13].iter().all(|c| c.is_ascii_digit())
}
fn exact_timestamp(s: &str) -> Option<(String, i64)> {
let unix = parse_iso8601_unix(s)?;
let tail = s.get(19..)?;
let fraction = if let Some(after_dot) = tail.strip_prefix('.') {
let digits = after_dot.bytes().take_while(u8::is_ascii_digit).count();
tail.get(..digits + 1)?
} else {
""
};
let utc = chrono::DateTime::<chrono::Utc>::from_timestamp(unix, 0)?;
Some((
format!("{}{fraction}Z", utc.format("%Y-%m-%dT%H:%M:%S")),
unix,
))
}
fn timestamp_fraction_cmp(timestamp: &str, now_nanoseconds: u32) -> Ordering {
let fraction = if timestamp.as_bytes().get(19) == Some(&b'.') {
timestamp.as_bytes()[20..]
.split(|byte| !byte.is_ascii_digit())
.next()
.unwrap_or_default()
} else {
&[]
};
let now_fraction = format!("{now_nanoseconds:09}");
for index in 0..fraction.len().max(9) {
let created_digit = fraction.get(index).copied().unwrap_or(b'0');
let now_digit = now_fraction.as_bytes().get(index).copied().unwrap_or(b'0');
match created_digit.cmp(&now_digit) {
Ordering::Equal => {}
difference => return difference,
}
}
Ordering::Equal
}
fn parse_iso8601_unix(s: &str) -> Option<i64> {
if s.len() < 20 {
return None;
}
let b = s.as_bytes();
if b[4] != b'-' || b[7] != b'-' || b[10] != b'T' || b[13] != b':' || b[16] != b':' {
return None;
}
let year: i64 = parse_digits(&b[0..4])?;
let month: i64 = parse_digits(&b[5..7])?;
let day: i64 = parse_digits(&b[8..10])?;
let hour: i64 = parse_digits(&b[11..13])?;
let minute: i64 = parse_digits(&b[14..16])?;
let second: i64 = parse_digits(&b[17..19])?;
let local = chrono::NaiveDate::from_ymd_opt(
year.try_into().ok()?,
month.try_into().ok()?,
day.try_into().ok()?,
)?
.and_hms_opt(
hour.try_into().ok()?,
minute.try_into().ok()?,
second.try_into().ok()?,
)?
.and_utc()
.timestamp();
let offset_secs = parse_tz_offset_secs(s.get(19..)?)?;
local.checked_sub(offset_secs)
}
fn parse_tz_offset_secs(tail: &str) -> Option<i64> {
let mut rest = tail;
if let Some(after_dot) = rest.strip_prefix('.') {
let frac_len = after_dot.bytes().take_while(u8::is_ascii_digit).count();
if frac_len == 0 {
return None;
}
rest = &after_dot[frac_len..];
}
if rest == "Z" {
return Some(0);
}
let sign: i64 = match rest.as_bytes().first()? {
b'+' => 1,
b'-' => -1,
_ => return None,
};
let digits = &rest[1..];
let (hh, mm) = match digits.len() {
5 if digits.as_bytes()[2] == b':' => (
parse_digits(&digits.as_bytes()[0..2])?,
parse_digits(&digits.as_bytes()[3..5])?,
),
4 => (
parse_digits(&digits.as_bytes()[0..2])?,
parse_digits(&digits.as_bytes()[2..4])?,
),
_ => return None,
};
if hh > 23 || mm > 59 {
return None;
}
Some(sign * (hh * 3600 + mm * 60))
}
fn parse_digits(b: &[u8]) -> Option<i64> {
if !b.iter().all(u8::is_ascii_digit) {
return None;
}
let s = std::str::from_utf8(b).ok()?;
s.parse().ok()
}
fn relative_time(diff_secs: i64) -> String {
if diff_secs < 60 {
format!("{diff_secs}s ago")
} else if diff_secs < 3600 {
format!("{}m ago", diff_secs / 60)
} else if diff_secs < 86400 {
format!("{}h ago", diff_secs / 3600)
} else {
format!("{}d ago", diff_secs / 86400)
}
}
fn truncate_to_3_sig_figs(f: f64) -> Value {
if f == 0.0 || !f.is_finite() {
return Value::from(f);
}
let magnitude = f.abs().log10().floor() as i32;
let factor = 10f64.powi(2 - magnitude);
let rounded = (f * factor).round() / factor;
let rounded = if rounded.is_finite() {
rounded
} else {
format!("{f:.2e}")
.parse::<f64>()
.ok()
.filter(|value| value.is_finite())
.unwrap_or(f)
};
serde_json::Number::from_f64(rounded)
.map(Value::Number)
.unwrap_or(Value::from(f))
}
#[cfg(test)]
#[path = "presentation_tests.rs"]
mod tests;
#[cfg(test)]
#[path = "presentation_stream_presentation_tests.rs"]
mod stream_presentation_tests;
#[cfg(test)]
#[path = "presentation_issue_2537_standard_tests.rs"]
mod issue_2537_standard_tests;
#[cfg(test)]
#[path = "presentation_issue_2537_receipt_policy_tests.rs"]
mod issue_2537_receipt_policy_tests;
#[cfg(test)]
#[path = "presentation_parsed_note_content_tests.rs"]
mod parsed_note_content_tests;
#[cfg(test)]
#[path = "presentation_adr045_amendment9_tests.rs"]
mod adr045_amendment9_tests;