app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

commit 4b5d56777a4909500ef4a1b9214ce4ac09c69008
parent c184c0c247305b56f353cdc30841d926125d6bae
Author: triesap <tyson@radroots.org>
Date:   Sun, 22 Mar 2026 01:21:59 +0000

core: add location resolver boundary

- add shared location resolver models for reverse lookup country list and country center queries
- expose the location resolver contract on the app backend without coupling any existing feature flow to geocoder usage
- keep the boundary geocoder-neutral by excluding raw country row dumps from the app-facing api
- add core tests for the default reverse options and stable resolver error codes

Diffstat:
Mcrates/core/src/lib.rs | 23+++++++++++++++++++++++
Acrates/core/src/location_resolver.rs | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 137 insertions(+), 0 deletions(-)

diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs @@ -4,10 +4,15 @@ use eframe::egui; use std::time::Duration; use zeroize::Zeroizing; +mod location_resolver; mod offline_geocoder; pub const APP_NAME: &str = "Rad Roots"; +pub use location_resolver::{ + RadrootsLocationCountry, RadrootsLocationPoint, RadrootsLocationResolverError, + RadrootsLocationReverseOptions, RadrootsResolvedLocation, +}; pub use offline_geocoder::{ RadrootsOfflineGeocoderDiagnostic, RadrootsOfflineGeocoderPlatform, RadrootsOfflineGeocoderState, RadrootsOfflineGeocoderUnavailableKind, @@ -101,6 +106,24 @@ pub trait RadrootsAppBackend { fn poll_identity_state(&self) -> Result<Option<IdentityGateState>, String> { Ok(None) } + fn reverse_location( + &self, + _point: RadrootsLocationPoint, + _options: Option<RadrootsLocationReverseOptions>, + ) -> Result<Vec<RadrootsResolvedLocation>, RadrootsLocationResolverError> { + Err(RadrootsLocationResolverError::Unsupported) + } + fn list_location_countries( + &self, + ) -> Result<Vec<RadrootsLocationCountry>, RadrootsLocationResolverError> { + Err(RadrootsLocationResolverError::Unsupported) + } + fn location_country_center( + &self, + _country_id: &str, + ) -> Result<RadrootsLocationPoint, RadrootsLocationResolverError> { + Err(RadrootsLocationResolverError::Unsupported) + } } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/crates/core/src/location_resolver.rs b/crates/core/src/location_resolver.rs @@ -0,0 +1,114 @@ +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct RadrootsLocationPoint { + pub lat: f64, + pub lng: f64, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct RadrootsLocationReverseOptions { + pub limit: usize, + pub degree_offset: f64, +} + +impl Default for RadrootsLocationReverseOptions { + fn default() -> Self { + Self { + limit: 1, + degree_offset: 0.5, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct RadrootsResolvedLocation { + pub id: i64, + pub name: String, + pub admin1_id: Option<i64>, + pub admin1_name: Option<String>, + pub country_id: String, + pub country_name: Option<String>, + pub point: RadrootsLocationPoint, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct RadrootsLocationCountry { + pub country_id: String, + pub country_name: Option<String>, + pub center: RadrootsLocationPoint, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum RadrootsLocationResolverError { + Unsupported, + Initializing, + Unavailable, + CountryCenterNotFound { country_id: String }, + QueryFailed { message: String }, +} + +impl RadrootsLocationResolverError { + pub fn code(&self) -> &'static str { + match self { + Self::Unsupported => "unsupported", + Self::Initializing => "initializing", + Self::Unavailable => "unavailable", + Self::CountryCenterNotFound { .. } => "country_center_not_found", + Self::QueryFailed { .. } => "query_failed", + } + } + + pub fn user_message(&self) -> &'static str { + match self { + Self::Unsupported => "Offline location resolution is not available on this platform.", + Self::Initializing => { + "Offline location resolution is still initializing on this device." + } + Self::Unavailable => "Offline location resolution is not available on this device.", + Self::CountryCenterNotFound { .. } => "The requested country center is not available.", + Self::QueryFailed { .. } => "The offline location query could not be completed.", + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn reverse_options_default_matches_geocoder_defaults() { + let options = RadrootsLocationReverseOptions::default(); + + assert_eq!(options.limit, 1); + assert_eq!(options.degree_offset, 0.5); + } + + #[test] + fn location_resolver_error_codes_are_stable() { + assert_eq!( + RadrootsLocationResolverError::Unsupported.code(), + "unsupported" + ); + assert_eq!( + RadrootsLocationResolverError::Initializing.code(), + "initializing" + ); + assert_eq!( + RadrootsLocationResolverError::Unavailable.code(), + "unavailable" + ); + assert_eq!( + RadrootsLocationResolverError::CountryCenterNotFound { + country_id: "US".to_owned(), + } + .code(), + "country_center_not_found" + ); + assert_eq!( + RadrootsLocationResolverError::QueryFailed { + message: "sqlite failed".to_owned(), + } + .code(), + "query_failed" + ); + } +}