Skip to content

feat(nip05)!: return Nip05ResolveResult from resolve()#635

Open
nogringo wants to merge 3 commits into
masterfrom
feat/nip05-resolve-result
Open

feat(nip05)!: return Nip05ResolveResult from resolve()#635
nogringo wants to merge 3 commits into
masterfrom
feat/nip05-resolve-result

Conversation

@nogringo
Copy link
Copy Markdown
Collaborator

@nogringo nogringo commented May 20, 2026

resolve() now returns a sealed Nip05ResolveResult (Nip05Found, Nip05NotFound, Nip05ResolveError) instead of Nip05?, so callers can distinguish "user not in the nostr.json file" from "server unreachable or invalid response".

BREAKING CHANGE: resolve() return type changed from Future<Nip05?> to Future. Switch on the sealed result instead of null-checking the returned Nip05.

Summary by CodeRabbit

  • New Features

    • NIP-05 resolution now returns explicit, typed outcomes: success with resolved data, user-not-found, or detailed error carrying the underlying cause.
    • The new result types are publicly available for consumers.
  • Breaking Changes

    • The resolve API returns a structured result type instead of a nullable value; callers must handle the new result variants.
  • Tests

    • Test suite updated to assert the new typed resolve outcomes, cache behavior, and in-flight deduplication return types.

Review Change Stack

resolve() now returns a sealed Nip05ResolveResult (Nip05Found,
Nip05NotFound, Nip05ResolveError) instead of Nip05?, so callers can
distinguish "user not in the nostr.json file" from "server unreachable
or invalid response".

BREAKING CHANGE: resolve() return type changed from Future<Nip05?> to
Future<Nip05ResolveResult>. Switch on the sealed result instead of
null-checking the returned Nip05.
@nogringo nogringo self-assigned this May 20, 2026
@nogringo nogringo requested review from 1-leo and frnandu May 20, 2026 15:43
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 10f688b2-502d-4c4e-aaf3-abb0141fc8af

📥 Commits

Reviewing files that changed from the base of the PR and between 6d0240a and f2d73b1.

📒 Files selected for processing (2)
  • packages/ndk/lib/domain_layer/entities/nip_05_resolve_result.dart
  • packages/ndk/lib/domain_layer/usecases/nip05/nip_05.dart

📝 Walkthrough

Walkthrough

Adds a sealed Nip05ResolveResult type, changes Nip05Usecase.resolve to return typed outcomes (Nip05Found, Nip05NotFound, Nip05ResolveError) with updated in-flight deduplication and caching, exports the new type, updates tests to assert typed results, and applies a small formatting tweak to Nip46EventSigner.

Changes

NIP-05 Resolution Result Type

Layer / File(s) Summary
Result type contract and export
packages/ndk/lib/domain_layer/entities/nip_05_resolve_result.dart, packages/ndk/lib/entities.dart
Nip05ResolveResult sealed type with Nip05Found(Nip05 data), Nip05NotFound(), and Nip05ResolveError subtypes (Nip05ResolveNetworkError, Nip05ResolveInvalidResponse) is added and exported.
Nip05Usecase.resolve API and implementation
packages/ndk/lib/domain_layer/usecases/nip05/nip_05.dart
Public signature changed from Future<Nip05?> to Future<Nip05ResolveResult>; replaces _inFlightFetches with _inFlightResolves; maps cache hits, repository nulls, and exceptions to typed outcomes and caches successful resolutions.
Test suite validation
packages/ndk/test/usecases/nip05/nip05_network_test.dart
Tests import the new result types and assert Nip05Found (verifying data fields and relays), Nip05NotFound, and Nip05ResolveError (with non-null cause); cache and deduplication tests updated accordingly.

Minor formatting

Layer / File(s) Summary
Nip46EventSigner declaration formatting
packages/ndk/lib/data_layer/repositories/signers/nip46_event_signer.dart
Single-line reformat of the Nip46EventSigner class declaration; no behavioral or API changes.

Sequence Diagram

sequenceDiagram
  participant Client
  participant Nip05Usecase
  participant Cache
  participant Repository
  Client->>Nip05Usecase: resolve(nip05)
  Nip05Usecase->>Cache: lookup(nip05)
  alt found in cache
    Cache-->>Nip05Usecase: Nip05
    Nip05Usecase-->>Client: Nip05Found(data)
  else not in cache
    Nip05Usecase->>Repository: fetch(nip05)
    alt repository returns Nip05
      Repository-->>Nip05Usecase: Nip05
      Nip05Usecase->>Cache: save(result)
      Nip05Usecase-->>Client: Nip05Found(data)
    else repository returns null
      Repository-->>Nip05Usecase: null
      Nip05Usecase-->>Client: Nip05NotFound()
    else repository throws
      Repository-->>Nip05Usecase: exception
      Nip05Usecase-->>Client: Nip05ResolveError(cause)
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • frnandu
  • 1-leo

Poem

🐰 A result type blooms in three shades so clear,
Found, NotFound, Error—no nullable fear,
Cache and fetch dance with dedupe in stride,
Tests sing the outcomes with certainty wide,
Tiny formatting hop keeps the codebase neat.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: introducing a new Nip05ResolveResult return type for the resolve() method, with a breaking change indicator.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/nip05-resolve-result

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/ndk/test/usecases/nip05/nip05_network_test.dart (1)

405-407: ⚡ Quick win

Use identity assertion instead of hashCode for dedup verification.

At Line 406 and Line 407, comparing hashCode is an indirect check. Prefer same(results[1]) / same(results[2]) against results[0] for deterministic dedup validation.

Suggested test change
-      expect(results[0].hashCode, equals(results[1].hashCode));
-      expect(results[1].hashCode, equals(results[2].hashCode));
+      expect(results[0], same(results[1]));
+      expect(results[1], same(results[2]));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ndk/test/usecases/nip05/nip05_network_test.dart` around lines 405 -
407, The test currently verifies deduplication by comparing hashCode on the
Nip05Found instances; instead replace those indirect checks with identity
assertions so the test ensures the same instance is returned. Update the
assertions that use results[0].hashCode, results[1].hashCode and
results[2].hashCode to use expect(results[1], same(results[0])) and
expect(results[2], same(results[0])) (keeping the first expect(results[0],
isA<Nip05Found>()) intact) to assert object identity deterministically.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/ndk/lib/domain_layer/usecases/nip05/nip_05.dart`:
- Line 11: The `_inFlightResolves` map is declared static, causing
cross-instance deduping that can return results tied to another Nip05Usecase’s
`_database` and `_nip05Repository`; change `_inFlightResolves` to an instance
field on `Nip05Usecase` (remove `static`) and update all usages in `resolve()`
and the related block around lines 107-119 to reference the instance field
(`this._inFlightResolves`) so in-flight resolves are scoped per `Nip05Usecase`
instance and cannot mix request contexts across instances.

---

Nitpick comments:
In `@packages/ndk/test/usecases/nip05/nip05_network_test.dart`:
- Around line 405-407: The test currently verifies deduplication by comparing
hashCode on the Nip05Found instances; instead replace those indirect checks with
identity assertions so the test ensures the same instance is returned. Update
the assertions that use results[0].hashCode, results[1].hashCode and
results[2].hashCode to use expect(results[1], same(results[0])) and
expect(results[2], same(results[0])) (keeping the first expect(results[0],
isA<Nip05Found>()) intact) to assert object identity deterministically.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 435d30fa-54a6-44f4-8d6d-7a6ac46c457e

📥 Commits

Reviewing files that changed from the base of the PR and between 5e3186d and cae997b.

📒 Files selected for processing (5)
  • packages/ndk/lib/data_layer/repositories/signers/nip46_event_signer.dart
  • packages/ndk/lib/domain_layer/entities/nip_05_resolve_result.dart
  • packages/ndk/lib/domain_layer/usecases/nip05/nip_05.dart
  • packages/ndk/lib/entities.dart
  • packages/ndk/test/usecases/nip05/nip05_network_test.dart

// Static map to keep track of in-flight requests
static final Map<String, Future<Nip05>> _inFlightRequests = {};
static final Map<String, Future<Nip05?>> _inFlightFetches = {};
static final Map<String, Future<Nip05ResolveResult>> _inFlightResolves = {};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid static cross-instance dedup state for resolve().

Line 11 makes _inFlightResolves global across all Nip05Usecase instances, but resolve() depends on instance-specific _database and _nip05Repository. This can mix request contexts and return another instance’s in-flight result for the same identifier.

Suggested fix
-  static final Map<String, Future<Nip05ResolveResult>> _inFlightResolves = {};
+  final Map<String, Future<Nip05ResolveResult>> _inFlightResolves = {};

Also applies to: 107-119

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ndk/lib/domain_layer/usecases/nip05/nip_05.dart` at line 11, The
`_inFlightResolves` map is declared static, causing cross-instance deduping that
can return results tied to another Nip05Usecase’s `_database` and
`_nip05Repository`; change `_inFlightResolves` to an instance field on
`Nip05Usecase` (remove `static`) and update all usages in `resolve()` and the
related block around lines 107-119 to reference the instance field
(`this._inFlightResolves`) so in-flight resolves are scoped per `Nip05Usecase`
instance and cannot mix request contexts across instances.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 20, 2026

Codecov Report

❌ Patch coverage is 78.57143% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.02%. Comparing base (5e3186d) to head (f2d73b1).
⚠️ Report is 8 commits behind head on master.

Files with missing lines Patch % Lines
...b/domain_layer/entities/nip_05_resolve_result.dart 60.00% 4 Missing ⚠️
...es/ndk/lib/domain_layer/usecases/nip05/nip_05.dart 88.88% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #635      +/-   ##
==========================================
- Coverage   72.03%   72.02%   -0.01%     
==========================================
  Files         209      210       +1     
  Lines       10653    10672      +19     
==========================================
+ Hits         7674     7687      +13     
- Misses       2979     2985       +6     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Contributor

@1-leo 1-leo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the code comments. Just the cause field needs a type

/// (DNS, timeout, connection refused), non-2xx HTTP responses (e.g. 404 / 500),
/// malformed JSON bodies, and unexpected response schemas.
class Nip05ResolveError extends Nip05ResolveResult {
final Object cause;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can the cause be typed?

Nip05ResolveError is now sealed with two subtypes: Nip05ResolveNetworkError
(fetch failed: DNS, timeout, non-2xx response) and Nip05ResolveInvalidResponse
(body could not be parsed). Callers can switch on the subtype to tell a
transient transport failure from a misconfigured server.

BREAKING CHANGE: Nip05ResolveError can no longer be constructed directly;
instantiate one of its sealed subtypes instead.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/ndk/lib/domain_layer/usecases/nip05/nip_05.dart (1)

123-129: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Cache write failures are being reported as resolve failures.

If fetchNip05 succeeds but saveNip05 throws, resolve() returns Nip05ResolveNetworkError even though the identifier was resolved. Scope the error mapping to fetch/parsing and handle cache persistence separately.

Suggested fix
 Future<Nip05ResolveResult> _performResolve(String nip05) async {
+  Nip05? result;
   try {
-    final result = await _nip05Repository.fetchNip05(nip05);
-    if (result == null) {
-      return const Nip05NotFound();
-    }
-    await _database.saveNip05(result);
-    return Nip05Found(result);
+    result = await _nip05Repository.fetchNip05(nip05);
     } on FormatException catch (e) {
       return Nip05ResolveInvalidResponse(e);
     } on TypeError catch (e) {
       return Nip05ResolveInvalidResponse(e);
     } catch (e) {
       return Nip05ResolveNetworkError(e);
     }
+
+    if (result == null) {
+      return const Nip05NotFound();
+    }
+
+    try {
+      await _database.saveNip05(result);
+    } catch (_) {
+      // Optional: log cache persistence failure.
+    }
+    return Nip05Found(result);
 }

Also applies to: 134-136

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ndk/lib/domain_layer/usecases/nip05/nip_05.dart` around lines 123 -
129, The current try/catch wraps both network parsing (fetchNip05) and cache
writes (saveNip05) so a cache write failure is incorrectly mapped to
Nip05ResolveNetworkError; change resolve() to separate concerns: put
_nip05Repository.fetchNip05(nip05) in its own try/catch and map only that catch
to Nip05ResolveNetworkError (and return Nip05NotFound when result==null), then
call _database.saveNip05(result) in a second try/catch so a save failure is
handled separately (e.g., log the cache write failure and still return
Nip05Found(result) or return a distinct cache error if one exists); apply the
same separation for the similar block referenced around the 134-136 area.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/ndk/lib/domain_layer/usecases/nip05/nip_05.dart`:
- Around line 123-129: The current try/catch wraps both network parsing
(fetchNip05) and cache writes (saveNip05) so a cache write failure is
incorrectly mapped to Nip05ResolveNetworkError; change resolve() to separate
concerns: put _nip05Repository.fetchNip05(nip05) in its own try/catch and map
only that catch to Nip05ResolveNetworkError (and return Nip05NotFound when
result==null), then call _database.saveNip05(result) in a second try/catch so a
save failure is handled separately (e.g., log the cache write failure and still
return Nip05Found(result) or return a distinct cache error if one exists); apply
the same separation for the similar block referenced around the 134-136 area.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4d94af01-d5f2-43e9-a505-fffa71e89179

📥 Commits

Reviewing files that changed from the base of the PR and between cae997b and 6d0240a.

📒 Files selected for processing (2)
  • packages/ndk/lib/domain_layer/entities/nip_05_resolve_result.dart
  • packages/ndk/lib/domain_layer/usecases/nip05/nip_05.dart

@nogringo nogringo requested a review from 1-leo May 22, 2026 16:57
@1-leo
Copy link
Copy Markdown
Contributor

1-leo commented May 26, 2026

@nogringo changed the returned errors to be exceptions

@nogringo
Copy link
Copy Markdown
Collaborator Author

idonotexist@uid.ovh return a 404 so the resolve method return Nip05ResolveError instead of Nip05NotFound.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants