Skip to content

Conversation

@absk1317
Copy link

@absk1317 absk1317 commented Dec 31, 2025

Summary

This PR replaces HTTParty with http.rb as the HTTP client library. The http.rb gem provides built-in support for persistent HTTP connections, which allows TCP connection reuse across multiple requests in high-throughput scenarios.

Changes

  • Replace httparty dependency with http (~> 5.0) in gemspec
  • Rewrite HTTPRequest class to use http.rb API
  • Add persistent: true client option to enable connection reuse
  • Update tests to work with the new HTTP client

Usage

# Enable persistent connections (opt-in)
client = Meilisearch::Client.new('http://127.0.0.1:7700', 'masterKey', { persistent: true })

Why http.rb?

  • Pure Ruby implementation (no native extensions required)
  • Built-in persistent connection support via .persistent() method
  • Clean, chainable API
  • Active maintenance and good documentation

Checklist

  • Tests pass (bundle exec rspec)
  • Linter passes (bundle exec rubocop lib/ spec/)
  • Backward compatible - existing public API unchanged

Summary by CodeRabbit

  • New Features

    • Support for persistent HTTP connections to reduce overhead and improve performance.
  • Documentation

    • Added a "Persistent connections" section to Getting Started with usage examples.
  • Improvements

    • Centralized and strengthened network error handling and retry semantics for clearer failures and safer retries.
    • Replaced the HTTP implementation to enable persistent clients and more consistent header/response handling.
  • Tests

    • Tests updated to exercise retry behavior, relax strict request assertions, and fix a typo.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 31, 2025

📝 Walkthrough

Walkthrough

Replaces HTTParty with the http gem, adds per-instance HTTP client with optional persistent connections, centralizes request building/execution (NO_BODY sentinel, unified execute flow), expands error mapping and retry logic, and updates tests and README docs.

Changes

Cohort / File(s) Summary
HTTP client core
lib/meilisearch/http_request.rb
Replaced HTTParty with http gem; added NO_BODY sentinel; introduced per-instance client builder (build_http_client) and persistent flag; unified request flow (build_request_optionsexecute_request); improved response parsing and error mapping; added retry control helpers (can_retry?, safe_to_retry?); updated public http_* signatures to accept NO_BODY.
Dependency manifest
meilisearch.gemspec
Replaced runtime dependency httparty (~> 0.24) with http (~> 5.0).
Documentation
README.md
Added "Persistent connections" section showing client creation with persistent: true and noting use of http gem persistent connections.
Error-handling tests
spec/meilisearch/client/errors_spec.rb
Tests now stub the instance-level @http_client methods (instead of class-level calls), adjust descriptions/contexts, and assert Meilisearch::CommunicationError on Errno::EPIPE.
Request & retry tests
spec/meilisearch/client/requests_spec.rb, spec/meilisearch_spec.rb
Removed a strict POST precondition; refactored retry tests to stub the client's internal @http_client, simulate HTTP::TimeoutError for retryable scenarios and Errno::ECONNREFUSED for non-retryable scenarios; assert retry counts and raised errors.
Integration/header tests
spec/meilisearch/index/base_spec.rb
Relaxed strict header/options mocking to presence checks for keys (e.g., max_retries, retry_multiplier, timeout, convert_body?) and header contents; call fetch_info to verify index UID instead of exact mocked responses.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant HTTPClient as "http gem client"
  participant API as "Meilisearch API"

  rect rgb(245,250,255)
    Client->>HTTPClient: build_http_client(options, persistent?)
    note right of HTTPClient: persistent:true → connection reuse
  end

  Client->>HTTPClient: send_request(method, path, params, body or NO_BODY)
  alt body present
    Client->>HTTPClient: build_request_options -> serialize body (if convert_body?)
  else NO_BODY
    note right of HTTPClient: omit request body
  end

  HTTPClient->>API: execute HTTP request
  API-->>HTTPClient: response (200 / error)

  alt success
    HTTPClient->>Client: validate_response -> parse_response_body -> return
  else transient error (HTTP::TimeoutError, connection)
    HTTPClient->>Client: map error -> can_retry? -> retry with backoff
  else fatal error (Errno::ECONNREFUSED, InvalidURI)
    HTTPClient->>Client: raise CommunicationError / InvalidURI
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Suggested labels

maintenance

Suggested reviewers

  • curquiza

Poem

🐰
I nibble sockets, stitch a steady thread,
Persistent hops keep latency fed.
NO_BODY crumbs tucked out of sight,
Retries hum soft through day and night.
Hop — connections wake and spread.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.75% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately summarizes the main change: replacing HTTParty with http.rb library and adding persistent HTTP connection support.
✨ Finishing touches
  • 📝 Generate docstrings

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

❤️ Share

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

@absk1317 absk1317 closed this Dec 31, 2025
Copy link

@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)
lib/meilisearch/http_request.rb (1)

134-137: Consider treating DELETE as idempotent for retry logic.

The current implementation only retries DELETE on connect timeout (when the request didn't reach the server). However, DELETE operations in REST APIs are typically idempotent—deleting the same resource multiple times has the same effect as deleting it once. Consider whether DELETE should be treated like GET for retry purposes.

# Option 1: Treat DELETE as always safe to retry (like GET)
def safe_to_retry?(method, error)
  [:get, :delete].include?(method) || ([:post, :put, :patch].include?(method) && error.is_a?(HTTP::ConnectTimeoutError))
end

# Option 2: Keep current behavior but document the reasoning
# Current: Only retry DELETE on connect timeout to avoid potential issues with
# non-idempotent delete implementations or cascading deletes
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 27a3998 and cda1c3c.

📒 Files selected for processing (7)
  • README.md
  • lib/meilisearch/http_request.rb
  • meilisearch.gemspec
  • spec/meilisearch/client/errors_spec.rb
  • spec/meilisearch/client/requests_spec.rb
  • spec/meilisearch/index/base_spec.rb
  • spec/meilisearch_spec.rb
💤 Files with no reviewable changes (1)
  • spec/meilisearch/client/requests_spec.rb
🧰 Additional context used
🧬 Code graph analysis (3)
spec/meilisearch_spec.rb (1)
lib/meilisearch/client.rb (1)
  • indexes (68-76)
spec/meilisearch/index/base_spec.rb (3)
lib/meilisearch/client.rb (2)
  • index (137-139)
  • fetch_index (146-148)
lib/meilisearch/version.rb (1)
  • qualified_version (6-8)
lib/meilisearch/index.rb (1)
  • fetch_info (24-28)
spec/meilisearch/client/errors_spec.rb (1)
lib/meilisearch/client.rb (1)
  • index (137-139)
🔇 Additional comments (18)
spec/meilisearch/client/errors_spec.rb (2)

15-15: LGTM!

Typo fix improves test description clarity.


25-36: LGTM!

The updated test correctly stubs the instance-level HTTP client and verifies that Errno::EPIPE raises CommunicationError, aligning with the new HTTP client architecture.

spec/meilisearch_spec.rb (2)

39-54: LGTM!

The retry test correctly validates that retryable errors (HTTP::TimeoutError) trigger the configured number of retries (3), resulting in 4 total calls. The low retry_multiplier (0.1) ensures fast test execution.


56-71: LGTM!

The test correctly validates that non-retryable errors (Errno::ECONNREFUSED) do not trigger retries, regardless of the max_retries setting. This ensures the retry logic distinguishes between transient and permanent failures.

spec/meilisearch/index/base_spec.rb (2)

102-116: LGTM!

The test correctly validates that options are passed through to the index and that headers are properly configured. Using include instead of strict equality is more resilient to internal implementation details. The fetch_info call provides end-to-end validation that the configured options work correctly.


118-133: LGTM!

The test correctly validates that custom client agents are combined with the default user agent in the format "custom_agent;default_agent" and that all options work end-to-end via the fetch_info call.

lib/meilisearch/http_request.rb (10)

18-19: LGTM!

The NO_BODY sentinel constant is a clean pattern to distinguish "no body provided" from "body is nil", which is important for HTTP methods where an empty body has different semantics than no body.


10-16: LGTM!

The default options are well-chosen:

  • persistent: false provides a safe default with opt-in for persistent connections
  • convert_body?: true maintains backward compatibility with automatic JSON serialization
  • Retry settings (max_retries: 2, retry_multiplier: 1.2) provide reasonable defaults for transient failures

29-47: LGTM!

The unified request routing through send_request provides a clean abstraction that centralizes:

  • Error handling
  • Retry logic
  • Request construction
  • Response validation

The use of NO_BODY as a default parameter for POST/PUT/PATCH methods properly distinguishes between "no body" and "nil body".


51-60: LGTM!

The header construction is clean and correct:

  • Conditional Authorization header based on API key presence
  • Proper User-Agent combining custom agents with the library version
  • compact removes nil values to avoid sending empty headers

67-73: LGTM!

The URL construction logic correctly handles both modes:

  • Persistent mode: uses relative paths (client already bound to base_url)
  • Non-persistent mode: prepends base_url to relative path

Options merging allows per-request overrides while preserving instance defaults.


94-96: LGTM!

Clear retry gate that combines attempt count with method/error safety checks.


113-126: LGTM!

Response validation and parsing are well-structured:

  • Non-success responses raise ApiError with status and body
  • Empty responses return nil (avoiding parse errors)
  • JSON parse failures gracefully fall back to raw body string
  • This maintains backward compatibility with existing code expecting parsed JSON responses

128-132: LGTM!

The URI error handling provides a helpful error message when the protocol is missing, which is a common mistake (e.g., using "localhost:7700" instead of "http://localhost:7700").


98-111: No action needed. The http.rb gem handles nil body values correctly—when a request body is nil or omitted, it simply sends no payload rather than erroring. The code correctly sets request_opts[:body] = nil and http.rb will handle it as expected.

Likely an incorrect or invalid review comment.


75-92: Error handling is complete and appropriate as-is.

The error handling correctly covers all exceptions that http.rb raises in its default behavior. Since the code does not enable http.rb's RaiseError feature, HTTP::ResponseError and HTTP::StatusError are never raised—response errors are instead handled manually via validate_response() which checks status codes and raises custom ApiError objects. The current catch blocks appropriately handle connection errors, timeouts, and URI validation errors that can occur in normal operation.

README.md (1)

99-108: No changes needed. The documentation is accurate. HTTP.rb version 5.0 supports persistent connections, and the Meilisearch-ruby client correctly implements this feature by calling the persistent() method on the HTTP client instance, as shown in lib/meilisearch/http_request.rb (client.persistent(@base_url)). The { persistent: true } option is properly handled and defaults to false.

meilisearch.gemspec (1)

18-18: The http gem version 5.0 exists, is stable, and has no known security vulnerabilities.

Version 5.0.0 was released in May 2021 and is marked stable. Multiple patch releases (5.0.1–5.0.4) followed, confirming stability. No CVEs or security advisories have been published for version 5.0.x. The dependency specification '~> 5.0' is appropriate.

@absk1317 absk1317 reopened this Dec 31, 2025
Copy link

@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 (1)
lib/meilisearch/http_request.rb (1)

108-112: Clarify the nil body behavior.

When body is explicitly nil (not NO_BODY), nil.to_json produces "null" which will be sent as the request body. This is technically correct given the sentinel pattern, but could be surprising.

Consider adding a brief comment explaining this intentional distinction, or handle nil specially if sending "null" as a body is never a valid use case for Meilisearch.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cda1c3c and 8cad038.

📒 Files selected for processing (1)
  • lib/meilisearch/http_request.rb
🧰 Additional context used
🧬 Code graph analysis (1)
lib/meilisearch/http_request.rb (2)
lib/meilisearch/error.rb (8)
  • attr_reader (7-53)
  • attr_reader (55-62)
  • attr_reader (64-71)
  • attr_reader (73-80)
  • initialize (23-33)
  • initialize (58-61)
  • initialize (67-70)
  • initialize (76-79)
lib/meilisearch/utils.rb (1)
  • parse (84-90)
🔇 Additional comments (7)
lib/meilisearch/http_request.rb (7)

7-12: Good documentation for thread safety considerations.

The thread safety documentation addresses the concern from the previous review. Clear guidance for users to create separate client instances per thread when using persistent connections.


24-25: Good use of sentinel pattern for body handling.

Using NO_BODY = Object.new.freeze as a sentinel is a clean way to distinguish between "no body passed" and "body is explicitly nil". This enables proper handling of edge cases in HTTP methods.


35-53: Clean unified request flow.

The refactoring to route all HTTP methods through send_request with a consistent interface is a good design improvement over the previous implementation.


81-98: Solid error handling and retry logic.

The error handling correctly maps HTTP library exceptions to Meilisearch-specific errors. The retry logic with exponential backoff (retry_multiplier ** attempts) is well-implemented, and the can_retry? check ensures safe retry semantics.


119-132: Good response handling with graceful fallback.

The response validation correctly uses ApiError with the expected parameters. The JSON parsing with fallback to raw body handles edge cases like non-JSON responses gracefully.


140-143: Correct retry safety semantics.

The safe_to_retry? method correctly implements retry safety: GET requests are idempotent and always safe to retry, while POST/PUT/PATCH/DELETE are only safe to retry on ConnectTimeoutError (when the request never reached the server). This prevents duplicate side effects from non-idempotent operations.


73-79: Line number correction: The code referenced is at lines 105-110, not 73-79.

The URL concatenation concern does not apply in practice. Throughout the codebase, relative paths consistently start with / (e.g., /indexes, /swap-indexes) while the base URL never ends with / (e.g., http://127.0.0.1:8800). This convention ensures correct concatenation: http://127.0.0.1:8800 + /indexes = http://127.0.0.1:8800/indexes.

Likely an incorrect or invalid review comment.

@absk1317
Copy link
Author

@coderabbitai help

@coderabbitai
Copy link

coderabbitai bot commented Dec 31, 2025

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.
    • 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.
  • 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 the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

CodeRabbit commands

These commands are invoked using PR/Issue 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 evaluate custom pre-merge check --instructions <custom-checks-instructions> --name <custom-checks-title> [--mode <error|warning>] to validate the custom pre-merge checks instructions. Defaults to error when --mode is omitted.
    • @coderabbitai ignore pre-merge checks to override pre-merge checks and get an approval on PR.
    • @coderabbitai run pre-merge checks to run pre-merge checks on the pull request.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve to 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 or @coderabbit 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.
  • You can also validate your configuration using the online YAML validator.
  • 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

Status, support, documentation and community

  • Visit our status page to check the current availability of CodeRabbit.
  • Create a ticket on our support page for assistance with any issues or questions.
  • Visit our documentation site for detailed information on how to use CodeRabbit.
  • Join our Discord community to connect with other users and get help from the community.
  • Follow us on X/Twitter for updates and announcements.

@absk1317
Copy link
Author

@coderabbitai full review

@coderabbitai
Copy link

coderabbitai bot commented Dec 31, 2025

✅ Actions performed

Full review triggered.

Copy link

@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 (1)
lib/meilisearch/http_request.rb (1)

157-164: Minor: Redundant nil check.

Since response.body.to_s will never return nil (it returns an empty string for empty bodies), the body.nil? check on line 159 is unnecessary. However, it doesn't affect correctness.

🔎 Optional simplification
     def parse_response_body(response)
       body = response.body.to_s
-      return nil if body.nil? || body.empty?
+      return nil if body.empty?
 
       JSON.parse(body)
     rescue JSON::ParserError
       body
     end
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8cad038 and 88773a1.

📒 Files selected for processing (1)
  • lib/meilisearch/http_request.rb
🧰 Additional context used
🧬 Code graph analysis (1)
lib/meilisearch/http_request.rb (1)
lib/meilisearch/error.rb (8)
  • attr_reader (7-53)
  • attr_reader (55-62)
  • attr_reader (64-71)
  • attr_reader (73-80)
  • initialize (23-33)
  • initialize (58-61)
  • initialize (67-70)
  • initialize (76-79)
🔇 Additional comments (9)
lib/meilisearch/http_request.rb (9)

16-26: LGTM - Good use of sentinel pattern and conservative defaults.

The NO_BODY sentinel is a clean pattern for distinguishing "no body passed" from "body is explicitly nil". The persistent: false default is appropriately conservative for opt-in behavior.


44-85: LGTM - Clean unified API design.

The public HTTP methods follow a consistent pattern, properly delegate to send_request, and the YARD documentation clearly describes the parameters and return types.


89-98: LGTM - Headers built correctly.

The conditional Authorization header and User-Agent construction are well-implemented. The .compact call cleanly removes nil values.


100-103: Thread safety documentation addresses previous concern.

The implementation correctly builds the HTTP client with optional persistent connections. The thread safety note at the class level (lines 9-12) properly documents the expected usage pattern in multi-threaded environments.


105-111: LGTM - URL construction handles persistent vs non-persistent correctly.

The URL is correctly built as just the relative path for persistent connections (since http.rb's persistent client already has the base URL), and as the full URL otherwise.


113-130: LGTM - Error handling and retry logic are well-structured.

The error handling correctly maps various exceptions to appropriate Meilisearch error types. The retry logic properly tracks attempts and uses exponential backoff via retry_multiplier.


132-134: Well-designed retry safety check.

The safe_to_retry? method correctly handles idempotency concerns: GET requests can always be retried, while non-GET methods are only retried on ConnectTimeoutError (when the request hasn't reached the server). This prevents accidental duplicate mutations from retry logic.

Also applies to: 172-175


136-149: LGTM - Request options built correctly.

The NO_BODY sentinel check using .equal? ensures only the exact sentinel object triggers the "no body" path, while explicit nil body values are properly JSON-serialized. The conditional header override is clean.


166-170: LGTM - Helpful URI error messaging.

The scheme detection and suggested fix ("Did you mean https://...") provides a much better developer experience for the common mistake of omitting the URL scheme.

Copy link

@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)
lib/meilisearch/http_request.rb (1)

157-164: Minor: body.nil? check is redundant.

At line 159, body.nil? will never be true because response.body.to_s at line 158 always returns a string (empty string at minimum, never nil). The body.empty? check alone is sufficient.

🔎 Suggested simplification
 def parse_response_body(response)
   body = response.body.to_s
-  return nil if body.nil? || body.empty?
+  return nil if body.empty?

   JSON.parse(body)
 rescue JSON::ParserError
   body
 end
README.md (1)

99-108: Good documentation for the new feature.

The persistent connections section is well-placed and clearly explains:

  • When to use it (high-throughput scenarios)
  • How to enable it
  • What benefit it provides

Consider adding a note about thread safety (creating separate client instances per thread) as mentioned in the code comments, especially since this is relevant for the high-throughput use cases where persistent connections shine.

🔎 Optional: Add thread safety note
 This uses `http.rb`'s built-in persistent connection support, reducing the overhead of establishing new connections for each request.
+
+> **Note:** In multi-threaded environments (Puma, Sidekiq), create a separate `Client` instance per thread rather than sharing one across threads.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 27a3998 and 88773a1.

📒 Files selected for processing (7)
  • README.md
  • lib/meilisearch/http_request.rb
  • meilisearch.gemspec
  • spec/meilisearch/client/errors_spec.rb
  • spec/meilisearch/client/requests_spec.rb
  • spec/meilisearch/index/base_spec.rb
  • spec/meilisearch_spec.rb
💤 Files with no reviewable changes (1)
  • spec/meilisearch/client/requests_spec.rb
🧰 Additional context used
🧬 Code graph analysis (3)
spec/meilisearch/client/errors_spec.rb (1)
lib/meilisearch/client.rb (1)
  • index (137-139)
spec/meilisearch/index/base_spec.rb (3)
lib/meilisearch/client.rb (2)
  • index (137-139)
  • create_index (95-101)
lib/meilisearch/version.rb (1)
  • qualified_version (6-8)
lib/meilisearch/index.rb (1)
  • fetch_info (24-28)
spec/meilisearch_spec.rb (1)
lib/meilisearch/client.rb (1)
  • indexes (68-76)
🔇 Additional comments (15)
lib/meilisearch/http_request.rb (9)

24-26: Good use of sentinel pattern for NO_BODY.

Using Object.new.freeze as a sentinel value effectively distinguishes between "no body provided" and "body is explicitly nil". This is a clean pattern that prevents ambiguity in the API.


44-85: Clean centralized request flow.

The HTTP method implementations properly delegate to send_request with consistent signatures. The use of NO_BODY as default for body-accepting methods (POST/PUT/PATCH) is appropriate.


100-103: Persistent connection implementation is correct.

The conditional wrapping with .persistent(@base_url) properly enables connection reuse when the option is set. Thread safety concerns were addressed with the documentation note at lines 9-12.


105-111: URL handling for persistent connections is correct.

When using persistent connections, http.rb expects relative paths since the base URL is already set on the client. The conditional at line 107 correctly handles both cases.


132-134: Retry count logic is correct.

With max_retries: 3, the condition attempts <= options[:max_retries] allows retries on attempts 1, 2, and 3, resulting in 4 total calls (1 initial + 3 retries), which matches the test expectations.


166-170: Helpful error message for missing URL scheme.

The check at line 167 provides a user-friendly suggestion when the URL is missing a protocol, guiding users to add https://. This improves the developer experience.


172-175: Correct retry safety logic for non-idempotent requests.

The implementation correctly identifies that:

  • GET requests are always safe to retry (idempotent)
  • POST/PUT/PATCH/DELETE only retry on ConnectTimeoutError when the connection was never established, preventing duplicate writes

This follows best practices for retry logic with non-idempotent operations.


140-144: Verify if explicitly passing nil body (serialized to "null") is correct for the Meilisearch API.

The code intentionally distinguishes between NO_BODY (no body sent) and an explicitly passed nil (body becomes the string "null"). This is confirmed by the sentinel design and comment at lines 141-142. However, there is at least one call site (lib/meilisearch/task.rb:32) that explicitly passes nil to http_post, which would result in sending "null" as the request body. Verify with the Meilisearch API documentation or integration tests whether this behavior is correct, or if explicitly passing nil should skip sending a body entirely.


119-129: The retry logic correctly handles HTTP::ConnectTimeoutError. The HTTP gem's exception hierarchy confirms that HTTP::ConnectTimeoutError is a direct subclass of HTTP::TimeoutError, so the rescue block at line 121 properly catches both connection timeouts and other timeout errors. The safe_to_retry? method then selectively retries based on the request method—allowing retries for all timeouts on GET requests, but only for HTTP::ConnectTimeoutError on non-idempotent methods (POST, PUT, PATCH, DELETE). This is correct and intentional.

spec/meilisearch/client/errors_spec.rb (1)

25-36: Test correctly validates EPIPE error handling.

The test properly:

  1. Accesses the internal @http_client instance variable
  2. Stubs the post method to raise Errno::EPIPE
  3. Verifies that CommunicationError is raised to the caller

This aligns with the centralized error handling in execute_request.

spec/meilisearch/index/base_spec.rb (2)

102-116: Improved test approach with actual HTTP calls.

The refactored test now:

  1. Verifies options are correctly merged (including convert_body?: true)
  2. Checks headers are properly constructed
  3. Actually exercises fetch_info to validate end-to-end functionality

This is a more robust integration test compared to mocking individual HTTP calls.


118-133: Custom agent test validates User-Agent composition.

The test correctly verifies that custom agents are prepended to the User-Agent header with the expected format: custom_agent;Meilisearch.qualified_version.

spec/meilisearch_spec.rb (2)

39-54: Retry test properly validates retry behavior.

The test correctly:

  1. Configures max_retries: 3 and retry_multiplier: 0.1 (fast backoff for tests)
  2. Stubs to raise HTTP::TimeoutError on every call
  3. Verifies exactly 4 calls occur (1 initial + 3 retries)
  4. Confirms Meilisearch::TimeoutError is ultimately raised

56-71: Non-retryable error test validates no retry on connection refused.

The test correctly demonstrates that Errno::ECONNREFUSED is not retried (only 1 call) despite max_retries: 10, confirming that connection errors bypass the retry logic and are immediately wrapped as CommunicationError.

meilisearch.gemspec (1)

18-18: Dependency change looks appropriate.

The switch from httparty to http (~> 5.0) aligns with the PR objectives. The pessimistic version constraint appropriately allows patch/minor updates within 5.x while preventing breaking changes from 6.0+. The http gem is actively maintained, with the latest version at 5.3.1.

@curquiza curquiza requested a review from brunoocasali January 19, 2026 17:54
@codecov
Copy link

codecov bot commented Jan 19, 2026

Codecov Report

❌ Patch coverage is 97.95918% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 99.87%. Comparing base (27a3998) to head (88773a1).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
lib/meilisearch/http_request.rb 97.95% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##              main     #670      +/-   ##
===========================================
- Coverage   100.00%   99.87%   -0.13%     
===========================================
  Files           10       10              
  Lines          809      821      +12     
===========================================
+ Hits           809      820      +11     
- Misses           0        1       +1     

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

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

@brunoocasali
Copy link
Member

Hi there, I really appreciate the effort here, thanks a lot :)

Before moving forward or not with your PR I wanted to know if your workload suffer from consistent-random timeouts along the way? I considered (even with a temp branch) this gem to replace httparty in an attempt to fix the issue meilisearch/meilisearch-rails#355 but no luck.

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.

2 participants