Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(bens): Added TLD-based fallback for name search in networks #1175

Open
wants to merge 23 commits into
base: main
Choose a base branch
from

Conversation

Ilyak777
Copy link

@Ilyak777 Ilyak777 commented Jan 6, 2025

Closes #917

This PR enhances the names_options_in_network function by introducing a fallback mechanism for names without TLD. If the input name does not include a TLD, the function appends possible TLDs associated with the network's protocols, generating up to a defined maximum number of results (MAX_NAMES_LIMIT).

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced domain name search functionality with improved TLD handling and suggestions.
    • Added a limit of 5 names per search result.
    • Introduced detailed information for the domain "abcnews.eth".
  • Bug Fixes

    • Improved error handling for domain name protocol searches.
    • Refined logging for domain name resolution processes.
  • Documentation

    • Updated README with improved formatting and clarity.
    • Enhanced code block and table presentations.
  • Chores

    • Updated test scenarios for domain name API endpoints.
    • Minor formatting adjustments in test data files.
    • Added new entries to the database for various domains and ownership changes.

@Ilyak777 Ilyak777 added the enhancement New feature or request label Jan 6, 2025
@Ilyak777 Ilyak777 requested a review from sevenzing January 6, 2025 13:23
@Ilyak777 Ilyak777 self-assigned this Jan 6, 2025
Copy link
Contributor

coderabbitai bot commented Jan 6, 2025

Walkthrough

The pull request introduces modifications to the Protocoler struct in the blockscout-ens/bens-logic/src/protocols/protocoler.rs file. A new constant MAX_NAMES_LIMIT is set to 5, and the names_options_in_network method is significantly enhanced. The method now retrieves TLDs associated with network protocols, handling names with and without dots differently. A new method, fetch_domain_options, is added to encapsulate the logic for fetching domain options. The changes also include updates to error handling and logging in various methods. Additionally, the pull request includes minor formatting updates to the README.md and modifications to test files, including changes to API endpoint paths in domain-related tests.

Assessment against linked issues

Objective Addressed Explanation
Improve lookup for partial domain names
Append TLDs to search for domain names

Poem

🐰 A Rabbit's Ode to Domain Discovery 🔍
In the realm of names, a quest unfolds,
Where partial whispers become domain gold.
With TLDs dancing, five names in sight,
Our lookup leaps with algorithmic might!
Hop, hop, hurray for better search delight! 🌐

Tip

🌐 Web search-backed reviews and chat
  • We have enabled web search-based reviews and chat for all users. This feature allows CodeRabbit to access the latest documentation and information on the web.
  • You can disable this feature by setting web_search: false in the knowledge_base settings.
  • Please share any feedback in the Discord discussion.

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@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: 0

🧹 Nitpick comments (3)
blockscout-ens/bens-logic/src/protocols/protocoler.rs (3)

69-69: Consider making the limit configurable or documenting its rationale.

Currently, MAX_NAMES_LIMIT is a hardcoded magic number. Labeling it as a constant is good, but consider either making it configurable (e.g., via environment variables or config files) or at least documenting why 5 is an appropriate limit for your use case.


207-241: Consider deduplicating or short-circuiting logic for performance.

When appending TLDs in a loop and collecting names, consider:

  1. Maintaining a set to avoid duplicates if multiple protocols yield the same name.
  2. Short-circuiting earlier once the limit is reached, both outside and inside find_names_with_tld, to reduce unnecessary calls.

This can improve performance and clarity.


243-258: Reassess error handling in filter_map to avoid silently discarding errors.

Using .filter_map(|p| DomainNameOnProtocol::new(name_with_tld, p).ok()) silently drops errors. If legitimate errors occur, they won't be surfaced, making debugging more difficult. Consider handling or logging errors before discarding them.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 82dc056 and f38fd92.

📒 Files selected for processing (1)
  • blockscout-ens/bens-logic/src/protocols/protocoler.rs (2 hunks)

Copy link
Contributor

@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 (4)
blockscout-ens/bens-logic/src/protocols/protocoler.rs (4)

293-305: Consider using HashSet for TLD collection.

The current implementation might collect duplicate TLDs. Using a HashSet would be more efficient:

-            .collect::<Vec<Tld>>();
+            .collect::<std::collections::HashSet<Tld>>();

322-324: Enhance error message for invalid names.

The current error message doesn't provide enough context about why the name is invalid.

-            return Err(ProtocolError::InvalidName(name.to_string()));
+            return Err(ProtocolError::InvalidName {
+                name: name.to_string(),
+                reason: "no valid TLD combinations found".to_string(),
+            });

329-344: Add documentation for the helper method.

This public method would benefit from documentation explaining its purpose, parameters, and return value.

+    /// Attempts to find domain names with the specified TLD in the given network.
+    /// 
+    /// # Arguments
+    /// * `name_with_tld` - The domain name including TLD
+    /// * `network_id` - The network to search in
+    /// * `maybe_filter` - Optional protocol filter
+    /// 
+    /// # Returns
+    /// A vector of valid domain names on their respective protocols
     fn find_names_with_tld(

293-344: Well-structured implementation of TLD-based fallback.

The implementation successfully achieves the PR objective of enhancing name resolution with TLD fallback. The separation of concerns between names_options_in_network and find_names_with_tld is clean and maintainable.

A few architectural considerations:

  1. The solution gracefully handles both TLD and non-TLD cases
  2. The result limiting ensures reasonable response sizes
  3. The error handling provides a good foundation for debugging

Consider adding metrics/logging to track:

  • Number of TLD fallbacks attempted
  • Success rate of TLD combinations
  • Performance impact of TLD collection
🧰 Tools
🪛 GitHub Actions: Test, lint and docker (bens)

[error] 303-341: Code formatting issues detected by cargo fmt. Multiple formatting inconsistencies found including incorrect spacing and line breaks. Run 'cargo fmt' to fix these issues.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f38fd92 and a113537.

📒 Files selected for processing (1)
  • blockscout-ens/bens-logic/src/protocols/protocoler.rs (2 hunks)
🧰 Additional context used
🪛 GitHub Actions: Test, lint and docker (bens)
blockscout-ens/bens-logic/src/protocols/protocoler.rs

[error] 303-341: Code formatting issues detected by cargo fmt. Multiple formatting inconsistencies found including incorrect spacing and line breaks. Run 'cargo fmt' to fix these issues.

🔇 Additional comments (1)
blockscout-ens/bens-logic/src/protocols/protocoler.rs (1)

148-148: LGTM! Good use of a constant for limiting results.

The constant helps prevent excessive name variations while providing a reasonable limit for user queries.

blockscout-ens/bens-logic/src/protocols/protocoler.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@sevenzing sevenzing left a comment

Choose a reason for hiding this comment

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

also add tests for Tld struct :)

Copy link
Contributor

@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: 0

🧹 Nitpick comments (2)
blockscout-ens/bens-logic/src/protocols/protocoler.rs (2)

148-148: Add documentation for the constant.

Consider adding a doc comment explaining why the limit is set to 5 and what factors influenced this choice.

+/// Maximum number of name variations to return when generating TLD-based suggestions.
+/// This limit prevents excessive variations while providing sufficient options for users.
 const MAX_NAMES_LIMIT: usize = 5;

323-326: Consider preserving error context.

The current implementation silently ignores errors during name suggestion generation. Consider collecting errors or at least logging them for debugging purposes.

-match self.names_options_in_network_with_suggestions(&name_with_tld, network_id, maybe_filter.clone()) {
-    Ok(vec) => vec,
-    Err(_) => Vec::new(),
+match self.names_options_in_network_with_suggestions(&name_with_tld, network_id, maybe_filter.clone()) {
+    Ok(vec) => vec,
+    Err(e) => {
+        tracing::debug!("Failed to generate suggestion for {}: {}", name_with_tld, e);
+        Vec::new()
+    }
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a113537 and 99b159b.

📒 Files selected for processing (1)
  • blockscout-ens/bens-logic/src/protocols/protocoler.rs (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Unit, doc and integration tests
🔇 Additional comments (3)
blockscout-ens/bens-logic/src/protocols/protocoler.rs (3)

319-329: Well-implemented functional approach!

The code effectively uses functional programming concepts (map, flat_map, take) to handle name variations, making it more readable and maintainable.


342-359: Clean and well-structured implementation!

The helper method effectively:

  • Validates input and provides clear error messages
  • Handles domain name creation and TLD extraction
  • Generates protocol-specific name variations

292-340: Verify backward compatibility.

The changes modify the behavior of names_options_in_network. Let's verify that existing callers handle both the new limit and potential errors correctly.

✅ Verification successful

Changes maintain backward compatibility

The implementation is safe as all callers properly handle both the new result limit and potential errors:

  • Internal callers either use first result or iterate results
  • External caller in reader.rs gracefully handles errors by returning empty list
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all callers of names_options_in_network to verify compatibility

# Find direct function calls
rg -A 2 "names_options_in_network\(" --type rust

# Find error handling patterns in callers
ast-grep --pattern 'match $_.names_options_in_network($$$) {
    $$$
}'

Length of output: 2034

Copy link
Contributor

@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 (2)
blockscout-ens/bens-logic/src/protocols/protocoler.rs (2)

148-148: Document the MAX_NAMES_LIMIT constant.

Add documentation explaining the purpose of this constant and the rationale behind choosing 5 as the limit.

+/// Maximum number of name variations to return when generating TLD-based suggestions.
+/// Limited to 5 to prevent excessive resource usage while providing sufficient options.
 const MAX_NAMES_LIMIT: usize = 5;

293-305: Optimize TLD collection for better performance.

The current implementation creates unnecessary intermediate collections. Consider using a more efficient chain of iterator operations.

-        let tlds = self
-            .networks
-            .get(&network_id)
-            .ok_or_else(|| ProtocolError::NetworkNotFound(network_id))?
-            .use_protocols
-            .iter()
-            .filter_map(|protocol_name| {
-                self.protocols
-                    .get(protocol_name)
-                    .map(|protocol| protocol.info.tld_list.iter().cloned())
-            })
-            .flatten()
-            .collect::<Vec<Tld>>();
+        let tlds = self
+            .networks
+            .get(&network_id)
+            .ok_or_else(|| ProtocolError::NetworkNotFound(network_id))?
+            .use_protocols
+            .iter()
+            .flat_map(|protocol_name| {
+                self.protocols
+                    .get(protocol_name)
+                    .into_iter()
+                    .flat_map(|protocol| protocol.info.tld_list.iter().cloned())
+            })
+            .collect::<Vec<Tld>>();
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 99b159b and 213c5ff.

📒 Files selected for processing (1)
  • blockscout-ens/bens-logic/src/protocols/protocoler.rs (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Unit, doc and integration tests
🔇 Additional comments (1)
blockscout-ens/bens-logic/src/protocols/protocoler.rs (1)

347-365: Rename function to better reflect its purpose.

The function name names_options_in_network_with_suggestions is misleading as it doesn't find names but rather returns name options for a given TLD.

Consider renaming the function to better reflect its purpose, for example:

-    fn names_options_in_network_with_suggestions(
+    fn get_name_options_for_tld(

blockscout-ens/bens-logic/src/protocols/protocoler.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@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: 0

♻️ Duplicate comments (1)
blockscout-ens/bens-logic/src/protocols/protocoler.rs (1)

307-342: ⚠️ Potential issue

Improve code structure and error handling.

The current implementation has several issues:

  1. Deep nesting with match statements makes the code harder to follow
  2. Silently ignoring errors during name creation could hide important issues

Consider restructuring the code to reduce nesting and improve error handling as suggested in the previous review.

🧹 Nitpick comments (2)
blockscout-ens/bens-logic/src/protocols/protocoler.rs (2)

148-148: Document the rationale for MAX_NAMES_LIMIT value.

Consider adding a doc comment explaining why 5 was chosen as the limit and what factors might influence this value.

+/// Maximum number of name variations to return when generating TLD-based suggestions.
+/// This limit prevents overwhelming users with too many options while ensuring
+/// the most relevant TLDs are included.
 const MAX_NAMES_LIMIT: usize = 5;

293-305: Simplify TLD collection using iterator chaining.

The nested iterator transformations can be simplified for better readability.

-        let tlds = self
-            .networks
-            .get(&network_id)
-            .ok_or_else(|| ProtocolError::NetworkNotFound(network_id))?
-            .use_protocols
-            .iter()
-            .filter_map(|protocol_name| {
-                self.protocols
-                    .get(protocol_name)
-                    .map(|protocol| protocol.info.tld_list.iter().cloned())
-            })
-            .flatten()
-            .collect::<Vec<Tld>>();
+        let network = self.networks
+            .get(&network_id)
+            .ok_or_else(|| ProtocolError::NetworkNotFound(network_id))?;
+        
+        let tlds: Vec<Tld> = network.use_protocols.iter()
+            .filter_map(|name| self.protocols.get(name))
+            .flat_map(|protocol| protocol.info.tld_list.iter().cloned())
+            .collect();
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 213c5ff and 9e2e224.

📒 Files selected for processing (2)
  • blockscout-ens/bens-logic/src/protocols/protocoler.rs (4 hunks)
  • blockscout-ens/bens-server/tests/data/domains/levvv_gno/detailed.json (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • blockscout-ens/bens-server/tests/data/domains/levvv_gno/detailed.json
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Unit, doc and integration tests
🔇 Additional comments (2)
blockscout-ens/bens-logic/src/protocols/protocoler.rs (2)

345-363: Well-structured helper method with proper error handling!

The implementation is clean, with clear error messages and appropriate error handling.


417-452: Add tests for TLD fallback functionality.

While the current tests cover basic TLD operations, consider adding tests for:

  1. Name resolution with TLD fallback
  2. Cases where MAX_NAMES_LIMIT is reached
  3. Error cases in TLD fallback path

Copy link
Contributor

@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: 0

🧹 Nitpick comments (4)
blockscout-ens/README.md (3)

49-56: Consider adding more context to the test run section.

While the instructions are clear, it would be helpful to add:

  • What types of tests are included (e.g., unit tests, integration tests)
  • What components are being tested
  • Expected test output or success criteria

78-93: Improve grammar and precision in contribution guidelines.

Consider these improvements:

  1. Line 78: "connected to a common database"
  2. Line 85: "Add your protocol to the list of"
  3. Line 86: "Update the default config"
  4. Line 87: "create a PR"
  5. Line 92: "contains the correct number of domains"

Apply this diff to improve the text:

-connected to common database
+connected to a common database
-Add your protocol to list of
+Add your protocol to the list of
-Update default config
+Update the default config
-create PR with:
+create a PR with:
-contains correct amount of domains
+contains the correct number of domains
🧰 Tools
🪛 LanguageTool

[uncategorized] ~78-~78: You might be missing the article “a” here.
Context: ...ig and start bens-server connected to common database (read more in [how to start be...

(AI_EN_LECTOR_MISSING_DETERMINER_A)


[uncategorized] ~85-~85: You might be missing the article “the” here.
Context: ...ain/index.html) 9. Add your protocol to list of [supported domains](#current-support...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~86-~86: You might be missing the article “the” here.
Context: ...(#current-supported-domains) 10. Update default config of BENS server for [production](...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~87-~87: You might be missing the article “a” here.
Context: ...onfig/staging.json) 11. Finally, create PR with: - New directory inside `bloc...

(AI_EN_LECTOR_MISSING_DETERMINER_A)


[uncategorized] ~92-~92: You might be missing the article “the” here.
Context: ...oof that your indexed subgraph contains correct amount of domains, resolved_addresses a...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~92-~92: ‘Amount of’ should usually only be used with uncountable or mass nouns. Consider using “number” if this is not the case.
Context: ... your indexed subgraph contains correct amount of domains, resolved_addresses and so o...

(AMOUNTOF_TO_NUMBEROF)


94-94: Remove unused anchor reference.

The line [anchor]: [anchor]: appears to be unused and should be removed.

🧰 Tools
🪛 Markdownlint (0.37.0)

94-94: Unused link or image reference definition: "anchor"
Link and image reference definitions should be needed

(MD053, link-image-reference-definitions)

blockscout-ens/bens-server/tests/domains.rs (1)

268-271: LGTM! Consider adding more cross-protocol test cases.

The changes correctly verify TLD-based fallback in a multi-protocol environment. However, consider adding test cases for:

  1. Ambiguous domain names that could resolve to multiple TLDs
  2. Invalid domain names that don't match any protocol's TLD pattern
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9e2e224 and 34a79d7.

📒 Files selected for processing (5)
  • blockscout-ens/README.md (2 hunks)
  • blockscout-ens/bens-logic/src/subgraph/reader.rs (1 hunks)
  • blockscout-ens/bens-logic/src/subgraph/sql/domain.rs (2 hunks)
  • blockscout-ens/bens-logic/tests/migrations/20231219085631_push_mock_gnosis_data.sql (1 hunks)
  • blockscout-ens/bens-server/tests/domains.rs (4 hunks)
✅ Files skipped from review due to trivial changes (2)
  • blockscout-ens/bens-logic/src/subgraph/sql/domain.rs
  • blockscout-ens/bens-logic/src/subgraph/reader.rs
🧰 Additional context used
🪛 LanguageTool
blockscout-ens/README.md

[uncategorized] ~78-~78: You might be missing the article “a” here.
Context: ...ig and start bens-server connected to common database (read more in [how to start be...

(AI_EN_LECTOR_MISSING_DETERMINER_A)


[uncategorized] ~85-~85: You might be missing the article “the” here.
Context: ...ain/index.html) 9. Add your protocol to list of [supported domains](#current-support...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~86-~86: You might be missing the article “the” here.
Context: ...(#current-supported-domains) 10. Update default config of BENS server for [production](...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~87-~87: You might be missing the article “a” here.
Context: ...onfig/staging.json) 11. Finally, create PR with: - New directory inside `bloc...

(AI_EN_LECTOR_MISSING_DETERMINER_A)


[uncategorized] ~92-~92: You might be missing the article “the” here.
Context: ...oof that your indexed subgraph contains correct amount of domains, resolved_addresses a...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~92-~92: ‘Amount of’ should usually only be used with uncountable or mass nouns. Consider using “number” if this is not the case.
Context: ... your indexed subgraph contains correct amount of domains, resolved_addresses and so o...

(AMOUNTOF_TO_NUMBEROF)

🪛 Markdownlint (0.37.0)
blockscout-ens/README.md

94-94: Unused link or image reference definition: "anchor"
Link and image reference definitions should be needed

(MD053, link-image-reference-definitions)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Unit, doc and integration tests
🔇 Additional comments (5)
blockscout-ens/README.md (2)

13-22: LGTM! Well-formatted table of supported domains.

The table clearly presents the supported domain services with their respective networks, TLDs, and notes.


26-36: LGTM! Well-structured environment variables documentation.

The table clearly presents the environment variables with their requirements, descriptions, and default values.

blockscout-ens/bens-server/tests/domains.rs (2)

Line range hint 73-85: LGTM! Event endpoints correctly updated for TLD-based fallback.

The changes maintain consistency in the API by removing TLD suffixes from event endpoints while preserving the existing functionality and response format.


58-64: LGTM! Test coverage aligns with TLD-based fallback implementation.

The changes correctly verify that domain names without TLD suffixes are properly resolved in the ETH network, while maintaining backward compatibility with the existing response format.

✅ Verification successful

Verification successful: TLD-based fallback implementation is properly tested

The test suite comprehensively covers:

  • TLD-less API endpoints across different protocols (ETH, GNO)
  • Special character handling (emoji domains)
  • Proper response format maintaining TLD information
  • Multiple network configurations (1, 1337)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify test coverage for various TLD scenarios
rg -A 2 "domains/[^/]+/detailed.json" blockscout-ens/bens-server/tests/

Length of output: 1071


Script:

#!/bin/bash
# Check test data files content and API endpoint patterns
echo "=== Test Data Files ==="
fd "detailed.json" blockscout-ens/bens-server/tests/data/domains -x cat {}

echo -e "\n=== API Endpoint Patterns ==="
rg "/api/v1/\d+/domains/[^\"']+" blockscout-ens/bens-server/tests/domains.rs

Length of output: 3894

blockscout-ens/bens-logic/tests/migrations/20231219085631_push_mock_gnosis_data.sql (1)

275-275: Avoid using MAX_INT (2147483647) for expiry dates.

While this is test data, using MAX_INT (2^31 - 1) for dates can lead to:

  1. Y2K38 problems on 32-bit systems
  2. Potential overflow issues when performing date arithmetic
  3. Difficulty in distinguishing between "never expires" and actual expiry dates

Let's verify if this pattern is used in production:

Consider using a more reasonable future date for test data, such as 5-10 years from now, or introduce a proper "no expiry" representation in the schema if that's the intent.

-INSERT INTO sgd2.domain (..., expiry_date) VALUES (..., 2147483647);
+INSERT INTO sgd2.domain (..., expiry_date) VALUES (..., 1893456000);  -- 2030-01-01
✅ Verification successful

Using MAX_INT for expiry dates is isolated to test data only

The codebase search confirms that the use of MAX_INT (2147483647) for expiry dates:

  • Is limited to mock data in test files
  • Is not present in production migrations or schema definitions
  • Production code properly handles expiry dates with timestamp conversions

While the suggestion to use a more reasonable future date in test data is a good practice, this is not a critical issue as it doesn't affect production code.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for MAX_INT or similar large timestamps in production SQL files
rg -g '!*test*' -g '!*mock*' '2147483647|9999999999' --type sql

# Search for large expiry dates in migrations
rg -g 'migrations/*.sql' 'expiry_date.*[12][90][0-9]{8}' 

Length of output: 273


Script:

#!/bin/bash
# Find SQL files excluding test/mock directories
fd -e sql -E '*test*' -E '*mock*' . | while read -r file; do
  echo "=== $file ==="
  rg -U '(2147483647|9999999999|expiry_date.*20[34][0-9])' "$file" || true
done

# Specifically search for expiry_date column definitions
fd -e sql . | while read -r file; do
  echo "=== $file ==="
  rg -U 'expiry_date.*timestamp|create table.*expiry_date' "$file" || true
done

Length of output: 8393

Copy link
Contributor

@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: 2

🧹 Nitpick comments (2)
blockscout-ens/bens-logic/src/protocols/protocoler.rs (2)

149-149: Consider making MAX_NAMES_LIMIT configurable.

While 5 is a reasonable default, consider making this limit configurable through environment variables or configuration files to allow flexibility in different deployment scenarios.

-const MAX_NAMES_LIMIT: usize = 5;
+const MAX_NAMES_LIMIT: usize = std::env::var("BENS_MAX_NAMES_LIMIT")
+    .ok()
+    .and_then(|v| v.parse().ok())
+    .unwrap_or(5);

381-414: Reduce code duplication with names_options_in_network_exact.

The function shares significant logic with names_options_in_network_exact. Consider extracting the common logic into a private helper function.

+    fn create_domain_names(
+        &self,
+        name: &str,
+        protocols: NonEmpty<DeployedProtocol<'_>>,
+    ) -> Result<Vec<DomainNameOnProtocol>, ProtocolError> {
+        protocols
+            .into_iter()
+            .map(|deployed_protocol| {
+                let empty_label_hash = match &deployed_protocol.protocol.info.protocol_specific {
+                    ProtocolSpecific::EnsLike(ens_like) => ens_like.empty_label_hash,
+                    ProtocolSpecific::D3Connect(d3_connect) => d3_connect.empty_label_hash,
+                };
+
+                log::debug!(
+                    "Creating DomainName with name: {}, empty_label_hash: {:?}",
+                    name, empty_label_hash
+                );
+
+                DomainName::new(name, empty_label_hash).map(|domain_name| DomainNameOnProtocol {
+                    inner: domain_name,
+                    deployed_protocol,
+                })
+            })
+            .collect()
+    }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 34a79d7 and 5e0e2ad.

📒 Files selected for processing (1)
  • blockscout-ens/bens-logic/src/protocols/protocoler.rs (5 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Unit, doc and integration tests
🔇 Additional comments (3)
blockscout-ens/bens-logic/src/protocols/protocoler.rs (3)

123-123: LGTM! Consistent field addition.

The addition of empty_label_hash to D3ConnectProtocol maintains consistency with EnsLikeProtocol and supports the protocol-agnostic handling of empty label hashes.


478-513: LGTM! Comprehensive test coverage.

The test module provides good coverage of the Tld struct's functionality, including edge cases and error conditions.


293-342: 🛠️ Refactor suggestion

Refactor to reduce nesting and improve maintainability.

The current implementation has deep nesting and duplicated error handling logic. Consider restructuring the code to be more functional and easier to follow.

-        if name.contains('.') {
-            let direct = self.names_options_in_network_exact(name, network_id, maybe_filter)?;
-            if direct.is_empty() {
-                Err(ProtocolError::InvalidName {
-                    name: name.to_string(),
-                    reason: "No matching protocols for given TLD".to_string(),
-                })
-            } else {
-                Ok(direct.into_iter().take(MAX_NAMES_LIMIT).collect())
-            }
-        } else {
-            let all_names_with_protocols: Vec<_> = tlds
-                .into_iter()
-                .map(|tld| format!("{}.{}", name, tld.0))
-                .flat_map(|name_with_tld| {
-                    self.names_options_in_network_with_suggestions(
-                        &name_with_tld,
-                        network_id,
-                        maybe_filter.clone(),
-                    )
-                    .unwrap_or_default()
-                })
-                .take(MAX_NAMES_LIMIT)
-                .collect();
-
-            if all_names_with_protocols.is_empty() {
-                Err(ProtocolError::InvalidName {
-                    name: name.to_string(),
-                    reason: "No valid TLDs".to_string(),
-                })
-            } else {
-                Ok(all_names_with_protocols)
-            }
-        }
+        let results = if name.contains('.') {
+            self.names_options_in_network_exact(name, network_id, maybe_filter)?
+        } else {
+            tlds.into_iter()
+                .map(|tld| format!("{}.{}", name, tld.0))
+                .flat_map(|name_with_tld| {
+                    self.names_options_in_network_with_suggestions(
+                        &name_with_tld,
+                        network_id,
+                        maybe_filter.clone(),
+                    )
+                    .map_err(|e| {
+                        log::debug!("Failed to get suggestions for {}: {}", name_with_tld, e);
+                        e
+                    })
+                    .unwrap_or_default()
+                })
+                .collect()
+        };
+
+        if results.is_empty() {
+            Err(ProtocolError::InvalidName {
+                name: name.to_string(),
+                reason: if name.contains('.') {
+                    "No matching protocols for given TLD"
+                } else {
+                    "No valid TLDs"
+                }
+                .to_string(),
+            })
+        } else {
+            Ok(results.into_iter().take(MAX_NAMES_LIMIT).collect())
+        }

Likely invalid or redundant comment.

blockscout-ens/bens-logic/src/protocols/protocoler.rs Outdated Show resolved Hide resolved
blockscout-ens/bens-logic/src/protocols/protocoler.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@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: 0

♻️ Duplicate comments (2)
blockscout-ens/bens-logic/src/protocols/protocoler.rs (2)

293-342: ⚠️ Potential issue

Improve error handling and code structure in names_options_in_network.

The current implementation has several issues:

  1. Deep nesting with if-else blocks makes the code harder to follow
  2. Silent error handling with unwrap_or_default() could hide important issues

Additionally, consider using early returns to reduce nesting:

-        if name.contains('.') {
-            let direct = self.names_options_in_network_exact(name, network_id, maybe_filter)?;
-            if direct.is_empty() {
-                Err(ProtocolError::InvalidName {
-                    name: name.to_string(),
-                    reason: "No matching protocols for given TLD".to_string(),
-                })
-            } else {
-                Ok(direct.into_iter().take(MAX_NAMES_LIMIT).collect())
-            }
-        } else {
+        if name.contains('.') {
+            let direct = self.names_options_in_network_exact(name, network_id, maybe_filter)?;
+            return if direct.is_empty() {
+                Err(ProtocolError::InvalidName {
+                    name: name.to_string(),
+                    reason: "No matching protocols for given TLD".to_string(),
+                })
+            } else {
+                Ok(direct.into_iter().take(MAX_NAMES_LIMIT).collect())
+            };
+        }

385-388: ⚠️ Potential issue

Replace println with proper logging.

Using println! for debugging is not suitable for production code. Use a proper logging framework instead.

Also applies to: 403-406, 425-428

🧹 Nitpick comments (1)
blockscout-ens/bens-logic/src/protocols/protocoler.rs (1)

149-149: Consider making MAX_NAMES_LIMIT configurable.

The hardcoded limit of 5 might be too restrictive for some use cases. Consider making this configurable through environment variables or configuration files.

-const MAX_NAMES_LIMIT: usize = 5;
+const MAX_NAMES_LIMIT: usize = std::env::var("MAX_NAMES_LIMIT")
+    .ok()
+    .and_then(|v| v.parse().ok())
+    .unwrap_or(5);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5e0e2ad and 46d13c1.

📒 Files selected for processing (2)
  • blockscout-ens/bens-logic/src/protocols/protocoler.rs (6 hunks)
  • blockscout-ens/bens-logic/src/subgraph/sql/domain.rs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • blockscout-ens/bens-logic/src/subgraph/sql/domain.rs
🔇 Additional comments (2)
blockscout-ens/bens-logic/src/protocols/protocoler.rs (2)

478-513: Well-structured unit tests added.

The new test module for the Tld struct provides good coverage of edge cases and core functionality.


123-123: Ensure consistent field initialization across protocol types.

The empty_label_hash field has been added to D3ConnectProtocol to match EnsLikeProtocol. This is good for consistency, but ensure all new instances are properly initialized.

✅ Verification successful

Field initialization is properly handled

The empty_label_hash field is safely initialized through the derived Default trait, where Option<B256> defaults to None. The struct's configuration ensures consistent initialization across all usage patterns.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for D3ConnectProtocol instantiations to verify initialization
ast-grep --pattern 'D3ConnectProtocol {
  $$$
}'

Length of output: 50


Script:

#!/bin/bash
# First verify the file exists and is accessible
echo "=== File check ==="
ls -l blockscout-ens/bens-logic/src/protocols/protocoler.rs

echo -e "\n=== Struct initialization check ==="
# Try different initialization patterns
ast-grep --pattern 'D3ConnectProtocol { $$$'
ast-grep --pattern 'D3ConnectProtocol::new($$$)'

echo -e "\n=== Fallback grep search ==="
rg "D3ConnectProtocol" blockscout-ens/bens-logic/src/protocols/protocoler.rs -A 5

Length of output: 1369


Script:

#!/bin/bash
echo "=== Default implementation check ==="
rg "impl .* for D3ConnectProtocol" -A 10 blockscout-ens/bens-logic/src/protocols/protocoler.rs

echo -e "\n=== Test initialization check ==="
rg "#\[test\]" -A 20 blockscout-ens/bens-logic/src/protocols/protocoler.rs

echo -e "\n=== Constructor check ==="
rg "impl D3ConnectProtocol" -A 10 blockscout-ens/bens-logic/src/protocols/protocoler.rs

Length of output: 1186


Script:

#!/bin/bash
# Extract the complete struct definition with attributes
echo "=== Struct definition with attributes ==="
rg "struct D3ConnectProtocol" -B 5 -A 10 blockscout-ens/bens-logic/src/protocols/protocoler.rs

# Check for any derive macros in the file
echo -e "\n=== Derive macros check ==="
rg "#\[derive" -A 1 blockscout-ens/bens-logic/src/protocols/protocoler.rs

Length of output: 1800

Copy link
Contributor

@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: 0

♻️ Duplicate comments (1)
blockscout-ens/bens-logic/src/protocols/protocoler.rs (1)

293-342: 🛠️ Refactor suggestion

Improve error handling and code structure.

The current implementation has deep nesting and silently ignores errors during name creation.

Consider restructuring the code to reduce nesting and improve error handling as suggested in the previous review.

🧹 Nitpick comments (3)
blockscout-ens/README.md (1)

86-89: Refine the wording in the PR requirements.

Consider replacing "amount of domains" with "number of domains" for better grammar.

-    - Result of indexed data: proof that your indexed subgraph contains correct amount of domains, resolved_addresses and so on
+    - Result of indexed data: proof that your indexed subgraph contains correct number of domains, resolved_addresses and so on
🧰 Tools
🪛 LanguageTool

[uncategorized] ~89-~89: ‘Amount of’ should usually only be used with uncountable or mass nouns. Consider using “number” if this is not the case.
Context: ... your indexed subgraph contains correct amount of domains, resolved_addresses and so o...

(AMOUNTOF_TO_NUMBEROF)

blockscout-ens/bens-logic/src/protocols/protocoler.rs (2)

149-149: Consider making MAX_NAMES_LIMIT configurable.

The hardcoded limit of 5 might need to be adjusted based on different use cases.

Consider making this a configurable parameter in the environment variables:

-const MAX_NAMES_LIMIT: usize = 5;
+const MAX_NAMES_LIMIT: usize = std::env::var("BENS__MAX_NAMES_LIMIT")
+    .ok()
+    .and_then(|v| v.parse().ok())
+    .unwrap_or(5);

374-400: Add debug logging for better observability.

The method would benefit from debug logging to track name suggestions.

Add debug logging:

+        log::debug!(
+            "Generating name suggestions for {} in network {}",
+            name_with_tld,
+            network_id
+        );
         let protocols = self.protocols_of_network_for_tld(
             network_id,
             Tld::from_domain_name(name_with_tld).unwrap(),
             maybe_filter,
         )?;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 46d13c1 and 6c41165.

📒 Files selected for processing (2)
  • blockscout-ens/README.md (4 hunks)
  • blockscout-ens/bens-logic/src/protocols/protocoler.rs (6 hunks)
🧰 Additional context used
🪛 Markdownlint (0.37.0)
blockscout-ens/README.md

26-26: Unused link or image reference definition: "anchor"
Link and image reference definitions should be needed

(MD053, link-image-reference-definitions)

🪛 LanguageTool
blockscout-ens/README.md

[uncategorized] ~89-~89: ‘Amount of’ should usually only be used with uncountable or mass nouns. Consider using “number” if this is not the case.
Context: ... your indexed subgraph contains correct amount of domains, resolved_addresses and so o...

(AMOUNTOF_TO_NUMBEROF)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Linting
  • GitHub Check: Unit, doc and integration tests
🔇 Additional comments (7)
blockscout-ens/README.md (3)

13-22: LGTM! Well-structured table of supported domains.

The table is properly formatted with consistent column widths and clear headers.


28-38: LGTM! Clear and well-organized environment variables table.

The table provides comprehensive information about configuration options with proper alignment and descriptions.


47-51: LGTM! Clear and consistent code block formatting.

The bash commands are properly indented and well-organized.

Also applies to: 69-71, 75-77

blockscout-ens/bens-logic/src/protocols/protocoler.rs (4)

123-123: LGTM! Consistent empty_label_hash field addition.

The addition of empty_label_hash to D3ConnectProtocol aligns with the field in EnsLikeProtocol.


344-372: LGTM! Clear error messages and proper empty_label_hash handling.

The method properly extracts TLD and handles empty_label_hash from both protocol types.


410-410: LGTM! Proper error handling in main_name_in_network.

The method correctly handles the case when no names are found.


454-489: LGTM! Comprehensive test coverage for Tld struct.

The test module provides good coverage of Tld functionality including edge cases.

@@ -55,11 +55,11 @@ async fn eth_protocol_scenario(base: Url, settings: &Settings) {
let context = utils::settings_context(settings);

// get detailed domain
let request: Value = send_get_request(&base, "/api/v1/1/domains/vitalik.eth").await;
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't we leave at least one test case where tld is preserved?

blockscout-ens/bens-logic/src/protocols/protocoler.rs Outdated Show resolved Hide resolved
blockscout-ens/bens-logic/src/protocols/protocoler.rs Outdated Show resolved Hide resolved
blockscout-ens/bens-logic/src/protocols/protocoler.rs Outdated Show resolved Hide resolved
blockscout-ens/bens-logic/src/protocols/protocoler.rs Outdated Show resolved Hide resolved
blockscout-ens/bens-logic/src/protocols/protocoler.rs Outdated Show resolved Hide resolved
blockscout-ens/bens-logic/src/protocols/protocoler.rs Outdated Show resolved Hide resolved
blockscout-ens/bens-logic/src/protocols/protocoler.rs Outdated Show resolved Hide resolved
blockscout-ens/bens-logic/src/protocols/protocoler.rs Outdated Show resolved Hide resolved
@@ -55,11 +55,11 @@ async fn eth_protocol_scenario(base: Url, settings: &Settings) {
let context = utils::settings_context(settings);

// get detailed domain
let request: Value = send_get_request(&base, "/api/v1/1/domains/vitalik.eth").await;
let request: Value = send_get_request(&base, "/api/v1/1/domains/vitalik").await;
Copy link
Contributor

Choose a reason for hiding this comment

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

adding another test case seems better, as the previous is still useful + more tests is better than less :)

Copy link
Contributor

Choose a reason for hiding this comment

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

same for all below

blockscout-ens/bens-logic/src/protocols/protocoler.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@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: 2

🧹 Nitpick comments (3)
blockscout-ens/bens-logic/src/protocols/protocoler.rs (3)

149-149: Add documentation for MAX_NAMES_LIMIT constant.

Consider adding a doc comment explaining why the limit is set to 5 and what it represents.

+/// Maximum number of name suggestions to return when searching for names in a network.
+/// This limit helps prevent excessive results when generating TLD-based suggestions.
 const MAX_NAMES_LIMIT: usize = 5;

344-372: Extract empty label hash logic to a helper method.

The empty label hash extraction logic is duplicated. Consider extracting it to a helper method.

+    fn get_empty_label_hash(protocol_specific: &ProtocolSpecific) -> Option<B256> {
+        match protocol_specific {
+            ProtocolSpecific::EnsLike(ens_like) => ens_like.empty_label_hash,
+            ProtocolSpecific::D3Connect(d3_connect) => d3_connect.empty_label_hash,
+        }
+    }

     fn names_options_in_network_exact(
         &self,
         name: &str,
         network_id: i64,
         maybe_filter: Option<NonEmpty<String>>,
     ) -> Result<Vec<DomainNameOnProtocol>, ProtocolError> {
         let tld = Tld::from_domain_name(name).ok_or_else(|| ProtocolError::InvalidName {
             name: name.to_string(),
-            reason: "Invalid TLD".to_string(),
+            reason: format!("Invalid or missing TLD in domain name: {}", name),
         })?;

         let protocols = self.protocols_of_network_for_tld(network_id, tld, maybe_filter)?;
         let mut results = Vec::new();
         for deployed_protocol in protocols {
-            let empty_label_hash = match &deployed_protocol.protocol.info.protocol_specific {
-                ProtocolSpecific::EnsLike(ens_like) => ens_like.empty_label_hash,
-                ProtocolSpecific::D3Connect(d3_connect) => d3_connect.empty_label_hash,
-            };
+            let empty_label_hash = Self::get_empty_label_hash(
+                &deployed_protocol.protocol.info.protocol_specific
+            );
             let domain_name = DomainName::new(name, empty_label_hash)?;
             results.push(DomainNameOnProtocol {
                 inner: domain_name,
                 deployed_protocol,
             });
         }
         Ok(results)
     }

410-410: Remove unnecessary clone.

The clone() operation is not needed here as maybe_filter is only used once.

-            .names_options_in_network(name, network_id, maybe_filter.clone())
+            .names_options_in_network(name, network_id, maybe_filter)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6c41165 and 9bfeb5d.

📒 Files selected for processing (1)
  • blockscout-ens/bens-logic/src/protocols/protocoler.rs (6 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Unit, doc and integration tests
🔇 Additional comments (2)
blockscout-ens/bens-logic/src/protocols/protocoler.rs (2)

123-123: LGTM! Good addition of empty_label_hash field.

The addition of empty_label_hash to D3ConnectProtocol brings consistency with EnsLikeProtocol and enables unified empty label hash handling across protocols.


454-489: LGTM! Comprehensive test coverage for Tld struct.

The test module provides good coverage of the Tld functionality, including edge cases like empty TLDs and dot handling.

Comment on lines 374 to 400
fn names_options_in_network_with_suggestions(
&self,
name_with_tld: &str,
network_id: i64,
maybe_filter: Option<NonEmpty<String>>,
) -> Result<Vec<DomainNameOnProtocol>, ProtocolError> {
let protocols = self.protocols_of_network_for_tld(
network_id,
Tld::from_domain_name(name_with_tld).unwrap(),
maybe_filter,
)?;

let mut results = Vec::new();
for deployed_protocol in protocols {
let empty_label_hash = match &deployed_protocol.protocol.info.protocol_specific {
ProtocolSpecific::EnsLike(ens_like) => ens_like.empty_label_hash,
ProtocolSpecific::D3Connect(d3_connect) => d3_connect.empty_label_hash,
};

let domain_name = DomainName::new(name_with_tld, empty_label_hash)?;
results.push(DomainNameOnProtocol {
inner: domain_name,
deployed_protocol,
});
}

Ok(results)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove unsafe unwrap and reduce code duplication.

The method has an unsafe unwrap and duplicates logic from names_options_in_network_exact.

Consider this safer implementation that reuses existing logic:

     fn names_options_in_network_with_suggestions(
         &self,
         name_with_tld: &str,
         network_id: i64,
         maybe_filter: Option<NonEmpty<String>>,
     ) -> Result<Vec<DomainNameOnProtocol>, ProtocolError> {
-        let protocols = self.protocols_of_network_for_tld(
-            network_id,
-            Tld::from_domain_name(name_with_tld).unwrap(),
-            maybe_filter,
-        )?;
-
-        let mut results = Vec::new();
-        for deployed_protocol in protocols {
-            let empty_label_hash = match &deployed_protocol.protocol.info.protocol_specific {
-                ProtocolSpecific::EnsLike(ens_like) => ens_like.empty_label_hash,
-                ProtocolSpecific::D3Connect(d3_connect) => d3_connect.empty_label_hash,
-            };
-
-            let domain_name = DomainName::new(name_with_tld, empty_label_hash)?;
-            results.push(DomainNameOnProtocol {
-                inner: domain_name,
-                deployed_protocol,
-            });
-        }
-
-        Ok(results)
+        self.names_options_in_network_exact(name_with_tld, network_id, maybe_filter)
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn names_options_in_network_with_suggestions(
&self,
name_with_tld: &str,
network_id: i64,
maybe_filter: Option<NonEmpty<String>>,
) -> Result<Vec<DomainNameOnProtocol>, ProtocolError> {
let protocols = self.protocols_of_network_for_tld(
network_id,
Tld::from_domain_name(name_with_tld).unwrap(),
maybe_filter,
)?;
let mut results = Vec::new();
for deployed_protocol in protocols {
let empty_label_hash = match &deployed_protocol.protocol.info.protocol_specific {
ProtocolSpecific::EnsLike(ens_like) => ens_like.empty_label_hash,
ProtocolSpecific::D3Connect(d3_connect) => d3_connect.empty_label_hash,
};
let domain_name = DomainName::new(name_with_tld, empty_label_hash)?;
results.push(DomainNameOnProtocol {
inner: domain_name,
deployed_protocol,
});
}
Ok(results)
fn names_options_in_network_with_suggestions(
&self,
name_with_tld: &str,
network_id: i64,
maybe_filter: Option<NonEmpty<String>>,
) -> Result<Vec<DomainNameOnProtocol>, ProtocolError> {
self.names_options_in_network_exact(name_with_tld, network_id, maybe_filter)
}

Comment on lines 293 to 342
) -> Result<Vec<DomainNameOnProtocol>, ProtocolError> {
let tlds = self
.networks
.get(&network_id)
.ok_or_else(|| ProtocolError::NetworkNotFound(network_id))?
.use_protocols
.iter()
.filter_map(|protocol_name| {
self.protocols
.get(protocol_name)
.map(|protocol| protocol.info.tld_list.iter().cloned())
})
.flatten()
.collect::<Vec<Tld>>();

if name.contains('.') {
let direct = self.names_options_in_network_exact(name, network_id, maybe_filter)?;
if direct.is_empty() {
Err(ProtocolError::InvalidName {
name: name.to_string(),
reason: "No matching protocols for given TLD".to_string(),
})
} else {
Ok(direct.into_iter().take(MAX_NAMES_LIMIT).collect())
}
} else {
let all_names_with_protocols: Vec<_> = tlds
.into_iter()
.map(|tld| format!("{}.{}", name, tld.0))
.flat_map(|name_with_tld| {
self.names_options_in_network_with_suggestions(
&name_with_tld,
network_id,
maybe_filter.clone(),
)
.unwrap_or_default()
})
.take(MAX_NAMES_LIMIT)
.collect();

if all_names_with_protocols.is_empty() {
Err(ProtocolError::InvalidName {
name: name.to_string(),
reason: "No valid TLDs".to_string(),
})
} else {
Ok(all_names_with_protocols)
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error handling and code structure.

The current implementation has several issues that should be addressed:

  1. Silent error handling with unwrap_or_default()
  2. Complex nested structure
  3. Unnecessary clone() operations

Consider this more functional approach with better error handling:

 fn names_options_in_network(
     &self,
     name: &str,
     network_id: i64,
     maybe_filter: Option<NonEmpty<String>>,
 ) -> Result<Vec<DomainNameOnProtocol>, ProtocolError> {
     let tlds: Vec<Tld> = self
         .networks
         .get(&network_id)
         .ok_or_else(|| ProtocolError::NetworkNotFound(network_id))?
         .use_protocols
         .iter()
         .flat_map(|protocol_name| {
             self.protocols
                 .get(protocol_name)
                 .map(|protocol| protocol.info.tld_list.iter().cloned())
         })
         .flatten()
         .collect();

     if name.contains('.') {
         self.names_options_in_network_exact(name, network_id, maybe_filter)
             .and_then(|results| {
                 if results.is_empty() {
                     Err(ProtocolError::InvalidName {
                         name: name.to_string(),
                         reason: "No matching protocols for given TLD".to_string(),
                     })
                 } else {
                     Ok(results.into_iter().take(MAX_NAMES_LIMIT).collect())
                 }
             })
     } else {
         let suggestions: Vec<_> = tlds
             .into_iter()
             .map(|tld| format!("{}.{}", name, tld.0))
             .filter_map(|name_with_tld| {
                 self.names_options_in_network_with_suggestions(
                     &name_with_tld,
                     network_id,
                     maybe_filter.as_ref().cloned(),
                 )
                 .map_err(|e| {
                     log::debug!("Failed to get suggestions for {}: {}", name_with_tld, e);
                     e
                 })
                 .ok()
             })
             .flatten()
             .take(MAX_NAMES_LIMIT)
             .collect();

         if suggestions.is_empty() {
             Err(ProtocolError::InvalidName {
                 name: name.to_string(),
                 reason: "No valid TLDs".to_string(),
             })
         } else {
             Ok(suggestions)
         }
     }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
) -> Result<Vec<DomainNameOnProtocol>, ProtocolError> {
let tlds = self
.networks
.get(&network_id)
.ok_or_else(|| ProtocolError::NetworkNotFound(network_id))?
.use_protocols
.iter()
.filter_map(|protocol_name| {
self.protocols
.get(protocol_name)
.map(|protocol| protocol.info.tld_list.iter().cloned())
})
.flatten()
.collect::<Vec<Tld>>();
if name.contains('.') {
let direct = self.names_options_in_network_exact(name, network_id, maybe_filter)?;
if direct.is_empty() {
Err(ProtocolError::InvalidName {
name: name.to_string(),
reason: "No matching protocols for given TLD".to_string(),
})
} else {
Ok(direct.into_iter().take(MAX_NAMES_LIMIT).collect())
}
} else {
let all_names_with_protocols: Vec<_> = tlds
.into_iter()
.map(|tld| format!("{}.{}", name, tld.0))
.flat_map(|name_with_tld| {
self.names_options_in_network_with_suggestions(
&name_with_tld,
network_id,
maybe_filter.clone(),
)
.unwrap_or_default()
})
.take(MAX_NAMES_LIMIT)
.collect();
if all_names_with_protocols.is_empty() {
Err(ProtocolError::InvalidName {
name: name.to_string(),
reason: "No valid TLDs".to_string(),
})
} else {
Ok(all_names_with_protocols)
}
}
}
fn names_options_in_network(
&self,
name: &str,
network_id: i64,
maybe_filter: Option<NonEmpty<String>>,
) -> Result<Vec<DomainNameOnProtocol>, ProtocolError> {
let tlds: Vec<Tld> = self
.networks
.get(&network_id)
.ok_or_else(|| ProtocolError::NetworkNotFound(network_id))?
.use_protocols
.iter()
.flat_map(|protocol_name| {
self.protocols
.get(protocol_name)
.map(|protocol| protocol.info.tld_list.iter().cloned())
})
.flatten()
.collect();
if name.contains('.') {
self.names_options_in_network_exact(name, network_id, maybe_filter)
.and_then(|results| {
if results.is_empty() {
Err(ProtocolError::InvalidName {
name: name.to_string(),
reason: "No matching protocols for given TLD".to_string(),
})
} else {
Ok(results.into_iter().take(MAX_NAMES_LIMIT).collect())
}
})
} else {
let suggestions: Vec<_> = tlds
.into_iter()
.map(|tld| format!("{}.{}", name, tld.0))
.filter_map(|name_with_tld| {
self.names_options_in_network_with_suggestions(
&name_with_tld,
network_id,
maybe_filter.as_ref().cloned(),
)
.map_err(|e| {
log::debug!("Failed to get suggestions for {}: {}", name_with_tld, e);
e
})
.ok()
})
.flatten()
.take(MAX_NAMES_LIMIT)
.collect();
if suggestions.is_empty() {
Err(ProtocolError::InvalidName {
name: name.to_string(),
reason: "No valid TLDs".to_string(),
})
} else {
Ok(suggestions)
}
}
}

Copy link
Contributor

@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)
blockscout-ens/bens-logic/src/protocols/protocoler.rs (1)

288-318: Consider making the implementation more functional.

The implementation could be more concise by using functional patterns:

     pub fn fetch_domain_options(
         &self,
         name_with_tld: &str,
         network_id: i64,
         maybe_filter: Option<NonEmpty<String>>,
     ) -> Result<Vec<DomainNameOnProtocol>, ProtocolError> {
         let tld =
             Tld::from_domain_name(name_with_tld).ok_or_else(|| ProtocolError::InvalidName {
                 name: name_with_tld.to_string(),
                 reason: "Invalid TLD".to_string(),
             })?;

-        let protocols = self.protocols_of_network_for_tld(network_id, tld, maybe_filter)?;
-
-        let mut results = Vec::new();
-        for deployed_protocol in protocols {
-            let empty_label_hash = deployed_protocol
-                .protocol
-                .info
-                .protocol_specific
-                .empty_label_hash();
-
-            let domain_name = DomainName::new(name_with_tld, empty_label_hash)?;
-            results.push(DomainNameOnProtocol {
-                inner: domain_name,
-                deployed_protocol,
-            });
-        }
-
-        Ok(results)
+        self.protocols_of_network_for_tld(network_id, tld, maybe_filter)?
+            .into_iter()
+            .map(|deployed_protocol| {
+                let empty_label_hash = deployed_protocol
+                    .protocol
+                    .info
+                    .protocol_specific
+                    .empty_label_hash();
+                DomainName::new(name_with_tld, empty_label_hash)
+                    .map(|domain_name| DomainNameOnProtocol {
+                        inner: domain_name,
+                        deployed_protocol,
+                    })
+            })
+            .collect()
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9bfeb5d and 64213c9.

📒 Files selected for processing (3)
  • blockscout-ens/bens-logic/src/protocols/protocoler.rs (7 hunks)
  • blockscout-ens/bens-server/tests/data/domains/abcnews_eth/detailed.json (1 hunks)
  • blockscout-ens/bens-server/tests/domains.rs (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • blockscout-ens/bens-server/tests/domains.rs
🧰 Additional context used
🪛 Biome (1.9.4)
blockscout-ens/bens-server/tests/data/domains/abcnews_eth/detailed.json

[error] 20-20: Expected a property but instead found '{'.

Expected a property here.

(parse)


[error] 20-20: Property key must be double quoted

(parse)


[error] 20-20: unexpected character |

(parse)


[error] 20-20: expected , but instead found json_encode

Remove json_encode

(parse)


[error] 20-20: unexpected character (

(parse)


[error] 20-20: unexpected character )

(parse)


[error] 20-20: unexpected character |

(parse)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Unit, doc and integration tests
  • GitHub Check: Linting
🔇 Additional comments (6)
blockscout-ens/bens-logic/src/protocols/protocoler.rs (6)

86-87: LGTM! Improved variable naming.

The variable names in pattern matching are now more descriptive and aligned with the protocol types.


123-123: LGTM! Added empty_label_hash support for D3ConnectProtocol.

The addition of empty_label_hash field brings parity with EnsLikeProtocol's functionality.


149-149: Verify the MAX_NAMES_LIMIT value.

The limit of 5 seems reasonable but should be verified against:

  1. User experience requirements
  2. Performance implications
  3. API consumer expectations

334-334: Verify the take(1) limit.

The method uses take(1) for direct matches but take(MAX_NAMES_LIMIT) for suggestions. This inconsistency might affect user experience.


419-454: LGTM! Comprehensive test coverage.

The test module provides good coverage for the Tld struct's functionality:

  • Dot trimming
  • Empty TLD handling
  • Reverse TLD
  • Domain name parsing

351-352: ⚠️ Potential issue

Improve error handling.

Using unwrap_or_default() silently ignores errors, which could hide important issues.

-                    self.fetch_domain_options(&name_with_tld, network_id, maybe_filter.clone())
-                        .unwrap_or_default()
+                    match self.fetch_domain_options(&name_with_tld, network_id, maybe_filter.as_ref().cloned()) {
+                        Ok(options) => options,
+                        Err(e) => {
+                            log::debug!("Failed to fetch domain options for {}: {}", name_with_tld, e);
+                            Vec::new()
+                        }
+                    }

Likely invalid or redundant comment.

)
.unwrap_or_default()
self.fetch_domain_options(&name_with_tld, network_id, maybe_filter.clone())
.unwrap_or_default()
Copy link
Contributor

Choose a reason for hiding this comment

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

Does it make sense to log an error or all Err(_) variants represent a normal program flow?

Copy link
Contributor

Choose a reason for hiding this comment

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

nice deduplication btw

Copy link
Contributor

@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)
blockscout-ens/bens-logic/src/protocols/protocoler.rs (1)

288-318: LGTM! Well-structured domain options fetching logic.

Good separation of concerns and proper error handling.

Replace println! statements with proper logging using the log crate:

-        println!(
+        log::debug!(
             "name {}", name
         );
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 64213c9 and 062a4ec.

📒 Files selected for processing (3)
  • blockscout-ens/bens-logic/src/protocols/protocoler.rs (7 hunks)
  • blockscout-ens/bens-logic/tests/migrations/20231019103631_push_mock_data.sql (1 hunks)
  • blockscout-ens/bens-server/tests/domains.rs (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • blockscout-ens/bens-server/tests/domains.rs
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Unit, doc and integration tests
🔇 Additional comments (4)
blockscout-ens/bens-logic/src/protocols/protocoler.rs (3)

86-87: LGTM! Improved variable naming.

The variable names are now more descriptive and consistent with their types.


149-149: LGTM! Good use of a named constant.

Using a constant improves maintainability and makes the limit value configurable.


426-461: LGTM! Comprehensive test coverage.

The test module provides good coverage of the Tld struct functionality, including edge cases.

blockscout-ens/bens-logic/tests/migrations/20231019103631_push_mock_data.sql (1)

37-37: LGTM! Well-structured test data.

The new test domain entry maintains consistency with the existing schema and includes all required fields.

Comment on lines +326 to +372
if name.contains('.') {
println!(
"name {}", name
);
let direct = self.fetch_domain_options(name, network_id, maybe_filter)?;
if direct.is_empty() {
Err(ProtocolError::InvalidName {
name: name.to_string(),
reason: "No matching protocols for given TLD".to_string(),
})
} else {
Ok(direct.into_iter().take(1).collect())
}
} else {
let tlds = self
.networks
.get(&network_id)
.ok_or_else(|| ProtocolError::NetworkNotFound(network_id))?
.use_protocols
.iter()
.filter_map(|protocol_name| self.protocols.get(protocol_name))
.flat_map(|protocol| protocol.info.tld_list.iter().cloned())
.collect::<Vec<Tld>>();

println!(
"tlds {:?}", tlds
);

let all_names_with_protocols: Vec<_> = tlds
.into_iter()
.map(|tld| format!("{}.{}", name, tld.0))
.flat_map(|name_with_tld| {
self.fetch_domain_options(&name_with_tld, network_id, maybe_filter.clone())
.unwrap_or_default()
})
.take(MAX_NAMES_LIMIT)
.collect();

if all_names_with_protocols.is_empty() {
Err(ProtocolError::InvalidName {
name: name.to_string(),
reason: "No valid TLDs".to_string(),
})
} else {
Ok(all_names_with_protocols)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error handling and logging.

  1. Silent error handling with unwrap_or_default() could hide important issues
  2. Debug statements should use proper logging

Consider this improved implementation:

-            println!(
+            log::debug!(
                 "name {}", name
             );

-            println!(
+            log::debug!(
                 "tlds {:?}", tlds
             );

             let all_names_with_protocols: Vec<_> = tlds
                 .into_iter()
                 .map(|tld| format!("{}.{}", name, tld.0))
                 .flat_map(|name_with_tld| {
-                    self.fetch_domain_options(&name_with_tld, network_id, maybe_filter.clone())
-                        .unwrap_or_default()
+                    match self.fetch_domain_options(&name_with_tld, network_id, maybe_filter.clone()) {
+                        Ok(options) => options,
+                        Err(e) => {
+                            log::debug!("Failed to fetch options for {}: {}", name_with_tld, e);
+                            Vec::new()
+                        }
+                    }
                 })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if name.contains('.') {
println!(
"name {}", name
);
let direct = self.fetch_domain_options(name, network_id, maybe_filter)?;
if direct.is_empty() {
Err(ProtocolError::InvalidName {
name: name.to_string(),
reason: "No matching protocols for given TLD".to_string(),
})
} else {
Ok(direct.into_iter().take(1).collect())
}
} else {
let tlds = self
.networks
.get(&network_id)
.ok_or_else(|| ProtocolError::NetworkNotFound(network_id))?
.use_protocols
.iter()
.filter_map(|protocol_name| self.protocols.get(protocol_name))
.flat_map(|protocol| protocol.info.tld_list.iter().cloned())
.collect::<Vec<Tld>>();
println!(
"tlds {:?}", tlds
);
let all_names_with_protocols: Vec<_> = tlds
.into_iter()
.map(|tld| format!("{}.{}", name, tld.0))
.flat_map(|name_with_tld| {
self.fetch_domain_options(&name_with_tld, network_id, maybe_filter.clone())
.unwrap_or_default()
})
.take(MAX_NAMES_LIMIT)
.collect();
if all_names_with_protocols.is_empty() {
Err(ProtocolError::InvalidName {
name: name.to_string(),
reason: "No valid TLDs".to_string(),
})
} else {
Ok(all_names_with_protocols)
}
}
if name.contains('.') {
- println!(
- "name {}", name
- );
+ log::debug!(
+ "name {}", name
+ );
let direct = self.fetch_domain_options(name, network_id, maybe_filter)?;
if direct.is_empty() {
Err(ProtocolError::InvalidName {
name: name.to_string(),
reason: "No matching protocols for given TLD".to_string(),
})
} else {
Ok(direct.into_iter().take(1).collect())
}
} else {
let tlds = self
.networks
.get(&network_id)
.ok_or_else(|| ProtocolError::NetworkNotFound(network_id))?
.use_protocols
.iter()
.filter_map(|protocol_name| self.protocols.get(protocol_name))
.flat_map(|protocol| protocol.info.tld_list.iter().cloned())
.collect::<Vec<Tld>>();
- println!(
- "tlds {:?}", tlds
- );
+ log::debug!(
+ "tlds {:?}", tlds
+ );
let all_names_with_protocols: Vec<_> = tlds
.into_iter()
.map(|tld| format!("{}.{}", name, tld.0))
.flat_map(|name_with_tld| {
- self.fetch_domain_options(&name_with_tld, network_id, maybe_filter.clone())
- .unwrap_or_default()
+ match self.fetch_domain_options(&name_with_tld, network_id, maybe_filter.clone()) {
+ Ok(options) => options,
+ Err(e) => {
+ log::debug!("Failed to fetch options for {}: {}", name_with_tld, e);
+ Vec::new()
+ }
+ }
})
.take(MAX_NAMES_LIMIT)
.collect();
if all_names_with_protocols.is_empty() {
Err(ProtocolError::InvalidName {
name: name.to_string(),
reason: "No valid TLDs".to_string(),
})
} else {
Ok(all_names_with_protocols)
}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

BENS: better lookup
4 participants