Conversation
There was a problem hiding this comment.
Pull request overview
This PR enables FileHandle support for POST, PUT, and PATCH requests, allowing files to be sent directly without loading them entirely into memory. It also fixes the HEAD and OPTIONS HTTP methods, updates the Body API to work with emberjson.Object instead of Dict[String, String], implements JSON caching for performance, and adds a verbose mode to Session.
- Adds FileHandle overloads for POST, PUT, and PATCH methods with file descriptor read callback
- Fixes HEAD method to use
nobody()and OPTIONS method implementation - Changes Body and Session APIs to use
emberjson.Objectfor JSON data - Adds JSON caching to Body to avoid re-parsing
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| floki/session.mojo | Adds fd_read_callback for FileHandle transfers, overloaded send() and HTTP methods for FileHandle, added verbose mode, changed API from Dict to emberjson.Object |
| floki/body.mojo | Added JSON caching with _json_cache field, changed constructor to accept emberjson.Object |
| floki/functions.mojo | Entire file commented out (likely needs update for new API) |
| test/test_session.mojo | Added test_post_file, test_put_file, test_patch_file to test FileHandle functionality; updated existing tests for new API |
| test/data/*.json | Added test data files for FileHandle testing |
| pixi.toml, pixi.lock | Updated mojo-curl dependency from 25.7.0 to 25.7.1 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| Args: | ||
| url: The URL to which the request is sent. | ||
| headers: A dictionary of HTTP headers to include in the request. | ||
| file: An optional FileHandle representing the request body. |
There was a problem hiding this comment.
The docstring incorrectly describes file as "An optional FileHandle" but the parameter is not optional (no default value). Update to: "A FileHandle representing the request body."
| file: An optional FileHandle representing the request body. | |
| file: A FileHandle representing the request body. |
| var headers: Dict[String, String] = {}, | ||
| ): | ||
| verbose: Bool = False, | ||
| ) raises: |
There was a problem hiding this comment.
The __init__ method is missing a docstring that describes the new verbose parameter. Consider adding documentation explaining that verbose enables libcurl's verbose logging mode.
| ) raises: | |
| ) raises: | |
| """ | |
| Initialize a new Session. | |
| Args: | |
| allow_redirects (Bool): Whether to follow HTTP redirects automatically. | |
| headers (Dict[String, String]): Default headers to include in requests. | |
| verbose (Bool): If True, enables libcurl's verbose logging mode for debugging. | |
| """ |
floki/session.mojo
Outdated
| if data: | ||
| var result = easy.post_fields(data) | ||
| if result != Result.OK: | ||
| raise Error("_handle_patch: Failed to set POST fields: ", easy.describe_error(result)) |
There was a problem hiding this comment.
The error message refers to "POST fields" in a PATCH request handler, which is misleading. While libcurl uses post_fields internally, the error message should be clearer, such as "Failed to set request body" or "Failed to set PATCH request fields".
| raise Error("_handle_patch: Failed to set POST fields: ", easy.describe_error(result)) | |
| raise Error("_handle_patch: Failed to set PATCH request fields: ", easy.describe_error(result)) |
floki/session.mojo
Outdated
| """ | ||
| constrained[ | ||
| method in [RequestMethod.POST, RequestMethod.PUT, RequestMethod.PATCH], | ||
| String("send: Unsupported HTTP method for FileDescriptor data. Received: ", method) |
There was a problem hiding this comment.
The error message attempts to use string interpolation with the method parameter, but method is a compile-time parameter and cannot be directly interpolated in this way. Consider using a more descriptive static error message like: "send: FileHandle data only supported for POST, PUT, and PATCH methods"
| String("send: Unsupported HTTP method for FileDescriptor data. Received: ", method) | |
| String("send: FileHandle data only supported for POST, PUT, and PATCH methods") |
| @fieldwise_init | ||
| struct DataToRead: | ||
| var data: ExternalImmutPointer[Byte] | ||
| var bytes_remaining: UInt | ||
|
|
||
|
|
||
| fn read_callback(ptr: ExternalMutPointer[c_char], size: c_size_t, nmemb: c_size_t, userdata: ExternalMutOpaquePointer) -> c_size_t: | ||
| var data = userdata.bitcast[DataToRead]() | ||
| var buffer_size = size * nmemb # Max bytes we can write to ptr | ||
|
|
||
| # Nothing to write | ||
| if buffer_size < 1: | ||
| return 0 | ||
|
|
||
| # We will copy as much data as possible into the 'ptr' buffer, but no more than 'size' * 'nmemb' bytes | ||
| # Determine how much data to copy: either remaining data or buffer capacity | ||
| var bytes_to_read = min(data[].bytes_remaining, buffer_size) | ||
| if bytes_to_read > 0: | ||
| # Copy the data into the buffer | ||
| memcpy( | ||
| dest=ptr, | ||
| src=data[].data.bitcast[Int8](), | ||
| count=Int(bytes_to_read), | ||
| ) | ||
|
|
||
| # Update the userdata to reflect the consumed data | ||
| data[].data += bytes_to_read | ||
| data[].bytes_remaining -= UInt(bytes_to_read) | ||
|
|
||
| return bytes_to_read | ||
|
|
||
| return 0 | ||
|
|
||
|
|
There was a problem hiding this comment.
The read_callback function and DataToRead struct appear to be unused in this implementation. All file reading operations use fd_read_callback instead, which reads directly from a FileDescriptor. Consider removing these unused definitions or documenting their intended future use.
| @fieldwise_init | |
| struct DataToRead: | |
| var data: ExternalImmutPointer[Byte] | |
| var bytes_remaining: UInt | |
| fn read_callback(ptr: ExternalMutPointer[c_char], size: c_size_t, nmemb: c_size_t, userdata: ExternalMutOpaquePointer) -> c_size_t: | |
| var data = userdata.bitcast[DataToRead]() | |
| var buffer_size = size * nmemb # Max bytes we can write to ptr | |
| # Nothing to write | |
| if buffer_size < 1: | |
| return 0 | |
| # We will copy as much data as possible into the 'ptr' buffer, but no more than 'size' * 'nmemb' bytes | |
| # Determine how much data to copy: either remaining data or buffer capacity | |
| var bytes_to_read = min(data[].bytes_remaining, buffer_size) | |
| if bytes_to_read > 0: | |
| # Copy the data into the buffer | |
| memcpy( | |
| dest=ptr, | |
| src=data[].data.bitcast[Int8](), | |
| count=Int(bytes_to_read), | |
| ) | |
| # Update the userdata to reflect the consumed data | |
| data[].data += bytes_to_read | |
| data[].bytes_remaining -= UInt(bytes_to_read) | |
| return bytes_to_read | |
| return 0 |
floki/session.mojo
Outdated
| # Set PUT with zero-length body | ||
| result = easy.post_fields(List[Byte]()) | ||
| if result != Result.OK: | ||
| raise Error("_handle_put: Failed to set POST fields: ", easy.describe_error(result)) |
There was a problem hiding this comment.
The error message refers to "POST fields" in a PUT request handler, which is misleading. While libcurl uses post_fields internally, the error message should be clearer, such as "Failed to set request body" or "Failed to set PUT request fields".
| raise Error("_handle_put: Failed to set POST fields: ", easy.describe_error(result)) | |
| raise Error("_handle_put: Failed to set PUT request body: ", easy.describe_error(result)) |
| # self.body = List[Byte](emberjson.to_string(emberjson.Object(json^)).as_bytes()) | ||
|
|
||
| fn __init__(out self, var data: emberjson.Object): | ||
| """Initializes the body from a dictionary, converting it to a form-encoded string.""" |
There was a problem hiding this comment.
The docstring is misleading - it says "converting it to a form-encoded string" but the implementation actually converts the data to JSON format. Update to: "Initializes the body from a JSON object."
| """Initializes the body from a dictionary, converting it to a form-encoded string.""" | |
| """Initializes the body from a JSON object.""" |
|
|
||
| # Copy the data into the buffer | ||
| try: | ||
| var fd = FileDescriptor(file[]._get_raw_fd()) |
There was a problem hiding this comment.
Creating a FileDescriptor from file[]._get_raw_fd() may cause ownership/lifetime issues. The FileDescriptor constructor takes ownership of the file descriptor, but the original FileHandle still owns it. This could lead to double-close or use-after-close bugs. Consider using FileDescriptor.steal(file[]._get_raw_fd()) if available, or document this potential issue.
| var fd = FileDescriptor(file[]._get_raw_fd()) | |
| var fd = FileDescriptor.steal(file[]._get_raw_fd()) |
add free functions start laying the groundwork for read callback usage add pathway to sending files cleanup enable free functions enable free functions
e4974e9 to
43fc1f9
Compare
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
No description provided.