diff --git a/fabric/go/fabric.go b/fabric/go/fabric.go index 4fd370e..dd04881 100644 --- a/fabric/go/fabric.go +++ b/fabric/go/fabric.go @@ -428,25 +428,26 @@ func (f *Fabric) updateAnchors(trustID string, kind trustKind) error { return nil } -// Resolve a single handle. Supports dotted names like "hello.alice@bitcoin". -func (f *Fabric) Resolve(handle string) (Resolved, error) { +// Resolve a single handle. Returns nil if not found. Supports dotted names like "hello.alice@bitcoin". +func (f *Fabric) Resolve(handle string) (*Resolved, error) { batch, err := f.ResolveAll([]string{handle}) if err != nil { - return Resolved{}, err + return nil, err } for _, z := range batch.Zones { if z.Handle == handle { - return Resolved{Zone: z, Roots: batch.Roots}, nil + return &Resolved{Zone: z, Roots: batch.Roots}, nil } } - return Resolved{}, &FabricError{Code: "decode", Message: handle + " not found"} + return nil, nil } // ResolveById resolves a numeric ID to a handle by querying relays // for the reverse mapping, then verifying via forward resolution. -func (f *Fabric) ResolveById(numId string) (Resolved, error) { +// Returns nil if not found. +func (f *Fabric) ResolveById(numId string) (*Resolved, error) { if err := f.Bootstrap(); err != nil { - return Resolved{}, err + return nil, err } urls := f.pool.ShuffledURLs(4) var lastErr error = &FabricError{Code: "no_peers", Message: "reverse resolution failed"} @@ -486,6 +487,9 @@ func (f *Fabric) ResolveById(numId string) (Resolved, error) { lastErr = err continue } + if resolved == nil { + continue + } if resolved.Zone.NumId == nil || *resolved.Zone.NumId != numId { lastErr = &FabricError{Code: "verify", Message: fmt.Sprintf("reverse mismatch: expected %s", numId)} @@ -494,7 +498,7 @@ func (f *Fabric) ResolveById(numId string) (Resolved, error) { return resolved, nil } - return Resolved{}, lastErr + return nil, lastErr } // SearchAddr searches for handles by address record, verifies via forward resolution. diff --git a/fabric/js/fabric-core/src/fabric.ts b/fabric/js/fabric-core/src/fabric.ts index b5c49bc..ec37d61 100644 --- a/fabric/js/fabric-core/src/fabric.ts +++ b/fabric/js/fabric-core/src/fabric.ts @@ -427,12 +427,12 @@ export class Fabric { // ── Resolution ── - /** Resolve a single handle. Supports nested names like `hello.alice@bitcoin`. */ - async resolve(handle: string): Promise { + /** Resolve a single handle. Returns null if not found. Supports nested names like `hello.alice@bitcoin`. */ + async resolve(handle: string): Promise { const batch = await this.resolveAll([handle]); const zone = batch.zones.find((z) => z.handle === handle); if (!zone) { - throw new FabricError(`${handle} not found`, "decode"); + return null; } return { zone, roots: batch.roots }; } @@ -451,13 +451,14 @@ export class Fabric { const entry = records.find(r => r.id === numId); if (!entry) continue; - let resolved: Resolved; + let resolved: Resolved | null; try { resolved = await this.resolve(entry.name); } catch (e) { lastErr = e instanceof Error ? e : new FabricError(String(e), "decode"); continue; } + if (!resolved) continue; const json = resolved.zone.toJson(); if (json?.num_id !== numId) { diff --git a/fabric/kotlin/src/main/kotlin/org/spacesprotocol/fabric/Fabric.kt b/fabric/kotlin/src/main/kotlin/org/spacesprotocol/fabric/Fabric.kt index 325a207..f5d7143 100644 --- a/fabric/kotlin/src/main/kotlin/org/spacesprotocol/fabric/Fabric.kt +++ b/fabric/kotlin/src/main/kotlin/org/spacesprotocol/fabric/Fabric.kt @@ -199,14 +199,13 @@ class Fabric( // -- Resolution -- - fun resolve(handle: String): Resolved { + fun resolve(handle: String): Resolved? { val batch = resolveAll(listOf(handle)) - val zone = batch.zones.find { it.handle == handle } - ?: throw FabricError("decode", "$handle not found") + val zone = batch.zones.find { it.handle == handle } ?: return null return Resolved(zone, batch.roots) } - fun resolveById(numId: String): Resolved { + fun resolveById(numId: String): Resolved? { bootstrap() val urls = pool.shuffledUrls(4) var lastErr: Exception = FabricError("no_peers", "reverse resolution failed") @@ -231,12 +230,7 @@ class Fabric( val entry = entries.find { it.id == numId } ?: continue - val resolved = try { - resolve(entry.name) - } catch (e: Exception) { - lastErr = e - continue - } + val resolved = resolve(entry.name) ?: continue if (resolved.zone.numId != numId) { lastErr = FabricError("verify", "reverse mismatch: expected $numId, got ${resolved.zone.numId}") @@ -246,7 +240,7 @@ class Fabric( return resolved } - throw lastErr + return null } fun searchAddr(name: String, addr: String): ResolvedBatch { diff --git a/fabric/python/fabric/client.py b/fabric/python/fabric/client.py index d5d2dd4..1591040 100644 --- a/fabric/python/fabric/client.py +++ b/fabric/python/fabric/client.py @@ -219,15 +219,15 @@ def badge_for(self, sovereignty: str, roots: list[str]) -> str: return BADGE_UNVERIFIED return BADGE_NONE - def resolve(self, handle: str) -> Resolved: + def resolve(self, handle: str) -> Resolved | None: batch = self.resolve_all([handle]) zone = next((z for z in batch.zones if z.handle == handle), None) if zone is None: - raise FabricError("decode", f"{handle} not found") + return None return Resolved(zone=zone, roots=batch.roots) - def resolve_by_id(self, num_id: str) -> Resolved: - """Resolve a numeric ID to a verified handle.""" + def resolve_by_id(self, num_id: str) -> Resolved | None: + """Resolve a numeric ID to a verified handle. Returns None if not found.""" self.bootstrap() urls = self._pool.shuffled_urls(4) last_err: Exception = FabricError("no_peers", "reverse resolution failed") @@ -249,10 +249,8 @@ def resolve_by_id(self, num_id: str) -> Resolved: if entry is None: continue - try: - resolved = self.resolve(entry["name"]) - except Exception as e: - last_err = e + resolved = self.resolve(entry["name"]) + if resolved is None: continue if getattr(resolved.zone, "num_id", None) != num_id: @@ -262,7 +260,7 @@ def resolve_by_id(self, num_id: str) -> Resolved: self._pool.mark_alive(u) return resolved - raise last_err + return None def search_addr(self, name: str, addr: str) -> ResolvedBatch: """Search for handles by address record, verify via forward resolution.""" diff --git a/fabric/swift/Sources/Fabric/Fabric.swift b/fabric/swift/Sources/Fabric/Fabric.swift index 93b27aa..7038419 100644 --- a/fabric/swift/Sources/Fabric/Fabric.swift +++ b/fabric/swift/Sources/Fabric/Fabric.swift @@ -322,17 +322,17 @@ public final class Fabric: @unchecked Sendable { // MARK: - Resolution - /// Resolve a single handle. Supports dotted names like `hello.alice@bitcoin`. - public func resolve(_ handle: String) async throws -> Resolved { + /// Resolve a single handle. Returns nil if not found. Supports dotted names like `hello.alice@bitcoin`. + public func resolve(_ handle: String) async throws -> Resolved? { let batch = try await resolveAll([handle]) guard let zone = batch.zones.first(where: { $0.handle == handle }) else { - throw FabricError.decode("\(handle) not found") + return nil } return Resolved(zone: zone, roots: batch.roots) } - /// Resolve a numeric ID to a verified handle. - public func resolveById(_ numId: String) async throws -> Resolved { + /// Resolve a numeric ID to a verified handle. Returns nil if not found. + public func resolveById(_ numId: String) async throws -> Resolved? { try await bootstrap() let relays = pool.shuffledUrls(4) @@ -347,16 +347,13 @@ public final class Fabric: @unchecked Sendable { guard let entry = entries.first(where: { $0.id == numId }) else { continue } - let resolved: Resolved - do { - resolved = try await resolve(entry.name) - } catch { continue } + guard let resolved = try await resolve(entry.name) else { continue } guard resolved.zone.numId == numId else { continue } return resolved } - throw FabricError.noPeers + return nil } /// Search for handles by address record, verify via forward resolution.