commit fa6802ef6800a3e6cddda266b71fa48598ca3e16
parent ded5fc00f3b24b232682e290c026fb0837360cd4
Author: triesap <tyson@radroots.org>
Date: Fri, 26 Jun 2026 08:37:20 +0000
sdk: promote GeoNames client surface
- Add GeoNames builder configuration and client handle on RadrootsClient.
- Replace public GeoNames helper exports with the target product-client API.
- Map geocoder failures into structured SDK GeoNames error taxonomy.
- Cover the new client and error contract in SDK runtime tests.
Diffstat:
8 files changed, 315 insertions(+), 88 deletions(-)
diff --git a/crates/sdk/src/error.rs b/crates/sdk/src/error.rs
@@ -26,13 +26,16 @@ pub enum RadrootsSdkErrorClass {
pub enum RadrootsSdkRecoveryAction {
RetryOutboxEnqueue,
InspectLocalStores,
+ InspectGeoNamesAsset,
RetryOperationWithSameIdempotencyKey,
ConfigureRelayTargets,
+ ConfigureGeoNamesCache,
ConfigureSigner,
FixRequest,
SelectAuthorizedActor,
CompleteSignerAuthentication,
RetryAfterTransportFailure,
+ RetryGeoNamesDownload,
EnableRequiredFeature,
}
@@ -40,6 +43,19 @@ pub enum RadrootsSdkRecoveryAction {
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
+pub enum RadrootsSdkGeoNamesErrorKind {
+ Configuration,
+ Download,
+ Cache,
+ Integrity,
+ Schema,
+ Lookup,
+}
+
+#[cfg(feature = "runtime")]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
+#[serde(rename_all = "snake_case")]
+#[non_exhaustive]
pub enum RadrootsSdkPartialLocalMutationFailure {
OutboxEnqueue,
OutboxIdempotencyConflict,
@@ -156,6 +172,10 @@ pub enum RadrootsSdkError {
Outbox {
message: String,
},
+ GeoNames {
+ kind: RadrootsSdkGeoNamesErrorKind,
+ message: String,
+ },
RelayTransport {
message: String,
},
@@ -195,6 +215,14 @@ impl RadrootsSdkError {
Self::ListingDraft { .. } => "listing_draft",
Self::ListingMutation { .. } => "listing_mutation",
Self::Outbox { .. } => "outbox",
+ Self::GeoNames { kind, .. } => match kind {
+ RadrootsSdkGeoNamesErrorKind::Configuration => "geonames_configuration",
+ RadrootsSdkGeoNamesErrorKind::Download => "geonames_download",
+ RadrootsSdkGeoNamesErrorKind::Cache => "geonames_cache",
+ RadrootsSdkGeoNamesErrorKind::Integrity => "geonames_integrity",
+ RadrootsSdkGeoNamesErrorKind::Schema => "geonames_schema",
+ RadrootsSdkGeoNamesErrorKind::Lookup => "geonames_lookup",
+ },
Self::RelayTransport { .. } => "relay_transport",
Self::Projection { .. } => "projection",
Self::PartialLocalMutation(_) => "partial_local_mutation",
@@ -207,6 +235,14 @@ impl RadrootsSdkError {
| Self::EventStore { .. }
| Self::Outbox { .. }
| Self::Projection { .. } => RadrootsSdkErrorClass::Storage,
+ Self::GeoNames { kind, .. } => match kind {
+ RadrootsSdkGeoNamesErrorKind::Configuration => RadrootsSdkErrorClass::Configuration,
+ RadrootsSdkGeoNamesErrorKind::Download => RadrootsSdkErrorClass::Transport,
+ RadrootsSdkGeoNamesErrorKind::Cache
+ | RadrootsSdkGeoNamesErrorKind::Integrity
+ | RadrootsSdkGeoNamesErrorKind::Schema => RadrootsSdkErrorClass::Storage,
+ RadrootsSdkGeoNamesErrorKind::Lookup => RadrootsSdkErrorClass::Request,
+ },
Self::ClockBeforeUnixEpoch | Self::TimestampOutOfRange { .. } => {
RadrootsSdkErrorClass::Clock
}
@@ -243,6 +279,11 @@ impl RadrootsSdkError {
| Self::ProductSyncRelaySetupFailure { .. }
| Self::EventStore { .. }
| Self::Outbox { .. }
+ | Self::GeoNames {
+ kind: RadrootsSdkGeoNamesErrorKind::Cache
+ | RadrootsSdkGeoNamesErrorKind::Download,
+ ..
+ }
| Self::RelayTransport { .. }
| Self::SignerRequestTimedOut { .. }
| Self::SignerTransport { .. }
@@ -257,6 +298,20 @@ impl RadrootsSdkError {
| Self::EventStore { .. }
| Self::Outbox { .. }
| Self::Projection { .. } => vec![RadrootsSdkRecoveryAction::InspectLocalStores],
+ Self::GeoNames { kind, .. } => match kind {
+ RadrootsSdkGeoNamesErrorKind::Configuration => {
+ vec![RadrootsSdkRecoveryAction::ConfigureGeoNamesCache]
+ }
+ RadrootsSdkGeoNamesErrorKind::Download => {
+ vec![RadrootsSdkRecoveryAction::RetryGeoNamesDownload]
+ }
+ RadrootsSdkGeoNamesErrorKind::Cache
+ | RadrootsSdkGeoNamesErrorKind::Integrity
+ | RadrootsSdkGeoNamesErrorKind::Schema => {
+ vec![RadrootsSdkRecoveryAction::InspectGeoNamesAsset]
+ }
+ RadrootsSdkGeoNamesErrorKind::Lookup => vec![RadrootsSdkRecoveryAction::FixRequest],
+ },
Self::UnauthorizedActor { .. }
| Self::SignerPubkeyMismatch { .. }
| Self::SignerRequestRejected { .. }
@@ -362,6 +417,7 @@ impl RadrootsSdkError {
| Self::Outbox { message }
| Self::RelayTransport { message }
| Self::Projection { message } => json!({ "message": message }),
+ Self::GeoNames { kind, message } => json!({ "kind": kind, "message": message }),
Self::PartialLocalMutation(error) => json!(error),
};
json!({
@@ -437,6 +493,13 @@ impl RadrootsSdkError {
message: message.into(),
}
}
+
+ pub(crate) fn missing_geonames_config() -> Self {
+ Self::GeoNames {
+ kind: RadrootsSdkGeoNamesErrorKind::Configuration,
+ message: "GeoNames cache root is not configured".to_owned(),
+ }
+ }
}
#[cfg(feature = "runtime")]
@@ -537,6 +600,9 @@ impl fmt::Display for RadrootsSdkError {
write!(f, "sdk listing mutation error: {message}")
}
Self::Outbox { message } => write!(f, "sdk outbox error: {message}"),
+ Self::GeoNames { kind, message } => {
+ write!(f, "sdk GeoNames {kind:?} error: {message}")
+ }
Self::RelayTransport { message } => {
write!(f, "sdk relay transport error: {message}")
}
@@ -609,6 +675,42 @@ impl From<radroots_event_store::RadrootsEventStoreError> for RadrootsSdkError {
}
#[cfg(feature = "runtime")]
+impl From<radroots_geocoder::GeocoderError> for RadrootsSdkError {
+ fn from(error: radroots_geocoder::GeocoderError) -> Self {
+ let kind = match &error {
+ radroots_geocoder::GeocoderError::InvalidAssetUrl { .. }
+ | radroots_geocoder::GeocoderError::InvalidAssetHost { .. } => {
+ RadrootsSdkGeoNamesErrorKind::Configuration
+ }
+ radroots_geocoder::GeocoderError::AssetDownload { .. } => {
+ RadrootsSdkGeoNamesErrorKind::Download
+ }
+ radroots_geocoder::GeocoderError::Io(_)
+ | radroots_geocoder::GeocoderError::Sqlite(_)
+ | radroots_geocoder::GeocoderError::AssetLockUnavailable { .. } => {
+ RadrootsSdkGeoNamesErrorKind::Cache
+ }
+ radroots_geocoder::GeocoderError::InvalidAssetSchema { .. } => {
+ RadrootsSdkGeoNamesErrorKind::Schema
+ }
+ radroots_geocoder::GeocoderError::InvalidAssetLength { .. }
+ | radroots_geocoder::GeocoderError::InvalidAssetSha256 { .. }
+ | radroots_geocoder::GeocoderError::InvalidAssetSqlite { .. }
+ | radroots_geocoder::GeocoderError::InvalidAssetIntegrity { .. } => {
+ RadrootsSdkGeoNamesErrorKind::Integrity
+ }
+ radroots_geocoder::GeocoderError::CountryCenterNotFound { .. } => {
+ RadrootsSdkGeoNamesErrorKind::Lookup
+ }
+ };
+ Self::GeoNames {
+ kind,
+ message: error.to_string(),
+ }
+ }
+}
+
+#[cfg(feature = "runtime")]
impl From<radroots_trade::listing::RadrootsListingDraftError> for RadrootsSdkError {
fn from(error: radroots_trade::listing::RadrootsListingDraftError) -> Self {
match error {
diff --git a/crates/sdk/src/geonames.rs b/crates/sdk/src/geonames.rs
@@ -2,6 +2,7 @@
use std::path::{Path, PathBuf};
+use crate::{GeoNamesClient, RadrootsSdkError};
pub use radroots_geocoder::{
GEONAMES_1_0_ASSET, GEONAMES_ASSET_BYTE_SIZE, GEONAMES_ASSET_FILE_NAME, GEONAMES_ASSET_HOST,
GEONAMES_ASSET_SHA256, GEONAMES_ASSET_URL, GEONAMES_ASSET_VERSION, GeoNamesAssetFetcher,
@@ -18,72 +19,123 @@ use radroots_runtime_paths::{
default_shared_geonames_root_from_cache_root,
};
-pub fn radroots_sdk_geonames_database_path_from_cache_root(
- cache_root: impl AsRef<Path>,
-) -> PathBuf {
- default_shared_geonames_database_path_from_cache_root(cache_root, GEONAMES_ASSET_VERSION)
+#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
+pub struct RadrootsGeoNamesConfig {
+ pub cache_root: PathBuf,
}
-pub fn radroots_sdk_geonames_database_path_from_cache_root_for_version(
- cache_root: impl AsRef<Path>,
- version: &str,
-) -> PathBuf {
- default_shared_geonames_database_path_from_cache_root(cache_root, version)
-}
+impl RadrootsGeoNamesConfig {
+ pub fn new(cache_root: impl Into<PathBuf>) -> Self {
+ Self {
+ cache_root: cache_root.into(),
+ }
+ }
-pub fn radroots_sdk_geonames_root_from_cache_root(cache_root: impl AsRef<Path>) -> PathBuf {
- default_shared_geonames_root_from_cache_root(cache_root)
-}
+ pub fn root_path(&self) -> PathBuf {
+ default_shared_geonames_root_from_cache_root(&self.cache_root)
+ }
-pub fn radroots_sdk_inspect_geonames_database_in_cache_root(
- cache_root: impl AsRef<Path>,
-) -> Result<GeoNamesAssetStatus, GeocoderError> {
- inspect_default_geonames_asset_in_cache_root(cache_root)
-}
+ pub fn database_path(&self) -> PathBuf {
+ geonames_database_path_from_cache_root(&self.cache_root)
+ }
-pub fn radroots_sdk_inspect_geonames_database_path_with_spec(
- path: impl AsRef<Path>,
- spec: &GeoNamesAssetSpec,
-) -> Result<GeoNamesAssetStatus, GeocoderError> {
- inspect_geonames_asset_path(path, spec)
+ pub fn database_path_for_version(&self, version: &str) -> PathBuf {
+ geonames_database_path_from_cache_root_for_version(&self.cache_root, version)
+ }
}
-pub fn radroots_sdk_ensure_geonames_database_in_cache_root(
- cache_root: impl AsRef<Path>,
-) -> Result<GeoNamesAssetStatus, GeocoderError> {
- ensure_default_geonames_asset_in_cache_root(cache_root)
-}
+impl<'sdk> GeoNamesClient<'sdk> {
+ pub fn config(&self) -> Option<&RadrootsGeoNamesConfig> {
+ self.sdk.geonames_config()
+ }
-pub fn radroots_sdk_ensure_geonames_database_in_cache_root_with_fetcher<F>(
- cache_root: impl AsRef<Path>,
- fetcher: &F,
-) -> Result<GeoNamesAssetStatus, GeocoderError>
-where
- F: GeoNamesAssetFetcher,
-{
- ensure_geonames_asset_in_cache_root_with_fetcher(cache_root, &GEONAMES_1_0_ASSET, fetcher)
-}
+ pub fn root_path(&self) -> Result<PathBuf, RadrootsSdkError> {
+ Ok(self.required_config()?.root_path())
+ }
-pub fn radroots_sdk_ensure_geonames_database_in_cache_root_with_spec_and_fetcher<F>(
- cache_root: impl AsRef<Path>,
- spec: &GeoNamesAssetSpec,
- fetcher: &F,
-) -> Result<GeoNamesAssetStatus, GeocoderError>
-where
- F: GeoNamesAssetFetcher,
-{
- ensure_geonames_asset_in_cache_root_with_fetcher(cache_root, spec, fetcher)
+ pub fn database_path(&self) -> Result<PathBuf, RadrootsSdkError> {
+ Ok(self.required_config()?.database_path())
+ }
+
+ pub fn database_path_for_version(&self, version: &str) -> Result<PathBuf, RadrootsSdkError> {
+ Ok(self.required_config()?.database_path_for_version(version))
+ }
+
+ pub fn inspect(&self) -> Result<GeoNamesAssetStatus, RadrootsSdkError> {
+ inspect_default_geonames_asset_in_cache_root(&self.required_config()?.cache_root)
+ .map_err(RadrootsSdkError::from)
+ }
+
+ pub fn inspect_path_with_spec(
+ &self,
+ path: impl AsRef<Path>,
+ spec: &GeoNamesAssetSpec,
+ ) -> Result<GeoNamesAssetStatus, RadrootsSdkError> {
+ inspect_geonames_asset_path(path, spec).map_err(RadrootsSdkError::from)
+ }
+
+ pub fn ensure(&self) -> Result<GeoNamesAssetStatus, RadrootsSdkError> {
+ ensure_default_geonames_asset_in_cache_root(&self.required_config()?.cache_root)
+ .map_err(RadrootsSdkError::from)
+ }
+
+ pub fn ensure_with_fetcher<F>(
+ &self,
+ fetcher: &F,
+ ) -> Result<GeoNamesAssetStatus, RadrootsSdkError>
+ where
+ F: GeoNamesAssetFetcher,
+ {
+ ensure_geonames_asset_in_cache_root_with_fetcher(
+ &self.required_config()?.cache_root,
+ &GEONAMES_1_0_ASSET,
+ fetcher,
+ )
+ .map_err(RadrootsSdkError::from)
+ }
+
+ pub fn ensure_with_spec_and_fetcher<F>(
+ &self,
+ spec: &GeoNamesAssetSpec,
+ fetcher: &F,
+ ) -> Result<GeoNamesAssetStatus, RadrootsSdkError>
+ where
+ F: GeoNamesAssetFetcher,
+ {
+ ensure_geonames_asset_in_cache_root_with_fetcher(
+ &self.required_config()?.cache_root,
+ spec,
+ fetcher,
+ )
+ .map_err(RadrootsSdkError::from)
+ }
+
+ pub fn open_verified(&self) -> Result<Geocoder, RadrootsSdkError> {
+ Geocoder::open_verified_geonames_asset(self.database_path()?, &GEONAMES_1_0_ASSET)
+ .map_err(RadrootsSdkError::from)
+ }
+
+ pub fn open_verified_path_with_spec(
+ &self,
+ path: impl AsRef<Path>,
+ spec: &GeoNamesAssetSpec,
+ ) -> Result<Geocoder, RadrootsSdkError> {
+ Geocoder::open_verified_geonames_asset(path, spec).map_err(RadrootsSdkError::from)
+ }
+
+ fn required_config(&self) -> Result<&RadrootsGeoNamesConfig, RadrootsSdkError> {
+ self.config()
+ .ok_or_else(RadrootsSdkError::missing_geonames_config)
+ }
}
-pub fn radroots_sdk_open_verified_geonames_database(
- path: impl AsRef<Path>,
-) -> Result<Geocoder, GeocoderError> {
- Geocoder::open_verified_geonames_asset(path, &GEONAMES_1_0_ASSET)
+fn geonames_database_path_from_cache_root(cache_root: impl AsRef<Path>) -> PathBuf {
+ default_shared_geonames_database_path_from_cache_root(cache_root, GEONAMES_ASSET_VERSION)
}
-pub fn radroots_sdk_open_verified_geonames_database_with_spec(
- path: impl AsRef<Path>,
- spec: &GeoNamesAssetSpec,
-) -> Result<Geocoder, GeocoderError> {
- Geocoder::open_verified_geonames_asset(path, spec)
+fn geonames_database_path_from_cache_root_for_version(
+ cache_root: impl AsRef<Path>,
+ version: &str,
+) -> PathBuf {
+ default_shared_geonames_database_path_from_cache_root(cache_root, version)
}
diff --git a/crates/sdk/src/lib.rs b/crates/sdk/src/lib.rs
@@ -47,8 +47,9 @@ mod workflow_runtime;
#[cfg(feature = "runtime")]
pub use crate::error::{
- RadrootsSdkError, RadrootsSdkErrorClass, RadrootsSdkPartialLocalMutationError,
- RadrootsSdkPartialLocalMutationFailure, RadrootsSdkRecoveryAction,
+ RadrootsSdkError, RadrootsSdkErrorClass, RadrootsSdkGeoNamesErrorKind,
+ RadrootsSdkPartialLocalMutationError, RadrootsSdkPartialLocalMutationFailure,
+ RadrootsSdkRecoveryAction,
};
#[cfg(feature = "runtime")]
pub use crate::farms_runtime::{
@@ -61,16 +62,7 @@ pub use crate::geonames::{
GEONAMES_ASSET_SHA256, GEONAMES_ASSET_URL, GEONAMES_ASSET_VERSION, GeoNamesAssetFetcher,
GeoNamesAssetSpec, GeoNamesAssetState, GeoNamesAssetStatus, GeoNamesBlockingHttpFetcher,
Geocoder, GeocoderCountryListResult, GeocoderError, GeocoderPoint, GeocoderReverseOptions,
- GeocoderReverseResult, radroots_sdk_ensure_geonames_database_in_cache_root,
- radroots_sdk_ensure_geonames_database_in_cache_root_with_fetcher,
- radroots_sdk_ensure_geonames_database_in_cache_root_with_spec_and_fetcher,
- radroots_sdk_geonames_database_path_from_cache_root,
- radroots_sdk_geonames_database_path_from_cache_root_for_version,
- radroots_sdk_geonames_root_from_cache_root,
- radroots_sdk_inspect_geonames_database_in_cache_root,
- radroots_sdk_inspect_geonames_database_path_with_spec,
- radroots_sdk_open_verified_geonames_database,
- radroots_sdk_open_verified_geonames_database_with_spec,
+ GeocoderReverseResult, RadrootsGeoNamesConfig,
};
#[cfg(feature = "runtime")]
pub use crate::idempotency::{SDK_IDEMPOTENCY_KEY_MAX_LEN, SdkIdempotencyKey};
@@ -100,7 +92,7 @@ pub use crate::orders_runtime::{
};
#[cfg(feature = "runtime")]
pub use crate::product_clients::{
- DvmClient, FarmsClient, ListingsClient, MarketClient, SyncClient, TradesClient,
+ DvmClient, FarmsClient, GeoNamesClient, ListingsClient, MarketClient, SyncClient, TradesClient,
};
#[cfg(feature = "runtime")]
pub use crate::relay_targets::{
diff --git a/crates/sdk/src/product_clients.rs b/crates/sdk/src/product_clients.rs
@@ -42,6 +42,19 @@ impl<'client> MarketClient<'client> {
#[cfg(feature = "runtime")]
#[derive(Clone, Copy)]
+pub struct GeoNamesClient<'client> {
+ pub(crate) sdk: &'client RadrootsClient,
+}
+
+#[cfg(feature = "runtime")]
+impl<'client> GeoNamesClient<'client> {
+ pub(crate) fn new(sdk: &'client RadrootsClient) -> Self {
+ Self { sdk }
+ }
+}
+
+#[cfg(feature = "runtime")]
+#[derive(Clone, Copy)]
pub struct TradesClient<'client> {
pub(crate) sdk: &'client RadrootsClient,
}
diff --git a/crates/sdk/src/runtime.rs b/crates/sdk/src/runtime.rs
@@ -1,7 +1,7 @@
#[cfg(feature = "runtime")]
use crate::{
- DvmClient, FarmsClient, ListingsClient, MarketClient, RadrootsSdkError, SdkRelayTargetSet,
- SdkRelayUrlPolicy, SyncClient, TradesClient,
+ DvmClient, FarmsClient, GeoNamesClient, ListingsClient, MarketClient, RadrootsGeoNamesConfig,
+ RadrootsSdkError, SdkRelayTargetSet, SdkRelayUrlPolicy, SyncClient, TradesClient,
};
#[cfg(all(feature = "runtime", feature = "signer-adapters"))]
use crate::{
@@ -392,6 +392,7 @@ pub struct RestoreReceipt {
#[derive(Clone)]
pub struct RadrootsClientBuilder {
storage: RadrootsSdkStorageConfig,
+ geonames: Option<RadrootsGeoNamesConfig>,
clock: RadrootsSdkClock,
relay_urls: Vec<String>,
relay_url_policy: SdkRelayUrlPolicy,
@@ -405,6 +406,7 @@ impl Default for RadrootsClientBuilder {
fn default() -> Self {
Self {
storage: RadrootsSdkStorageConfig::Memory,
+ geonames: None,
clock: RadrootsSdkClock::System,
relay_urls: Vec::new(),
relay_url_policy: SdkRelayUrlPolicy::Public,
@@ -427,6 +429,16 @@ impl RadrootsClientBuilder {
self
}
+ pub fn geonames_config(mut self, geonames: RadrootsGeoNamesConfig) -> Self {
+ self.geonames = Some(geonames);
+ self
+ }
+
+ pub fn geonames_cache_root(mut self, cache_root: impl Into<PathBuf>) -> Self {
+ self.geonames = Some(RadrootsGeoNamesConfig::new(cache_root));
+ self
+ }
+
pub fn clock(mut self, clock: RadrootsSdkClock) -> Self {
self.clock = clock;
self
@@ -466,6 +478,7 @@ impl RadrootsClientBuilder {
_event_store: storage.event_store,
_outbox: storage.outbox,
storage_paths: storage.paths,
+ geonames: self.geonames,
clock: self.clock,
relay_urls,
publish_transport: self.publish_transport,
@@ -481,6 +494,7 @@ pub struct RadrootsClient {
pub(crate) _event_store: RadrootsEventStore,
pub(crate) _outbox: RadrootsOutbox,
storage_paths: Option<RadrootsSdkStoragePaths>,
+ geonames: Option<RadrootsGeoNamesConfig>,
clock: RadrootsSdkClock,
relay_urls: Vec<String>,
publish_transport: SdkPublishTransport,
@@ -506,6 +520,10 @@ impl RadrootsClient {
MarketClient::new(self)
}
+ pub fn geonames(&self) -> GeoNamesClient<'_> {
+ GeoNamesClient::new(self)
+ }
+
pub fn trades(&self) -> TradesClient<'_> {
TradesClient::new(self)
}
@@ -561,6 +579,10 @@ impl RadrootsClient {
self.storage_paths.as_ref()
}
+ pub fn geonames_config(&self) -> Option<&RadrootsGeoNamesConfig> {
+ self.geonames.as_ref()
+ }
+
pub async fn storage_status(
&self,
_request: StorageStatusRequest,
diff --git a/crates/sdk/tests/geonames.rs b/crates/sdk/tests/geonames.rs
@@ -1,41 +1,50 @@
#![cfg(feature = "runtime")]
use radroots_sdk::{
- GEONAMES_ASSET_HOST, GEONAMES_ASSET_VERSION, GeoNamesAssetState,
- radroots_sdk_geonames_database_path_from_cache_root,
- radroots_sdk_geonames_database_path_from_cache_root_for_version,
- radroots_sdk_geonames_root_from_cache_root,
- radroots_sdk_inspect_geonames_database_in_cache_root,
+ GEONAMES_ASSET_HOST, GEONAMES_ASSET_VERSION, GeoNamesAssetState, RadrootsClient,
+ RadrootsGeoNamesConfig, RadrootsSdkError, RadrootsSdkErrorClass, RadrootsSdkGeoNamesErrorKind,
+ RadrootsSdkRecoveryAction,
};
-#[test]
-fn sdk_geonames_facade_resolves_shared_cache_paths_and_reports_missing_state() {
+#[tokio::test]
+async fn sdk_geonames_client_resolves_shared_cache_paths_and_reports_missing_state() {
let tempdir = tempfile::tempdir().expect("tempdir");
let cache_root = tempdir.path().join("cache");
+ let sdk = RadrootsClient::builder()
+ .geonames_cache_root(cache_root.clone())
+ .build()
+ .await
+ .expect("sdk");
+ let geonames = sdk.geonames();
assert_eq!(GEONAMES_ASSET_HOST, "assets.radroots.io");
assert_eq!(GEONAMES_ASSET_VERSION, "1.0");
assert_eq!(
- radroots_sdk_geonames_root_from_cache_root(&cache_root),
+ sdk.geonames_config(),
+ Some(&RadrootsGeoNamesConfig::new(cache_root.clone()))
+ );
+ assert_eq!(
+ geonames.root_path().expect("geonames root"),
cache_root.join("shared").join("geonames")
);
assert_eq!(
- radroots_sdk_geonames_database_path_from_cache_root(&cache_root),
+ geonames.database_path().expect("geonames database path"),
cache_root
.join("shared")
.join("geonames")
.join("geonames-1.0.db")
);
assert_eq!(
- radroots_sdk_geonames_database_path_from_cache_root_for_version(&cache_root, "1.1"),
+ geonames
+ .database_path_for_version("1.1")
+ .expect("geonames version path"),
cache_root
.join("shared")
.join("geonames")
.join("geonames-1.1.db")
);
- let status =
- radroots_sdk_inspect_geonames_database_in_cache_root(&cache_root).expect("inspection");
+ let status = geonames.inspect().expect("inspection");
assert_eq!(status.state, GeoNamesAssetState::Missing);
assert_eq!(status.version, "1.0");
assert_eq!(
@@ -46,3 +55,27 @@ fn sdk_geonames_facade_resolves_shared_cache_paths_and_reports_missing_state() {
.join("geonames-1.0.db")
);
}
+
+#[tokio::test]
+async fn sdk_geonames_client_reports_missing_config_as_structured_error() {
+ let sdk = RadrootsClient::builder().build().await.expect("sdk");
+ let error = sdk
+ .geonames()
+ .database_path()
+ .expect_err("missing geonames config");
+
+ match &error {
+ RadrootsSdkError::GeoNames { kind, .. } => {
+ assert_eq!(*kind, RadrootsSdkGeoNamesErrorKind::Configuration);
+ }
+ other => panic!("expected geonames config error, got {other}"),
+ }
+ assert_eq!(error.code(), "geonames_configuration");
+ assert_eq!(error.class(), RadrootsSdkErrorClass::Configuration);
+ assert!(!error.retryable());
+ assert_eq!(
+ error.recovery_actions(),
+ vec![RadrootsSdkRecoveryAction::ConfigureGeoNamesCache]
+ );
+ assert_eq!(error.detail_json()["detail"]["kind"], "configuration");
+}
diff --git a/crates/sdk/tests/runtime_foundation.rs b/crates/sdk/tests/runtime_foundation.rs
@@ -2,12 +2,12 @@
use radroots_sdk::{
BackupRequest, IntegrityRequest, LISTING_PUBLISH_OPERATION_KIND, RadrootsClient,
- RadrootsSdkClock, RadrootsSdkError, RadrootsSdkErrorClass, RadrootsSdkRecoveryAction,
- RadrootsSdkStorageConfig, RadrootsSdkTimestamp, RestoreRequest, SDK_IDEMPOTENCY_KEY_MAX_LEN,
- SDK_RELAY_TARGET_MAX_COUNT, SdkBackupState, SdkBackupVerification, SdkEventStoreStorageStatus,
- SdkIdempotencyKey, SdkOutboxStorageStatus, SdkRelayTargetPolicy, SdkRelayTargetSet,
- SdkRelayUrlPolicy, SdkRestoreState, SdkSqliteStoreStatus, SdkStorageKind, StorageStatusReceipt,
- StorageStatusRequest,
+ RadrootsSdkClock, RadrootsSdkError, RadrootsSdkErrorClass, RadrootsSdkGeoNamesErrorKind,
+ RadrootsSdkRecoveryAction, RadrootsSdkStorageConfig, RadrootsSdkTimestamp, RestoreRequest,
+ SDK_IDEMPOTENCY_KEY_MAX_LEN, SDK_RELAY_TARGET_MAX_COUNT, SdkBackupState, SdkBackupVerification,
+ SdkEventStoreStorageStatus, SdkIdempotencyKey, SdkOutboxStorageStatus, SdkRelayTargetPolicy,
+ SdkRelayTargetSet, SdkRelayUrlPolicy, SdkRestoreState, SdkSqliteStoreStatus, SdkStorageKind,
+ StorageStatusReceipt, StorageStatusRequest,
};
use std::path::PathBuf;
@@ -19,6 +19,7 @@ async fn sdk_builder_defaults_to_memory_storage_and_no_relays() {
assert!(sdk.storage_paths().is_none());
let _listings = sdk.listings();
let _market = sdk.market();
+ let _geonames = sdk.geonames();
let _trades = sdk.trades();
let _sync = sdk.sync();
let _dvm = sdk.dvm();
@@ -471,6 +472,16 @@ fn sdk_error_contract_methods_cover_all_variants() {
vec![RadrootsSdkRecoveryAction::InspectLocalStores],
),
(
+ RadrootsSdkError::GeoNames {
+ kind: RadrootsSdkGeoNamesErrorKind::Download,
+ message: "download".to_owned(),
+ },
+ "geonames_download",
+ RadrootsSdkErrorClass::Transport,
+ true,
+ vec![RadrootsSdkRecoveryAction::RetryGeoNamesDownload],
+ ),
+ (
RadrootsSdkError::RelayTransport {
message: "relay".to_owned(),
},
diff --git a/crates/sdk/tests/unit/runtime_tests.rs b/crates/sdk/tests/unit/runtime_tests.rs
@@ -128,6 +128,7 @@ async fn open_storage_and_storage_kind_cover_memory_directory_and_file_failures(
_event_store: memory.event_store,
_outbox: memory.outbox,
storage_paths: None,
+ geonames: None,
clock: RadrootsSdkClock::Fixed(RadrootsSdkTimestamp::from_unix_seconds(1)),
relay_urls: Vec::new(),
publish_transport: SdkPublishTransport::DirectNostrRelay,
@@ -148,6 +149,7 @@ async fn open_storage_and_storage_kind_cover_memory_directory_and_file_failures(
_event_store: directory_storage.event_store,
_outbox: directory_storage.outbox,
storage_paths: Some(directory_paths),
+ geonames: None,
clock: RadrootsSdkClock::Fixed(RadrootsSdkTimestamp::from_unix_seconds(1)),
relay_urls: Vec::new(),
publish_transport: SdkPublishTransport::DirectNostrRelay,