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

✨ added client builder #141

Merged
merged 14 commits into from
Mar 31, 2024
Merged

✨ added client builder #141

merged 14 commits into from
Mar 31, 2024

Conversation

Shahab96
Copy link
Contributor

@Shahab96 Shahab96 commented Mar 23, 2024

resolves #107

  • The setup script for tests seems to use memcached 1.5.20 and the gitignore needed to be updated so I've added that as well. Tests now use the latest version of memcached (1.6.26 as of the time of this PR)
  • While read and write timeouts are set in the client builder, I am unsure if the connection would retain those settings if it gets dropped and a new one is made.

Summary by CodeRabbit

  • New Features

    • Enhanced client connections with a ClientBuilder for customizable configurations.
  • Refactor

    • Improved client connection setup process for better performance and usability.
  • Chores

    • Updated .gitignore to reference the latest memcached version.

Copy link
Contributor

coderabbitai bot commented Mar 23, 2024

Walkthrough

The recent update enhances the client connection configuration in the project by introducing a ClientBuilder struct. This enables detailed customization of parameters like timeouts and pool sizes, streamlining the connection setup through a builder pattern for improved flexibility and readability. The .gitignore file was updated to reflect changes in an external dependency version.

Changes

File(s) Change Summary
.gitignore Updated directory reference from memcached-1.5.20 to memcached-*.
src/client.rs Added ClientBuilder for configuring connection parameters, modified connect method.

🐇✨
In the realm of code, a rabbit did hop,
Bringing ClientBuilder to the shop.
With tweaks and tunes, here and there,
Connections now flow, light as air.
From .gitignore tweaks to connect's new plot,
A smoother journey, for data, we've got.
🌟🐾

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>.
    • 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 generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @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.

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 as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • The JSON schema for the configuration file is available here.
  • 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/coderabbit-overrides.v2.json

CodeRabbit Discord Community

Join our Discord Community to get help, request features, and share feedback.

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.

Review Status

Actionable comments generated: 0

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 471deb1 and 8d2e631.
Files selected for processing (3)
  • .gitignore (1 hunks)
  • src/client.rs (3 hunks)
  • src/lib.rs (1 hunks)
Additional comments: 4
src/lib.rs (1)
  • 83-83: The addition of ClientBuilder to the public exports is a significant change, making it accessible to users of the library. Ensure that the API of ClientBuilder is stable and well-documented, as it's now part of the public API.
src/client.rs (3)
  • 441-526: The ClientBuilder struct and its methods are well-structured and provide a flexible way to configure connection parameters. A few considerations:
  • Ensure that all configurable parameters are documented, explaining their impact on the client's behavior.
  • Consider adding validation for the parameters (e.g., max_size should be positive) to prevent misconfiguration.
  • The #[must_use] annotation on the build method is a good practice, encouraging users to handle the result properly.
  • 76-78: The addition of the builder method to the Client struct is a key integration point for the ClientBuilder. This method simplifies the creation of a Client instance with custom configurations. Ensure that the method is documented, especially highlighting how it differs from the connect method and when to use each.
  • 113-113: The modification of the connect method to utilize ClientBuilder is a significant improvement, aligning with the PR's objectives to enhance flexibility and error resilience in client configuration. This change should be highlighted in the documentation, guiding users on the transition to the new configuration approach.

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.

Review Status

Actionable comments generated: 0

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 471deb1 and 76a8cad.
Files selected for processing (3)
  • .gitignore (1 hunks)
  • src/client.rs (3 hunks)
  • src/lib.rs (1 hunks)
Files skipped from review as they are similar to previous changes (1)
  • src/lib.rs
Additional comments: 5
.gitignore (1)
  • 7-7: The update to the .gitignore file correctly reflects the change in the memcached version used in the test setup. This ensures that the project's version control ignores are accurately aligned with the actual versions of external dependencies used in testing.
src/client.rs (4)
  • 76-78: The introduction of the builder method in the Client struct is a good design choice, facilitating a more flexible and error-resilient way of constructing client instances through the ClientBuilder.
  • 113-113: Utilizing the ClientBuilder in the connect method of the Client struct is a strategic enhancement. It improves the flexibility and error resilience of the client configuration process. Ensure that all existing calls to Client::connect are compatible with this change.
  • 441-526: The ClientBuilder struct and its methods for configuring connection parameters are well-implemented, following best practices for builder pattern design in Rust. However, the build method's current implementation does not return an error, despite being designed to accommodate potential future modifications that might introduce error conditions.

Consider adding a comment explaining the rationale behind designing the build method to return a Result type, even though it currently does not return an error. This will help future maintainers understand the design choice and ensure that the method's signature is forward-compatible with potential error conditions.

  • 521-522: Setting the read_timeout and write_timeout in the build method of the ClientBuilder after creating the Client instance is a logical approach. However, it's important to ensure that these settings persist across dropped and re-established connections, as mentioned in the PR objectives. This might require additional logic in the connection management to re-apply these settings upon reconnection.

src/client.rs Outdated
self
}

#[must_use]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

On second thought I'm wondering if this is truly needed. I don't imagine people would make a connection and just discard it.

@Shahab96
Copy link
Contributor Author

@coderabbitai review

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.

Review Status

Actionable comments generated: 0

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 471deb1 and f547b43.
Files ignored due to path filters (1)
  • Cargo.toml is excluded by: !**/*.toml
Files selected for processing (3)
  • .gitignore (1 hunks)
  • src/client.rs (3 hunks)
  • src/lib.rs (1 hunks)
Files skipped from review as they are similar to previous changes (1)
  • src/lib.rs
Additional comments: 3
.gitignore (1)
  • 7-7: The update in the .gitignore file to reference memcached-1.6.9 aligns with the PR objectives and ensures that the correct version is used in tests.
src/client.rs (2)
  • 441-526: The ClientBuilder struct and its methods are well-implemented, following the builder pattern to allow flexible configuration of the Client. The use of #[must_use] on the build method is a good practice to ensure that the method's result is not accidentally ignored. However, consider adding documentation comments to each method to explain their purpose and usage, enhancing maintainability and usability for other developers.
  • 73-82: > 📝 NOTE

This review was outside the diff hunks and was mapped to the diff hunk with the greatest overlap. Original lines [76-113]

The addition of the builder method and the modification of the connect method in the Client struct to utilize the ClientBuilder are correctly implemented. These changes enhance the flexibility and configurability of client connections. It's recommended to add documentation comments to the builder method to explain its purpose and how it should be used, similar to the advice given for the ClientBuilder methods.

Cargo.toml Outdated Show resolved Hide resolved
@aisk
Copy link
Owner

aisk commented Mar 24, 2024

Hi, thank you for contribution, but can you add some basic test case for these newly added codes?

And the coderabbitai is no quite useful now, please feel free to ignore it if you think it's wroung.

@shadogar
Copy link

Hi, thank you for contribution, but can you add some basic test case for these newly added codes?

And the coderabbitai is no quite useful now, please feel free to ignore it if you think it's wroung.

I can add some unit tests to check the connection being created correctly, the connection itself then gets tested by the existing tests though. Would that be good?

@aisk
Copy link
Owner

aisk commented Mar 24, 2024

Yes, please go ahead.

@Shahab96 Shahab96 requested a review from aisk March 24, 2024 17:07
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.

Review Status

Actionable comments generated: 0

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between f547b43 and 6df3883.
Files selected for processing (1)
  • src/client.rs (6 hunks)
Additional comments: 3
src/client.rs (3)
  • 441-526: The ClientBuilder struct and its methods provide a fluent interface for configuring connection parameters. However, there are a few areas that could be improved for better code quality and maintainability:
  1. Documentation: Each method in the ClientBuilder could benefit from more detailed documentation, especially regarding the default values and the impact of each setting on the client's behavior.

  2. Error Handling in build Method: The build method uses .ok()? to silently ignore errors when parsing URLs and building connection pools. It might be more appropriate to return these errors to the caller to handle them accordingly.

  3. Pool Wait Timeout: The pool_wait_timeout field is set but not used in the build method. If it's intended for future use, consider documenting its purpose or implementing its functionality.

  4. Test Coverage: Ensure that there are tests covering various configurations of the ClientBuilder, especially edge cases and error scenarios.

  5. Usage of #[must_use]: The #[must_use] attribute on the build method is a good practice. Consider adding a message to it, such as #[must_use = "The built client must be used or stored"] to provide more context.

Consider these improvements to enhance the ClientBuilder implementation and its integration with the Client.

  • 73-82: > 📝 NOTE

This review was outside the diff hunks and was mapped to the diff hunk with the greatest overlap. Original lines [76-113]

The integration of the ClientBuilder within the Client struct through the builder and connect methods is well-implemented. However, there's a potential improvement in the connect method:

  • Direct Use of builder in connect: The connect method directly delegates to builder(target).build(), which is concise but bypasses any additional configuration that might be done in the builder method in the future. If the builder method is expected to evolve, consider encapsulating the build process within it or providing a clear pathway for future enhancements.

While the current implementation is functional, consider future-proofing the builder and connect integration for potential enhancements.

  • 438-554: > 📝 NOTE

This review was outside the diff hunks and was mapped to the diff hunk with the greatest overlap. Original lines [527-604]

The updated tests demonstrate the usage of the ClientBuilder and validate its functionality. It's commendable that the tests cover various scenarios, including Unix sockets, TLS configurations, and basic operations like delete and increment. A few suggestions for the tests:

  1. Negative Test Cases: Include tests that verify the behavior of the ClientBuilder and Client when invalid configurations are provided, such as invalid URLs or unsupported parameters.

  2. Documentation: Adding comments to the tests explaining the purpose of each test case and the expected outcomes can improve readability and maintainability.

  3. Refactoring: Some test cases are repeated for the direct connect method and the builder method. Consider refactoring these tests to reduce duplication and improve test coverage efficiency.

Enhancing the test suite with negative test cases, better documentation, and refactoring can improve the robustness and clarity of the tests.

src/client.rs Outdated Show resolved Hide resolved
@aisk
Copy link
Owner

aisk commented Mar 26, 2024

I think the test code you added is a little bit redundant. I think they are not needed, and what I want to test is to check if the build method can build something what we specified and nothing is wroung.

@Shahab96
Copy link
Contributor Author

I think the test code you added is a little bit redundant. I think they are not needed, and what I want to test is to check if the build method can build something what we specified and nothing is wroung.

I'm not sure what you mean by building something and nothing is wrong, do you mean like using the builder and having the resulting connection not be configured the way the caller requested?

@aisk
Copy link
Owner

aisk commented Mar 26, 2024

Yes, I think we should just test the build result is what we specified.

@Shahab96
Copy link
Contributor Author

Yes, I think we should just test the build result is what we specified.

I'm looking into adding tests for this but I am not seeing a clear path to do so. Client does not expose any public params other than the hash function, which we just use the default anyway. None of the settings we provide can be accessed as params of the client. I'm thinking testing this would actually just be testing that r2d2 works properly, since the builder is really only calling that, and so we shouldn't need to do this.

Please let me know if I'm misunderstanding something here.

src/client.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.

Review Status

Actionable comments generated: 1

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 6df3883 and 5febf60.
Files selected for processing (1)
  • src/client.rs (6 hunks)
Additional comments: 3
src/client.rs (3)
  • 76-78: The introduction of the builder method in the Client struct is a good practice as it provides a clear and fluent interface for constructing a Client instance with customized settings. This approach enhances code readability and maintainability.
  • 113-113: The modification to the connect method to utilize the ClientBuilder for constructing a Client instance is a significant improvement. It leverages the builder pattern for more flexible and readable client configuration, aligning with modern Rust practices.
  • 492-523: The build method in the ClientBuilder struct correctly aggregates the configuration and constructs a Client instance. However, it's important to ensure that the error handling strategy is consistent and robust, especially when dealing with external resources like connection pools. Consider explicitly handling potential errors during the pool creation process and returning a detailed error message to the caller.

src/client.rs Outdated
Comment on lines 441 to 524
pub struct ClientBuilder<C: Connectable> {
target: C,
max_size: u32,
min_idle: Option<u32>,
max_lifetime: Option<Duration>,
read_timeout: Option<Duration>,
write_timeout: Option<Duration>,
}

impl<C: Connectable> ClientBuilder<C> {
pub fn new(target: C) -> Self {
ClientBuilder {
target,
max_size: 1,
min_idle: None,
max_lifetime: None,
read_timeout: None,
write_timeout: None,
}
}

/// Set the maximum number of connections managed by the pool.
pub fn max_pool_size(mut self, max_size: u32) -> Self {
self.max_size = max_size;
self
}

/// Set the minimum number of idle connections to maintain in the pool.
pub fn min_idle_conns(mut self, min_idle: u32) -> Self {
self.min_idle = Some(min_idle);
self
}

/// Set the maximum lifetime of connections in the pool.
pub fn max_conn_lifetime(mut self, max_lifetime: Duration) -> Self {
self.max_lifetime = Some(max_lifetime);
self
}

/// Set the socket read timeout for TCP connections.
pub fn read_timeout(mut self, read_timeout: Duration) -> Self {
self.read_timeout = Some(read_timeout);
self
}

/// Set the socket write timeout for TCP connections.
pub fn write_timeout(mut self, write_timeout: Duration) -> Self {
self.write_timeout = Some(write_timeout);
self
}

/// Build the client. This will create a connection pool and return a client, or an error if the connection pool could not be created.
pub fn build(self) -> Result<Client, MemcacheError> {
let urls = self.target.get_urls();
let max_size = self.max_size;
let min_idle = self.min_idle;
let max_lifetime = self.max_lifetime;

let connections = urls
.iter()
.filter_map(|url| {
let url = Url::parse(url.as_str()).ok()?;

r2d2::Pool::builder()
.max_size(max_size)
.min_idle(min_idle)
.max_lifetime(max_lifetime)
.connection_timeout(Duration::from_secs(30))
.build(ConnectionManager::new(url))
.ok()
})
.collect();

let client = Client {
connections,
hash_function: default_hash_function,
};

client.set_read_timeout(self.read_timeout)?;
client.set_write_timeout(self.write_timeout)?;

Ok(client)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

The ClientBuilder struct and its implementation are well-designed, providing a fluent interface for configuring connection parameters. However, there are a few areas for improvement:

  1. The naming of the builder methods could be more consistent with Rust's conventions. For example, max_pool_size and min_idle_conns could be renamed to with_max_pool_size and with_min_idle_conns to indicate that these methods are part of a builder pattern.
  2. The build method's error handling could be enhanced. Currently, it silently ignores errors when parsing URLs and building the connection pool. It might be more appropriate to return an error if any URL is invalid or if the connection pool cannot be created for a specific URL.
- pub fn max_pool_size(mut self, max_size: u32) -> Self {
+ pub fn with_max_pool_size(mut self, max_size: u32) -> Self {

- pub fn min_idle_conns(mut self, min_idle: u32) -> Self {
+ pub fn with_min_idle_conns(mut self, min_idle: u32) -> Self {

Consider improving error handling in the build method to ensure that failures in URL parsing or connection pool creation are communicated to the caller.


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.

Suggested change
pub struct ClientBuilder<C: Connectable> {
target: C,
max_size: u32,
min_idle: Option<u32>,
max_lifetime: Option<Duration>,
read_timeout: Option<Duration>,
write_timeout: Option<Duration>,
}
impl<C: Connectable> ClientBuilder<C> {
pub fn new(target: C) -> Self {
ClientBuilder {
target,
max_size: 1,
min_idle: None,
max_lifetime: None,
read_timeout: None,
write_timeout: None,
}
}
/// Set the maximum number of connections managed by the pool.
pub fn max_pool_size(mut self, max_size: u32) -> Self {
self.max_size = max_size;
self
}
/// Set the minimum number of idle connections to maintain in the pool.
pub fn min_idle_conns(mut self, min_idle: u32) -> Self {
self.min_idle = Some(min_idle);
self
}
/// Set the maximum lifetime of connections in the pool.
pub fn max_conn_lifetime(mut self, max_lifetime: Duration) -> Self {
self.max_lifetime = Some(max_lifetime);
self
}
/// Set the socket read timeout for TCP connections.
pub fn read_timeout(mut self, read_timeout: Duration) -> Self {
self.read_timeout = Some(read_timeout);
self
}
/// Set the socket write timeout for TCP connections.
pub fn write_timeout(mut self, write_timeout: Duration) -> Self {
self.write_timeout = Some(write_timeout);
self
}
/// Build the client. This will create a connection pool and return a client, or an error if the connection pool could not be created.
pub fn build(self) -> Result<Client, MemcacheError> {
let urls = self.target.get_urls();
let max_size = self.max_size;
let min_idle = self.min_idle;
let max_lifetime = self.max_lifetime;
let connections = urls
.iter()
.filter_map(|url| {
let url = Url::parse(url.as_str()).ok()?;
r2d2::Pool::builder()
.max_size(max_size)
.min_idle(min_idle)
.max_lifetime(max_lifetime)
.connection_timeout(Duration::from_secs(30))
.build(ConnectionManager::new(url))
.ok()
})
.collect();
let client = Client {
connections,
hash_function: default_hash_function,
};
client.set_read_timeout(self.read_timeout)?;
client.set_write_timeout(self.write_timeout)?;
Ok(client)
}
}
pub fn with_max_pool_size(mut self, max_size: u32) -> Self {
self.max_size = max_size;
self
}
pub fn with_min_idle_conns(mut self, min_idle: u32) -> Self {
self.min_idle = Some(min_idle);
self
}

@Shahab96 Shahab96 requested a review from aisk March 27, 2024 11:45
}

#[test]
fn build_client_bad_url() {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was able to come up with this, I do think it's much improved from what I previously wrote, as bad urls were just ignored from the list of connections without informing the caller

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.

Review Status

Actionable comments generated: 0

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 5febf60 and 2fc5468.
Files selected for processing (1)
  • src/client.rs (7 hunks)
Additional comments: 10
src/client.rs (10)
  • 447-454: The ClientBuilder struct is introduced to configure connection parameters flexibly. This is a positive change for enhancing configurability and future-proofing the client setup process.
  • 457-466: The ClientBuilder::new method initializes the builder with default values. It's good practice to provide sensible defaults, ensuring a smooth user experience for common use cases.
  • 468-472: The method with_max_pool_size correctly sets the maximum number of connections managed by the pool. This method follows the builder pattern conventions in Rust, which is a good practice.
  • 474-478: The method with_min_idle_conns allows setting the minimum number of idle connections, enhancing the flexibility of connection management. This is another example of the builder pattern being correctly applied.
  • 480-484: The method with_max_conn_lifetime introduces the ability to set the maximum lifetime of connections in the pool. This addition is beneficial for managing resources effectively.
  • 486-490: The method with_read_timeout sets the socket read timeout for TCP connections. This is an important feature for controlling how long the client waits for a response.
  • 492-496: The method with_write_timeout sets the socket write timeout for TCP connections, complementing the read timeout setting and providing comprehensive timeout configuration.
  • 498-530: The build method in ClientBuilder creates a connection pool and returns a client or an error if the pool could not be created. This method is crucial for the builder's functionality. However, it's noted that the method is designed to return a Result type, even though it currently cannot fail. This design choice is made with future extensibility in mind, ensuring that any potential changes that could introduce errors in the future won't result in breaking changes. The function is also annotated with #[must_use] to encourage consumers to handle the result, enhancing error checking and future compatibility.
  • 535-539: The test build_client_happy_path verifies that a client can be successfully built using the ClientBuilder. This is a basic test case that ensures the builder's primary functionality works as expected.
  • 541-544: The test build_client_bad_url checks the builder's behavior when provided with an invalid URL. This test is important for ensuring error handling works correctly in the builder.

@Shahab96
Copy link
Contributor Author

Sorry, I'm new to gitmoji, which emotes should have been used for the failing commits?

@aisk
Copy link
Owner

aisk commented Mar 30, 2024

Sorry, I'm new to gitmoji, which emotes should have been used for the failing commits?

This doesn't matter, I will squash the commits when merge the commit, so only the PR title will be used as commit message.

src/client.rs Outdated Show resolved Hide resolved
Comment on lines +460 to +464
max_size: 1,
min_idle: None,
max_lifetime: None,
read_timeout: None,
write_timeout: None,
Copy link
Owner

Choose a reason for hiding this comment

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

I'd like to let these fields have the same name as the method names.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This goes against the builder pattern, the method names would have with_ but not field names. If you still want them I can add them anyway though.

Copy link
Owner

Choose a reason for hiding this comment

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

Sorry, I made a misleading, I mean the method name without with_, like pool_max_size, but not the current max_size.

src/client.rs Outdated Show resolved Hide resolved
@aisk
Copy link
Owner

aisk commented Mar 30, 2024

Hi, I think this PR is almost done, except for the comments above, and the test case.

I think we can just add some test cases. In these test cases, we can build a Client, with some with_ methods. And we can check the result, which should contain a vector of r2d2 Pool objects, to see if the expected configs were applied.

I'm not sure if these fields are accessble or not, if not, maybe we can found some other ways to make the test, but the goal should be same.

@Shahab96
Copy link
Contributor Author

Hi, I think this PR is almost done, except for the comments above, and the test case.

I think we can just add some test cases. In these test cases, we can build a Client, with some with_ methods. And we can check the result, which should contain a vector of r2d2 Pool objects, to see if the expected configs were applied.

I'm not sure if these fields are accessble or not, if not, maybe we can found some other ways to make the test, but the goal should be same.

Those fields are all private, but we can still test that the builder works

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

Review Status

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 2fc5468 and 31fa057.
Files selected for processing (1)
  • src/client.rs (7 hunks)
Additional Context Used
Additional comments not posted (1)
src/client.rs (1)

533-571: The build method in ClientBuilder is well-implemented, ensuring that a Client instance is returned or an error if the connection pool could not be created. However, it's important to ensure that the error handling is comprehensive and informative for the user. Given the previous discussions about improving error handling, verify that all potential error cases are covered and that the errors returned are descriptive enough to help users diagnose issues.

src/client.rs Outdated
Comment on lines 576 to 591
#[test]
fn build_client_happy_path() {
let client = super::Client::builder()
.add_server("memcache://localhost:12345")
.build()
.unwrap();
assert!(client.version().unwrap()[0].1 != "");
}

#[test]
fn build_client_bad_url() {
let client = super::Client::builder()
.add_server("memcache://localhost:12345:")
.build();
assert!(client.is_err());
}
Copy link
Contributor

@coderabbitai coderabbitai bot Mar 31, 2024

Choose a reason for hiding this comment

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

The tests build_client_happy_path and build_client_bad_url effectively verify the basic functionality of the ClientBuilder. However, considering the discussion about adding more test cases, it would be beneficial to include tests that cover more scenarios, such as invalid configurations (e.g., setting a negative timeout) or edge cases in the connection parameters. This would ensure the robustness of the ClientBuilder implementation.

Would you like me to help draft additional test cases covering a wider range of scenarios for the ClientBuilder?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The tests build_client_happy_path and build_client_bad_url effectively verify the basic functionality of the ClientBuilder. However, considering the discussion about adding more test cases, it would be beneficial to include tests that cover more scenarios, such as invalid configurations (e.g., setting a negative timeout) or edge cases in the connection parameters. This would ensure the robustness of the ClientBuilder implementation.

Would you like me to help draft additional test cases covering a wider range of scenarios for the ClientBuilder?

Yes

Copy link
Contributor

Choose a reason for hiding this comment

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

#[cfg(test)]
mod additional_tests {
    use super::*;
    use std::time::Duration;

    #[test]
    fn build_client_with_negative_timeout() {
        let client = Client::builder()
            .add_server("memcache://localhost:12345")
            .with_connection_timeout(Duration::from_secs(-1))
            .build();
        assert!(client.is_err(), "Expected error when setting a negative timeout");
    }

    #[test]
    fn build_client_with_large_pool_size() {
        let client = Client::builder()
            .add_server("memcache://localhost:12345")
            .with_max_pool_size(u32::MAX)
            .build();
        assert!(client.is_ok(), "Expected successful client creation with large pool size");
    }

    #[test]
    fn build_client_with_custom_hash_function() {
        fn custom_hash_function(_key: &str) -> u64 {
            42 // A simple, predictable hash function for testing.
        }

        let client = Client::builder()
            .add_server("memcache://localhost:12345")
            .with_hash_function(custom_hash_function)
            .build()
            .unwrap();

        // This test assumes that the custom hash function will affect the selection of connections.
        // As the implementation details of connection selection are not exposed, this test might need to be adjusted.
        assert_eq!((client.hash_function)("any_key"), 42, "Expected custom hash function to be used");
    }

    #[test]
    fn build_client_with_unsupported_protocol() {
        let client = Client::builder()
            .add_server("unsupported://localhost:12345")
            .build();
        assert!(client.is_err(), "Expected error when using an unsupported protocol");
    }
}

These test cases cover a range of scenarios for the ClientBuilder, including handling of invalid configurations and the use of a custom hash function. Feel free to adjust these examples as needed to fit the specifics of your implementation and testing framework.

Copy link
Contributor

Choose a reason for hiding this comment

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

@Shahab96, here are additional test cases for the ClientBuilder to cover a wider range of scenarios, including invalid configurations and edge cases in connection parameters:

#[cfg(test)]
mod additional_tests {
    use super::*;
    use std::time::Duration;

    #[test]
    fn build_client_exceed_max_pool_size() {
        let client = Client::builder()
            .add_server("memcache://localhost:12345")
            .with_max_pool_size(u32::MAX)
            .build();
        assert!(client.is_ok(), "Should handle max pool size gracefully");
    }

    #[test]
    fn build_client_negative_timeout() {
        let client = Client::builder()
            .add_server("memcache://localhost:12345")
            .with_read_timeout(Duration::from_secs(u64::MAX))
            .build();
        assert!(client.is_err(), "Should fail on negative timeout");
    }

    #[test]
    fn build_client_zero_min_idle_conns() {
        let client = Client::builder()
            .add_server("memcache://localhost:12345")
            .with_min_idle_conns(0)
            .build();
        assert!(client.is_ok(), "Should handle zero min idle conns");
    }

    #[test]
    fn build_client_invalid_hash_function() {
        let invalid_hash_function = |_: &str| -> u64 { panic!("This should not be called"); };
        let client = Client::builder()
            .add_server("memcache://localhost:12345")
            .with_hash_function(invalid_hash_function)
            .build();
        assert!(client.is_ok(), "Should handle custom hash function gracefully");
    }

    #[test]
    fn build_client_with_all_optional_parameters() {
        let client = Client::builder()
            .add_server("memcache://localhost:12345")
            .with_max_pool_size(10)
            .with_min_idle_conns(2)
            .with_max_conn_lifetime(Duration::from_secs(30))
            .with_read_timeout(Duration::from_secs(5))
            .with_write_timeout(Duration::from_secs(5))
            .with_connection_timeout(Duration::from_secs(2))
            .build();
        assert!(client.is_ok(), "Should successfully build with all optional parameters");
    }
}

These tests aim to ensure that the ClientBuilder can handle various configurations and edge cases, such as handling maximum values, dealing with potential negative timeouts (simulated by using u64::MAX which would overflow), and ensuring that optional parameters are applied correctly.

@Shahab96
Copy link
Contributor Author

Hi, I think this PR is almost done, except for the comments above, and the test case.

I think we can just add some test cases. In these test cases, we can build a Client, with some with_ methods. And we can check the result, which should contain a vector of r2d2 Pool objects, to see if the expected configs were applied.

I'm not sure if these fields are accessble or not, if not, maybe we can found some other ways to make the test, but the goal should be same.

It looks like there are ways we can test some of the items, like the connection timeout for example, by simulating activity. However since the current client implementation doesn't have these tests either, it's likely something to add to a separate PR, as this one is just for adding the builder.
I'm still not sure exactly how to approach testing the internals, as at that point we're actually testing r2d2 and not this crate. So it might not be appropriate to do that unless we actually end up running simulations.

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

Review Status

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 31fa057 and 9ce59bd.
Files selected for processing (1)
  • src/client.rs (7 hunks)
Additional Context Used
Additional comments not posted (3)
src/client.rs (3)

491-494: The method with_max_pool_size correctly sets the maximum pool size. However, the naming convention for builder pattern methods in Rust typically includes the with_ prefix, which you've followed here. This is good practice and aligns with Rust's conventions for builder patterns.


533-570: The build method in ClientBuilder is crucial for creating a Client instance. It correctly aggregates the configuration and constructs the Client. However, it's important to ensure that error handling is robust, especially when parsing URLs and creating connection pools. Consider enhancing error handling to provide more detailed feedback to the caller in case of failure, such as invalid URLs or issues with connection pool creation.


576-591: The tests build_client_happy_path, build_client_bad_url, build_client_with_large_pool_size, and build_client_with_custom_hash_function effectively verify the basic functionality and error handling of the ClientBuilder. It's good to see coverage for both successful paths and expected failures. Consider adding more tests to cover edge cases and invalid configurations to ensure the robustness of the ClientBuilder implementation.

src/client.rs Outdated Show resolved Hide resolved
Shahab96 and others added 3 commits March 30, 2024 23:10
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
}
}

impl Connectable for &[(&str, u16)] {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These two should allow consumers to either repeatedly call add_server with a single tuple, or with a slice of tuples. That'll take care of host:port specification.

@Shahab96 Shahab96 requested a review from aisk March 31, 2024 04:26
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

Review Status

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 9ce59bd and 87e5491.
Files selected for processing (3)
  • .gitignore (1 hunks)
  • src/client.rs (7 hunks)
  • tests/setup_tests.sh (1 hunks)
Additional Context Used
Additional comments not posted (3)
.gitignore (1)

7-7: The update from a specific version to a wildcard pattern for memcached directories in .gitignore is a good practice. It simplifies management and ensures future versions are automatically ignored without needing additional updates.

tests/setup_tests.sh (1)

5-5: Updating the MEMCACHED_VERSION to "1.6.26" aligns with the PR objectives of ensuring compatibility with the latest memcached version. This is a straightforward and necessary update.

src/client.rs (1)

21-37: The implementation of the Connectable trait for various types ((&str, u16), &[(&str, u16)], Url, String, Vec<String>, &str, Vec<&str>) is a good practice. It provides flexibility in specifying connection targets for the ClientBuilder. This approach enhances the usability of the API by accommodating different types of input.

src/client.rs Outdated
Comment on lines 459 to 578
write_timeout: None,
connection_timeout: None,
hash_function: default_hash_function,
}
}

/// Add a memcached server to the pool.
pub fn add_server<C: Connectable>(mut self, target: C) -> Self {
self.target.extend(target.get_urls());
self
}

/// Set the maximum number of connections managed by the pool.
pub fn with_max_pool_size(mut self, max_size: u32) -> Self {
self.max_size = max_size;
self
}

/// Set the minimum number of idle connections to maintain in the pool.
pub fn with_min_idle_conns(mut self, min_idle: u32) -> Self {
self.min_idle = Some(min_idle);
self
}

/// Set the maximum lifetime of connections in the pool.
pub fn with_max_conn_lifetime(mut self, max_lifetime: Duration) -> Self {
self.max_lifetime = Some(max_lifetime);
self
}

/// Set the socket read timeout for TCP connections.
pub fn with_read_timeout(mut self, read_timeout: Duration) -> Self {
self.read_timeout = Some(read_timeout);
self
}

/// Set the socket write timeout for TCP connections.
pub fn with_write_timeout(mut self, write_timeout: Duration) -> Self {
self.write_timeout = Some(write_timeout);
self
}

/// Set the connection timeout for TCP connections.
pub fn with_connection_timeout(mut self, connection_timeout: Duration) -> Self {
self.connection_timeout = Some(connection_timeout);
self
}

/// Set the hash function for the client.
pub fn with_hash_function(mut self, hash_function: fn(&str) -> u64) -> Self {
self.hash_function = hash_function;
self
}

/// Build the client. This will create a connection pool and return a client, or an error if the connection pool could not be created.
pub fn build(self) -> Result<Client, MemcacheError> {
let urls = self.target.get_urls();
let max_size = self.max_size;
let min_idle = self.min_idle;
let max_lifetime = self.max_lifetime;
let timeout = self.connection_timeout;

let mut connections = vec![];

for url in urls.iter() {
let url = Url::parse(url.as_str()).map_err(|e| MemcacheError::BadURL(e.to_string()))?;

match url.scheme() {
"memcache" | "memcache+tls" | "memcache+udp" => {}
_ => {
return Err(MemcacheError::BadURL(format!("Unsupported protocol: {}", url.scheme())));
}
}

let mut builder = r2d2::Pool::builder()
.max_size(max_size)
.min_idle(min_idle)
.max_lifetime(max_lifetime);

if let Some(timeout) = timeout {
builder = builder.connection_timeout(timeout);
}

let connection = builder
.build(ConnectionManager::new(url))
.map_err(|e| MemcacheError::PoolError(e))?;

connections.push(connection);
}

let client = Client {
connections,
hash_function: self.hash_function,
};

client.set_read_timeout(self.read_timeout)?;
client.set_write_timeout(self.write_timeout)?;

Ok(client)
}
Copy link
Contributor

@coderabbitai coderabbitai bot Mar 31, 2024

Choose a reason for hiding this comment

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

The ClientBuilder struct and its methods are well-designed, providing a fluent interface for configuring connection parameters. However, there are a few areas for improvement:

  1. Consistency in Method Naming: The naming of the builder methods could be more consistent with Rust's conventions. For example, with_max_pool_size and with_min_idle_conns could be renamed to with_max_pool_size and with_min_idle_conns to indicate that these methods are part of a builder pattern.

  2. Error Handling in build Method: The build method's error handling could be enhanced. Currently, it silently ignores errors when parsing URLs and building the connection pool. It might be more appropriate to return an error if any URL is invalid or if the connection pool cannot be created for a specific URL.

- pub fn max_pool_size(mut self, max_size: u32) -> Self {
+ pub fn with_max_pool_size(mut self, max_size: u32) -> Self {

- pub fn min_idle_conns(mut self, min_idle: u32) -> Self {
+ pub fn with_min_idle_conns(mut self, min_idle: u32) -> Self {

Consider improving error handling in the build method to ensure that failures in URL parsing or connection pool creation are communicated to the caller.


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.

Suggested change
pub struct ClientBuilder {
target: Vec<String>,
max_size: u32,
min_idle: Option<u32>,
max_lifetime: Option<Duration>,
read_timeout: Option<Duration>,
write_timeout: Option<Duration>,
connection_timeout: Option<Duration>,
hash_function: fn(&str) -> u64,
}
impl ClientBuilder {
/// Create an empty client builder.
pub fn new() -> Self {
ClientBuilder {
target: vec![],
max_size: 1,
min_idle: None,
max_lifetime: None,
read_timeout: None,
write_timeout: None,
connection_timeout: None,
hash_function: default_hash_function,
}
}
/// Add a memcached server to the pool.
pub fn add_server<C: Connectable>(mut self, target: C) -> Self {
self.target.extend(target.get_urls());
self
}
/// Set the maximum number of connections managed by the pool.
pub fn with_max_pool_size(mut self, max_size: u32) -> Self {
self.max_size = max_size;
self
}
/// Set the minimum number of idle connections to maintain in the pool.
pub fn with_min_idle_conns(mut self, min_idle: u32) -> Self {
self.min_idle = Some(min_idle);
self
}
/// Set the maximum lifetime of connections in the pool.
pub fn with_max_conn_lifetime(mut self, max_lifetime: Duration) -> Self {
self.max_lifetime = Some(max_lifetime);
self
}
/// Set the socket read timeout for TCP connections.
pub fn with_read_timeout(mut self, read_timeout: Duration) -> Self {
self.read_timeout = Some(read_timeout);
self
}
/// Set the socket write timeout for TCP connections.
pub fn with_write_timeout(mut self, write_timeout: Duration) -> Self {
self.write_timeout = Some(write_timeout);
self
}
/// Set the connection timeout for TCP connections.
pub fn with_connection_timeout(mut self, connection_timeout: Duration) -> Self {
self.connection_timeout = Some(connection_timeout);
self
}
/// Set the hash function for the client.
pub fn with_hash_function(mut self, hash_function: fn(&str) -> u64) -> Self {
self.hash_function = hash_function;
self
}
/// Build the client. This will create a connection pool and return a client, or an error if the connection pool could not be created.
pub fn build(self) -> Result<Client, MemcacheError> {
let urls = self.target.get_urls();
let max_size = self.max_size;
let min_idle = self.min_idle;
let max_lifetime = self.max_lifetime;
let timeout = self.connection_timeout;
let mut connections = vec![];
for url in urls.iter() {
let url = Url::parse(url.as_str()).map_err(|e| MemcacheError::BadURL(e.to_string()))?;
match url.scheme() {
"memcache" | "memcache+tls" | "memcache+udp" => {}
_ => {
return Err(MemcacheError::BadURL(format!("Unsupported protocol: {}", url.scheme())));
}
}
let mut builder = r2d2::Pool::builder()
.max_size(max_size)
.min_idle(min_idle)
.max_lifetime(max_lifetime);
if let Some(timeout) = timeout {
builder = builder.connection_timeout(timeout);
}
let connection = builder
.build(ConnectionManager::new(url))
.map_err(|e| MemcacheError::PoolError(e))?;
connections.push(connection);
}
let client = Client {
connections,
hash_function: self.hash_function,
};
client.set_read_timeout(self.read_timeout)?;
client.set_write_timeout(self.write_timeout)?;
Ok(client)
}

Copy link
Contributor Author

@Shahab96 Shahab96 Mar 31, 2024

Choose a reason for hiding this comment

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

Bad feedback

  1. Url parsing error is mapped and returned via the ? operator.
  2. The suggestion is to delete the builder.

Copy link
Contributor

Choose a reason for hiding this comment

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

@Shahab96, I'm here to assist in enhancing the code and ensuring best practices. If there are specific aspects of the feedback you disagree with or if you have alternative suggestions, I'd be glad to discuss them further. Your insights are valuable, and I'm here to support you in making the best decisions for your project. Could you please share more details about your concerns?

src/client.rs Outdated
Comment on lines 585 to 664
#[test]
fn build_client_happy_path() {
let client = super::Client::builder()
.add_server("memcache://localhost:12345")
.build()
.unwrap();
assert!(client.version().unwrap()[0].1 != "");
}

#[test]
fn build_client_bad_url() {
let client = super::Client::builder()
.add_server("memcache://localhost:12345:")
.build();
assert!(client.is_err());
}

#[test]
fn build_client_with_large_pool_size() {
let client = super::Client::builder()
.add_server("memcache://localhost:12345")
// This is a large pool size, but it should still be valid.
// This does make the test run very slow however.
.with_max_pool_size(100)
.build();
assert!(
client.is_ok(),
"Expected successful client creation with large pool size"
);
}

#[test]
fn build_client_with_custom_hash_function() {
fn custom_hash_function(_key: &str) -> u64 {
42 // A simple, predictable hash function for testing.
}

let client = super::Client::builder()
.add_server("memcache://localhost:12345")
.with_hash_function(custom_hash_function)
.build()
.unwrap();

// This test assumes that the custom hash function will affect the selection of connections.
// As the implementation details of connection selection are not exposed, this test might need to be adjusted.
assert_eq!(
(client.hash_function)("any_key"),
42,
"Expected custom hash function to be used"
);
}

#[test]
fn build_client_zero_min_idle_conns() {
let client = super::Client::builder()
.add_server("memcache://localhost:12345")
.with_min_idle_conns(0)
.build();
assert!(client.is_ok(), "Should handle zero min idle conns");
}

#[test]
fn build_client_invalid_hash_function() {
let invalid_hash_function = |_: &str| -> u64 {
panic!("This should not be called");
};
let client = super::Client::builder()
.add_server("memcache://localhost:12345")
.with_hash_function(invalid_hash_function)
.build();
assert!(client.is_ok(), "Should handle custom hash function gracefully");
}

#[test]
fn build_client_with_unsupported_protocol() {
let client = super::Client::builder()
.add_server("unsupported://localhost:12345")
.build();
assert!(client.is_err(), "Expected error when using an unsupported protocol");
}
Copy link
Contributor

@coderabbitai coderabbitai bot Mar 31, 2024

Choose a reason for hiding this comment

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

The tests for the ClientBuilder (build_client_happy_path, build_client_bad_url, build_client_with_large_pool_size, build_client_with_custom_hash_function, build_client_zero_min_idle_conns, build_client_invalid_hash_function, build_client_with_unsupported_protocol, build_client_with_all_optional_parameters) effectively verify the basic functionality and error handling of the ClientBuilder. However, considering the discussion about adding more test cases, it would be beneficial to include tests that cover more scenarios, such as invalid configurations (e.g., setting a negative timeout) or edge cases in the connection parameters. This would ensure the robustness of the ClientBuilder implementation.

Would you like me to help draft additional test cases covering a wider range of scenarios for the ClientBuilder?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No

Copy link
Contributor

Choose a reason for hiding this comment

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

@Shahab96, understood. If you have any other requests or need assistance in the future, feel free to let me know.

Copy link
Owner

@aisk aisk left a comment

Choose a reason for hiding this comment

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

Thank you for continue the work, I only have two last comments.

src/client.rs Show resolved Hide resolved
src/client.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

Review Status

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 87e5491 and bec939b.
Files selected for processing (1)
  • src/client.rs (4 hunks)
Additional Context Used
Additional comments not posted (3)
src/client.rs (3)

459-588: The ClientBuilder struct and its methods provide a fluent interface for configuring and building a Client instance. However, there are several areas for improvement and clarification:

  1. Error Handling in add_server and build Methods: Both methods perform URL validation but handle potential errors differently. It's good practice to ensure consistent error handling across the builder pattern. Consider refining the error messages for clarity and actionability.

  2. Method Naming Consistency: The method names like with_max_pool_size, with_min_idle_conns, etc., follow the builder pattern naming convention well. However, ensure that all builder methods that set properties follow this naming convention for consistency.

  3. Documentation and Examples: While the methods have comments, adding more detailed documentation, especially for the build method, would be beneficial. Including examples of handling errors returned by the build method could improve usability.

  4. Validation of Configuration Values: Currently, there's no visible validation for the configuration values (e.g., negative timeouts, excessively large pool sizes). Adding validation steps could prevent runtime issues and provide clearer errors during the build process.

  5. Connection Protocol Validation: The validation of supported protocols ("memcache", "memcache+tls", "memcache+udp") in the build method is crucial. Consider extracting this into a separate method for clarity and potential reuse.

  6. Setting Timeouts on Client: The build method sets read and write timeouts on the Client after its creation. This approach works but consider if there's a more integrated way to handle this within the Client's initialization to encapsulate all configuration steps within the builder.

Consider these points to enhance the ClientBuilder's usability, error handling, and documentation.


21-37: The implementations of the Connectable trait for different types ((&str, u16), &[(&str, u16)], Url) are concise and serve their purpose well. However, consider the following for potential improvement:

  1. Consistency in Return Statements: The return keyword is used explicitly in these implementations, which is not necessary in Rust when returning the last expression in a block. Removing return and the semicolon for the last expression can make the code more idiomatic.

  2. Error Handling for URL Construction: For the tuple and array implementations, the format is straightforward and unlikely to fail. However, consider scenarios where URL construction might fail due to invalid inputs and how these cases are handled or reported.

Consider making the return statements more idiomatic by omitting the explicit return keyword where possible.


594-707: The tests for the ClientBuilder cover various scenarios, including happy paths, bad URLs, unsupported protocols, and custom configurations. These tests are well-structured and provide good coverage of the builder's functionality. However, consider the following points for further enhancement:

  1. Negative and Edge Case Testing: While some tests cover error scenarios (e.g., build_client_bad_url, build_client_with_unsupported_protocol), adding more tests for negative and edge cases, such as invalid configuration values (e.g., negative timeouts), could further ensure the robustness of the builder.

  2. Documentation and Comments: Adding comments to the tests explaining the purpose and expected outcomes can improve readability and maintainability, especially for more complex scenarios.

  3. Test Organization: As the number of tests grows, organizing them into submodules or using a more descriptive naming convention could help manage the test suite more effectively.

Enhance the test suite by adding more negative and edge case tests, improving documentation, and considering test organization strategies.

@aisk aisk merged commit 9fa76e7 into aisk:master Mar 31, 2024
3 of 4 checks passed
@aisk
Copy link
Owner

aisk commented Mar 31, 2024

Thank you for your contribution!

@Shahab96 Shahab96 deleted the client-builder branch March 31, 2024 16:13
@Shahab96 Shahab96 mentioned this pull request Mar 31, 2024
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.

Add ClientBuilder
3 participants