From e33d63e3e20aba0342559561a4945b074385b651 Mon Sep 17 00:00:00 2001 From: VPRamon Date: Tue, 24 Feb 2026 19:49:49 +0100 Subject: [PATCH 1/3] Add qtty and qtty-ffi dependencies; enhance FFI with QttyQuantity support --- Cargo.lock | 51 +++++++- tempoch-ffi/Cargo.toml | 1 + tempoch-ffi/cbindgen.toml | 7 ++ tempoch-ffi/include/tempoch_ffi.h | 29 +++++ tempoch-ffi/src/lib.rs | 17 +++ tempoch-ffi/src/period.rs | 49 ++++---- tempoch-ffi/src/time.rs | 186 +++++++++++++++++++++++------- 7 files changed, 269 insertions(+), 71 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a32b5b4..6007e2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -380,14 +380,31 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "qtty" +version = "0.3.0" +dependencies = [ + "qtty-core 0.3.0", + "qtty-derive 0.3.0", +] + [[package]] name = "qtty" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec18b86dcb4c777f979a171699a5f552675bbbfd7406d3d002403a26d7bd3215" dependencies = [ - "qtty-core", - "qtty-derive", + "qtty-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "qtty-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "qtty-core" +version = "0.3.0" +dependencies = [ + "libm", + "qtty-derive 0.3.0", + "typenum", ] [[package]] @@ -397,10 +414,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02271e8e4889de336896d2b690ad40ef811e68a81726885a7ddfbccf7e35b19c" dependencies = [ "libm", - "qtty-derive", + "qtty-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "typenum", ] +[[package]] +name = "qtty-derive" +version = "0.3.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "qtty-derive" version = "0.3.0" @@ -412,6 +438,18 @@ dependencies = [ "syn", ] +[[package]] +name = "qtty-ffi" +version = "0.3.0" +dependencies = [ + "cbindgen", + "qtty 0.3.0", + "quote", + "serde", + "serde_json", + "syn", +] + [[package]] name = "quote" version = "1.0.44" @@ -545,7 +583,7 @@ name = "tempoch" version = "0.3.0" dependencies = [ "chrono", - "qtty", + "qtty 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json", "tempoch-core", ] @@ -555,7 +593,7 @@ name = "tempoch-core" version = "0.3.0" dependencies = [ "chrono", - "qtty", + "qtty 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde", ] @@ -565,7 +603,8 @@ version = "0.1.0" dependencies = [ "cbindgen", "chrono", - "qtty", + "qtty 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "qtty-ffi", "serde_json", "tempoch", ] diff --git a/tempoch-ffi/Cargo.toml b/tempoch-ffi/Cargo.toml index 11abe86..942770f 100644 --- a/tempoch-ffi/Cargo.toml +++ b/tempoch-ffi/Cargo.toml @@ -18,6 +18,7 @@ serde = ["tempoch/serde", "dep:serde_json"] [dependencies] tempoch = { path = "../tempoch", version = "0.3.0" } qtty = "0.3.0" +qtty-ffi = { path = "../../../qtty-cpp/qtty/qtty-ffi", version = "0.3.0" } chrono = "0.4.43" serde_json = { version = "1.0", optional = true } diff --git a/tempoch-ffi/cbindgen.toml b/tempoch-ffi/cbindgen.toml index 10247b1..42c6ed0 100644 --- a/tempoch-ffi/cbindgen.toml +++ b/tempoch-ffi/cbindgen.toml @@ -4,16 +4,23 @@ autogen_warning = "/* Warning: this file is auto-generated by cbindgen. Do not m documentation_style = "c99" style = "both" cpp_compat = true +after_includes = """ +/* QttyQuantity and UnitId definitions come from qtty_ffi.h */ +#include "qtty_ffi.h" +""" [defines] "feature = serde" = "TEMPOCH_FFI_HAS_SERDE" [export] +# Exclude qtty-ffi types — they come from qtty_ffi.h (included via after_includes) +exclude = ["QttyQuantity", "UnitId", "DimensionId", "QttyDerivedQuantity"] [export.rename] "TempochStatus" = "tempoch_status_t" "TempochUtc" = "tempoch_utc_t" "TempochPeriodMjd" = "tempoch_period_mjd_t" +"QttyQuantity" = "qtty_quantity_t" [enum] rename_variants = "ScreamingSnakeCase" diff --git a/tempoch-ffi/include/tempoch_ffi.h b/tempoch-ffi/include/tempoch_ffi.h index c8a8674..db6e225 100644 --- a/tempoch-ffi/include/tempoch_ffi.h +++ b/tempoch-ffi/include/tempoch_ffi.h @@ -7,6 +7,9 @@ #include #include #include +/* QttyQuantity and UnitId definitions come from qtty_ffi.h */ +#include "qtty_ffi.h" + // Status codes returned by tempoch-ffi functions. // @@ -131,6 +134,32 @@ tempoch_status_t tempoch_period_mjd_intersection(struct tempoch_period_mjd_t a, // Compute Julian centuries since J2000 for a given Julian Date. double tempoch_jd_julian_centuries(double jd); +// Compute the difference between two Julian Dates as a `QttyQuantity` in days. + qtty_quantity_t tempoch_jd_difference_qty(double jd1, double jd2); + +// Add a `QttyQuantity` duration (must be time-compatible) to a Julian Date. +// The quantity is converted to days internally. +// +// # Safety +// `out` must be a valid, writable pointer to `f64`. + tempoch_status_t tempoch_jd_add_qty(double jd, qtty_quantity_t duration, double *out); + +// Compute the difference between two Modified Julian Dates as a `QttyQuantity` in days. + qtty_quantity_t tempoch_mjd_difference_qty(double mjd1, double mjd2); + +// Add a `QttyQuantity` duration (must be time-compatible) to a Modified Julian Date. +// The quantity is converted to days internally. +// +// # Safety +// `out` must be a valid, writable pointer to `f64`. + tempoch_status_t tempoch_mjd_add_qty(double mjd, qtty_quantity_t duration, double *out); + +// Compute Julian centuries since J2000 as a `QttyQuantity`. + qtty_quantity_t tempoch_jd_julian_centuries_qty(double jd); + +// Compute the duration of a period as a `QttyQuantity` in days. + qtty_quantity_t tempoch_period_mjd_duration_qty(struct tempoch_period_mjd_t period); + #ifdef __cplusplus } // extern "C" #endif // __cplusplus diff --git a/tempoch-ffi/src/lib.rs b/tempoch-ffi/src/lib.rs index e9b1460..b61e3d2 100644 --- a/tempoch-ffi/src/lib.rs +++ b/tempoch-ffi/src/lib.rs @@ -5,6 +5,9 @@ //! //! This crate exposes a flat C-compatible API for creating and manipulating //! Julian Dates, Modified Julian Dates, time periods, and UTC conversions. +//! +//! Duration-related functions return [`QttyQuantity`] from qtty-ffi, providing +//! type-safe unit information alongside numeric values. mod error; mod period; @@ -14,6 +17,20 @@ pub use error::*; pub use period::*; pub use time::*; +// Re-export qtty-ffi types used in our public FFI surface +pub use qtty_ffi::{QttyQuantity, UnitId}; + +/// Catches any panic and returns an error value instead of unwinding across FFI. +macro_rules! catch_panic { + ($default:expr, $body:expr) => {{ + match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| $body)) { + Ok(result) => result, + Err(_) => $default, + } + }}; +} +pub(crate) use catch_panic; + /// Returns the tempoch-ffi ABI version (semver-encoded: major*10000 + minor*100 + patch). #[allow(clippy::erasing_op, clippy::identity_op)] #[no_mangle] diff --git a/tempoch-ffi/src/period.rs b/tempoch-ffi/src/period.rs index eb3a51f..1142985 100644 --- a/tempoch-ffi/src/period.rs +++ b/tempoch-ffi/src/period.rs @@ -3,6 +3,7 @@ //! FFI bindings for tempoch period/interval types. +use crate::catch_panic; use crate::error::TempochStatus; use tempoch::{Interval, ModifiedJulianDate, Period, MJD}; @@ -50,17 +51,18 @@ pub unsafe extern "C" fn tempoch_period_mjd_new( end_mjd: f64, out: *mut TempochPeriodMjd, ) -> TempochStatus { - if out.is_null() { - return TempochStatus::NullPointer; - } - if start_mjd > end_mjd { - return TempochStatus::InvalidPeriod; - } - // SAFETY: `out` was checked for null and the caller guarantees it points to writable memory. - unsafe { - *out = TempochPeriodMjd { start_mjd, end_mjd }; - } - TempochStatus::Ok + catch_panic!(TempochStatus::InvalidPeriod, { + if out.is_null() { + return TempochStatus::NullPointer; + } + if start_mjd > end_mjd { + return TempochStatus::InvalidPeriod; + } + unsafe { + *out = TempochPeriodMjd { start_mjd, end_mjd }; + } + TempochStatus::Ok + }) } /// Compute the duration of a period in days. @@ -80,17 +82,18 @@ pub unsafe extern "C" fn tempoch_period_mjd_intersection( b: TempochPeriodMjd, out: *mut TempochPeriodMjd, ) -> TempochStatus { - if out.is_null() { - return TempochStatus::NullPointer; - } - let pa = a.to_period(); - let pb = b.to_period(); - match pa.intersection(&pb) { - Some(result) => { - // SAFETY: `out` was checked for null and the caller guarantees it points to writable memory. - unsafe { *out = TempochPeriodMjd::from_period(&result) }; - TempochStatus::Ok + catch_panic!(TempochStatus::NoIntersection, { + if out.is_null() { + return TempochStatus::NullPointer; } - None => TempochStatus::NoIntersection, - } + let pa = a.to_period(); + let pb = b.to_period(); + match pa.intersection(&pb) { + Some(result) => { + unsafe { *out = TempochPeriodMjd::from_period(&result) }; + TempochStatus::Ok + } + None => TempochStatus::NoIntersection, + } + }) } diff --git a/tempoch-ffi/src/time.rs b/tempoch-ffi/src/time.rs index af7cab5..e5ccf54 100644 --- a/tempoch-ffi/src/time.rs +++ b/tempoch-ffi/src/time.rs @@ -4,9 +4,11 @@ //! FFI bindings for tempoch time types: JulianDate, ModifiedJulianDate, //! and UTC conversions. +use crate::catch_panic; use crate::error::TempochStatus; use chrono::{DateTime, NaiveDate, Utc}; use qtty::Days; +use qtty_ffi::{QttyQuantity, UnitId}; use tempoch::{JulianDate, ModifiedJulianDate, TimeInstant, JD, MJD}; // ═══════════════════════════════════════════════════════════════════════════ @@ -80,18 +82,19 @@ pub extern "C" fn tempoch_jd_to_mjd(jd: f64) -> f64 { /// `out` must be a valid, writable pointer to `f64`. #[no_mangle] pub unsafe extern "C" fn tempoch_jd_from_utc(utc: TempochUtc, out: *mut f64) -> TempochStatus { - if out.is_null() { - return TempochStatus::NullPointer; - } - match utc.into_chrono() { - Some(dt) => { - let jd = JulianDate::from_utc(dt); - // SAFETY: `out` was checked for null and the caller guarantees writable memory. - unsafe { *out = jd.value() }; - TempochStatus::Ok + catch_panic!(TempochStatus::UtcConversionFailed, { + if out.is_null() { + return TempochStatus::NullPointer; } - None => TempochStatus::UtcConversionFailed, - } + match utc.into_chrono() { + Some(dt) => { + let jd = JulianDate::from_utc(dt); + unsafe { *out = jd.value() }; + TempochStatus::Ok + } + None => TempochStatus::UtcConversionFailed, + } + }) } /// Convert a Julian Date to UTC. Returns Ok on success, @@ -101,17 +104,18 @@ pub unsafe extern "C" fn tempoch_jd_from_utc(utc: TempochUtc, out: *mut f64) -> /// `out` must be a valid, writable pointer to `TempochUtc`. #[no_mangle] pub unsafe extern "C" fn tempoch_jd_to_utc(jd: f64, out: *mut TempochUtc) -> TempochStatus { - if out.is_null() { - return TempochStatus::NullPointer; - } - match JulianDate::new(jd).to_utc() { - Some(dt) => { - // SAFETY: `out` was checked for null and the caller guarantees writable memory. - unsafe { *out = TempochUtc::from_chrono(&dt) }; - TempochStatus::Ok + catch_panic!(TempochStatus::UtcConversionFailed, { + if out.is_null() { + return TempochStatus::NullPointer; } - None => TempochStatus::UtcConversionFailed, - } + match JulianDate::new(jd).to_utc() { + Some(dt) => { + unsafe { *out = TempochUtc::from_chrono(&dt) }; + TempochStatus::Ok + } + None => TempochStatus::UtcConversionFailed, + } + }) } // ═══════════════════════════════════════════════════════════════════════════ @@ -136,18 +140,19 @@ pub extern "C" fn tempoch_mjd_to_jd(mjd: f64) -> f64 { /// `out` must be a valid, writable pointer to `f64`. #[no_mangle] pub unsafe extern "C" fn tempoch_mjd_from_utc(utc: TempochUtc, out: *mut f64) -> TempochStatus { - if out.is_null() { - return TempochStatus::NullPointer; - } - match utc.into_chrono() { - Some(dt) => { - let mjd = ModifiedJulianDate::from_utc(dt); - // SAFETY: `out` was checked for null and the caller guarantees writable memory. - unsafe { *out = mjd.value() }; - TempochStatus::Ok + catch_panic!(TempochStatus::UtcConversionFailed, { + if out.is_null() { + return TempochStatus::NullPointer; } - None => TempochStatus::UtcConversionFailed, - } + match utc.into_chrono() { + Some(dt) => { + let mjd = ModifiedJulianDate::from_utc(dt); + unsafe { *out = mjd.value() }; + TempochStatus::Ok + } + None => TempochStatus::UtcConversionFailed, + } + }) } /// Convert a Modified Julian Date to UTC. @@ -156,19 +161,24 @@ pub unsafe extern "C" fn tempoch_mjd_from_utc(utc: TempochUtc, out: *mut f64) -> /// `out` must be a valid, writable pointer to `TempochUtc`. #[no_mangle] pub unsafe extern "C" fn tempoch_mjd_to_utc(mjd: f64, out: *mut TempochUtc) -> TempochStatus { - if out.is_null() { - return TempochStatus::NullPointer; - } - match ModifiedJulianDate::new(mjd).to_utc() { - Some(dt) => { - // SAFETY: `out` was checked for null and the caller guarantees writable memory. - unsafe { *out = TempochUtc::from_chrono(&dt) }; - TempochStatus::Ok + catch_panic!(TempochStatus::UtcConversionFailed, { + if out.is_null() { + return TempochStatus::NullPointer; } - None => TempochStatus::UtcConversionFailed, - } + match ModifiedJulianDate::new(mjd).to_utc() { + Some(dt) => { + unsafe { *out = TempochUtc::from_chrono(&dt) }; + TempochStatus::Ok + } + None => TempochStatus::UtcConversionFailed, + } + }) } +// ═══════════════════════════════════════════════════════════════════════════ +// Duration / Difference (raw f64 — backward compatible) +// ═══════════════════════════════════════════════════════════════════════════ + /// Compute the difference between two Julian Dates in days. #[no_mangle] pub extern "C" fn tempoch_jd_difference(jd1: f64, jd2: f64) -> f64 { @@ -204,3 +214,95 @@ pub extern "C" fn tempoch_mjd_add_days(mjd: f64, days: f64) -> f64 { pub extern "C" fn tempoch_jd_julian_centuries(jd: f64) -> f64 { JulianDate::new(jd).julian_centuries().value() } + +// ═══════════════════════════════════════════════════════════════════════════ +// Duration / Difference (QttyQuantity — typed) +// ═══════════════════════════════════════════════════════════════════════════ +// These functions return `QttyQuantity` values with proper unit metadata, +// enabling type-safe conversions via the qtty-ffi API. + +/// Compute the difference between two Julian Dates as a `QttyQuantity` in days. +#[no_mangle] +pub extern "C" fn tempoch_jd_difference_qty(jd1: f64, jd2: f64) -> QttyQuantity { + let t1 = JulianDate::new(jd1); + let t2 = JulianDate::new(jd2); + QttyQuantity::new(t1.difference(&t2).value(), UnitId::Day) +} + +/// Add a `QttyQuantity` duration (must be time-compatible) to a Julian Date. +/// The quantity is converted to days internally. +/// +/// # Safety +/// `out` must be a valid, writable pointer to `f64`. +#[no_mangle] +pub unsafe extern "C" fn tempoch_jd_add_qty( + jd: f64, + duration: QttyQuantity, + out: *mut f64, +) -> TempochStatus { + catch_panic!(TempochStatus::UtcConversionFailed, { + if out.is_null() { + return TempochStatus::NullPointer; + } + // Convert quantity to days via qtty-ffi registry + let days_val = match duration.convert_to(UnitId::Day) { + Some(q) => q.value, + None => return TempochStatus::UtcConversionFailed, + }; + let result = JulianDate::new(jd) + .add_duration(Days::new(days_val)) + .value(); + unsafe { *out = result }; + TempochStatus::Ok + }) +} + +/// Compute the difference between two Modified Julian Dates as a `QttyQuantity` in days. +#[no_mangle] +pub extern "C" fn tempoch_mjd_difference_qty(mjd1: f64, mjd2: f64) -> QttyQuantity { + let t1 = ModifiedJulianDate::new(mjd1); + let t2 = ModifiedJulianDate::new(mjd2); + QttyQuantity::new(t1.difference(&t2).value(), UnitId::Day) +} + +/// Add a `QttyQuantity` duration (must be time-compatible) to a Modified Julian Date. +/// The quantity is converted to days internally. +/// +/// # Safety +/// `out` must be a valid, writable pointer to `f64`. +#[no_mangle] +pub unsafe extern "C" fn tempoch_mjd_add_qty( + mjd: f64, + duration: QttyQuantity, + out: *mut f64, +) -> TempochStatus { + catch_panic!(TempochStatus::UtcConversionFailed, { + if out.is_null() { + return TempochStatus::NullPointer; + } + let days_val = match duration.convert_to(UnitId::Day) { + Some(q) => q.value, + None => return TempochStatus::UtcConversionFailed, + }; + let result = ModifiedJulianDate::new(mjd) + .add_duration(Days::new(days_val)) + .value(); + unsafe { *out = result }; + TempochStatus::Ok + }) +} + +/// Compute Julian centuries since J2000 as a `QttyQuantity`. +#[no_mangle] +pub extern "C" fn tempoch_jd_julian_centuries_qty(jd: f64) -> QttyQuantity { + QttyQuantity::new( + JulianDate::new(jd).julian_centuries().value(), + UnitId::JulianCentury, + ) +} + +/// Compute the duration of a period as a `QttyQuantity` in days. +#[no_mangle] +pub extern "C" fn tempoch_period_mjd_duration_qty(period: crate::TempochPeriodMjd) -> QttyQuantity { + QttyQuantity::new(period.end_mjd - period.start_mjd, UnitId::Day) +} From 2b8ecad52686ebee4d421de852ca0ce95f8c44bc Mon Sep 17 00:00:00 2001 From: VPRamon Date: Tue, 24 Feb 2026 20:36:25 +0100 Subject: [PATCH 2/3] qtty-0.3.1 --- tempoch-ffi/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tempoch-ffi/Cargo.toml b/tempoch-ffi/Cargo.toml index 942770f..623d166 100644 --- a/tempoch-ffi/Cargo.toml +++ b/tempoch-ffi/Cargo.toml @@ -17,8 +17,8 @@ serde = ["tempoch/serde", "dep:serde_json"] [dependencies] tempoch = { path = "../tempoch", version = "0.3.0" } -qtty = "0.3.0" -qtty-ffi = { path = "../../../qtty-cpp/qtty/qtty-ffi", version = "0.3.0" } +qtty = "0.3.1" +qtty-ffi = "0.3.1" chrono = "0.4.43" serde_json = { version = "1.0", optional = true } From c7aed7ecc36ba946844bfa04834da9b8bc3765c7 Mon Sep 17 00:00:00 2001 From: VPRamon Date: Tue, 24 Feb 2026 20:48:36 +0100 Subject: [PATCH 3/3] Bump qtty, qtty-core, qtty-derive, and qtty-ffi to version 0.3.1 in Cargo.lock --- Cargo.lock | 56 ++++++++++++++++-------------------------------------- 1 file changed, 16 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6007e2a..7102136 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -382,56 +382,30 @@ dependencies = [ [[package]] name = "qtty" -version = "0.3.0" -dependencies = [ - "qtty-core 0.3.0", - "qtty-derive 0.3.0", -] - -[[package]] -name = "qtty" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec18b86dcb4c777f979a171699a5f552675bbbfd7406d3d002403a26d7bd3215" +checksum = "b42948ee9aba0ed2217fd0d89a666df2ab020e3e25ef9b0328150e6d6c0aaee3" dependencies = [ - "qtty-core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "qtty-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "qtty-core" -version = "0.3.0" -dependencies = [ - "libm", - "qtty-derive 0.3.0", - "typenum", + "qtty-core", + "qtty-derive", ] [[package]] name = "qtty-core" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02271e8e4889de336896d2b690ad40ef811e68a81726885a7ddfbccf7e35b19c" +checksum = "283c549662e44f8a2848964e9639f2924f7a442f94294d508fc70c531a9717b3" dependencies = [ "libm", - "qtty-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "qtty-derive", "typenum", ] [[package]] name = "qtty-derive" -version = "0.3.0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "qtty-derive" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6b1435ce1b31534c420c9b7c08dc7cf1d4a39f18748e7c7f29b71296d558a51" +checksum = "5edb55d59461af47a984baae7fafc2374dac2a79d41434a77ee7807f0f592757" dependencies = [ "proc-macro2", "quote", @@ -440,10 +414,12 @@ dependencies = [ [[package]] name = "qtty-ffi" -version = "0.3.0" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93de7d3db9abe1006b8a8b635de51f6d967636be144d06c28f66c3b8a49bd17" dependencies = [ "cbindgen", - "qtty 0.3.0", + "qtty", "quote", "serde", "serde_json", @@ -583,7 +559,7 @@ name = "tempoch" version = "0.3.0" dependencies = [ "chrono", - "qtty 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "qtty", "serde_json", "tempoch-core", ] @@ -593,7 +569,7 @@ name = "tempoch-core" version = "0.3.0" dependencies = [ "chrono", - "qtty 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "qtty", "serde", ] @@ -603,7 +579,7 @@ version = "0.1.0" dependencies = [ "cbindgen", "chrono", - "qtty 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "qtty", "qtty-ffi", "serde_json", "tempoch",