Skip to content

feat: add write context to set writefunction - #32

Merged
jiacai2050 merged 3 commits into
mainfrom
feat-write-context
Jul 27, 2025
Merged

feat: add write context to set writefunction#32
jiacai2050 merged 3 commits into
mainfrom
feat-write-context

Conversation

@jiacai2050

Copy link
Copy Markdown
Owner

No description provided.

@jiacai2050
jiacai2050 requested a review from Copilot July 27, 2025 10:02

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Summary of Changes

Hello @jiacai2050, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request refactors the handling of response bodies within the curl Zig library. The primary goal is to provide a more flexible and idiomatic API for capturing HTTP response data by introducing a 'write context' concept. This change simplifies the core fetch and upload functions, making them more consistent and easier to use, while also updating the underlying buffer management to align with this new approach.

Highlights

  • Unified Write Context API: Introduced a new setWriteContext function in Easy.zig that simplifies how response bodies are handled. This function now takes a generic context object (e.g., DynamicContext or StaticContext) and its associated write method, abstracting away the direct management of CURLOPT_WRITEDATA and CURLOPT_WRITEFUNCTION.
  • New Context Types for Response Handling: Added DynamicContext and StaticContext structs in src/util.zig. These new types encapsulate the logic for dynamically growing buffers and fixed-size buffers, respectively, and provide a standardized write method for use with the new setWriteContext API.
  • Simplified Fetch and Upload Functions: The fetch and upload methods in Easy.zig have been refactored to accept the new writeContext parameter, removing the need for separate fetchAlloc and uploadAlloc functions. This streamlines the API for retrieving response bodies.
  • Removed Direct Body Management from Response: The Body union and its deinit method have been removed from the Response struct in Easy.zig, as the responsibility for managing the response body data is now delegated entirely to the provided writeContext.
  • Updated Examples: All example files (advanced.zig, basic.zig, header.zig, multi.zig) have been updated to reflect the new setWriteContext API and the use of DynamicContext and StaticContext for handling response data.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments or fill out our survey to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR refactors the write callback system to use context-based write functions, removing the Body union and simplifying the API design. The change introduces StaticContext and DynamicContext wrapper types that provide a unified interface for handling response data.

  • Replaces StaticBuffer and DynamicBuffer with StaticContext and DynamicContext wrapper types
  • Introduces setWriteContext method that automatically sets both WRITEDATA and WRITEFUNCTION
  • Simplifies fetch methods by removing separate fetchAlloc and buffer parameter variants

Reviewed Changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/util.zig Adds new StaticContext and DynamicContext wrapper types with write methods
src/root.zig Updates public API exports to use new context types
src/Easy.zig Refactors write callback system and simplifies fetch/upload methods
examples/multi.zig Updates example to use new DynamicContext API
examples/header.zig Simplifies example by removing buffer management
examples/basic.zig Updates examples to demonstrate new context-based API
examples/advanced.zig Converts to use new write context system
.tool-versions Updates Zig version from 0.14.0 to 0.14.1
Comments suppressed due to low confidence (1)

.tool-versions:1

  • Zig version 0.14.1 does not exist. The latest stable version as of my knowledge cutoff is 0.13.0, and 0.14.0 would be a development version. Please verify this version exists before using it.
zig 0.14.1

Comment thread src/Easy.zig Outdated
Comment thread src/Easy.zig
if (comptime !@hasDecl(ptr.child, "write")) {
@compileError("writeContext must have a `write` function");
}
try self.setWriteContext(context, @field(ptr.child, "write"));

Copilot AI Jul 27, 2025

Copy link

Choose a reason for hiding this comment

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

Using @field with a string literal to access the write method is fragile and bypasses compile-time type checking. If the write field doesn't exist or has the wrong signature, this will cause a runtime error. Consider using @hasDecl check combined with direct method access or a more type-safe approach.

Suggested change
try self.setWriteContext(context, @field(ptr.child, "write"));
const writeFn = ptr.child.write;
try self.setWriteContext(context, writeFn);

Copilot uses AI. Check for mistakes.
Comment thread src/Easy.zig

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a writeContext concept to abstract away response body handling, which greatly simplifies the API and improves its usability. The implementation is well-done, leveraging Zig's compile-time features for type safety. I've found a few issues: a critical portability bug in a callback function, a memory leak in one of the examples due to incorrect resource management, and a minor misleading comment. Once these are addressed, this will be an excellent improvement.

Comment thread src/Easy.zig Outdated
Comment on lines +516 to +517
const data = ptr[0..real_size];
stdout.writeAll(data) catch {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

This implementation has a portability issue that will cause a compilation error on platforms where c_char is a signed type (e.g., i8 on Linux).

The stdout.writeAll function expects a []const u8, but ptr[0..real_size] produces a []c_char. When c_char is i8, this results in a type mismatch.

To fix this, you should cast the ptr to a [*]const u8 before slicing it. This ensures the data is treated as a sequence of bytes, which is correct for any platform.

    const data = (@as([*]const u8, @ptrCast(ptr)))[0..real_size];
    stdout.writeAll(data) catch {

Comment thread examples/multi.zig
Comment on lines +30 to +31
var ctx1 = curl.DynamicContext.init(allocator);
var ctx2 = curl.DynamicContext.init(allocator);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

There's a potential memory leak here. ctx1 and ctx2 are initialized but are not guaranteed to be deinitialized if an error occurs before the while loop completes (e.g., in multi.addHandle or multi.perform).

Using defer immediately after initialization is the idiomatic way in Zig to ensure resources are cleaned up correctly, even in the presence of errors. I recommend restoring this pattern, which was present in the previous version of the code.

    var ctx1 = curl.DynamicContext.init(allocator);
    defer ctx1.deinit();
    var ctx2 = curl.DynamicContext.init(allocator);
    defer ctx2.deinit();

Comment thread examples/multi.zig Outdated
Comment on lines +70 to +71
const ctx: *curl.DynamicContext = @ptrCast(@alignCast(private_data.?));
defer ctx.deinit();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Deinitializing the context here introduces a couple of issues:

  1. Ownership Violation: The main function owns ctx1 and ctx2. This inner scope should not be responsible for deinitializing them. The owner should handle the cleanup.
  2. Double Free: If you add the defer statements in main as I suggested in my other comment (to prevent leaks), this line will cause a double-free when the defer in main executes upon returning.

Please remove this line. The defer statements in main will handle the cleanup correctly at the end of the function's scope.

Comment thread src/util.zig Outdated
Comment on lines +62 to +63
ctx.buffer.appendSlice(data) catch {
// Not enough space in the buffer

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

This comment is slightly misleading. A DynamicBuffer (which is a std.ArrayList) is designed to grow, so the error isn't about not having enough space in a pre-existing buffer. The catch is for an allocation failure, which typically means the system is out of memory.

A more accurate comment would be // Out of memory.

            // Out of memory

@jiacai2050
jiacai2050 force-pushed the feat-write-context branch from cde32c7 to 25e3fcf Compare July 27, 2025 10:55
@jiacai2050

Copy link
Copy Markdown
Owner Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

The code changes introduce the writeContext to set writefunction, which is a solid architectural change that enhances type safety, simplifies the public API, and improves extensibility. However, the implementation of setWriteContextInner does not handle empty struct literals, which prevents a key use case from compiling. The review includes a detailed explanation and a suggested fix for this.

Comment thread src/Easy.zig
Comment on lines +430 to +447
fn setWriteContextInner(self: Self, context: anytype) !void {
const ctxType = @typeInfo(@TypeOf(context));
switch (ctxType) {
.void => {
// No write context, do nothing.
return;
},
.pointer => |ptr| {
if (comptime !@hasDecl(ptr.child, "write")) {
@compileError("writeContext must have a `write` function");
}
try self.setWriteContext(context, @field(ptr.child, "write"));
},
else => {
@compileError("writeContext must be a pointer or void type, current: " ++ @typeName(@TypeOf(context)));
},
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The current implementation of setWriteContextInner doesn't correctly handle passing an empty struct literal {} for the writeContext parameter. The examples in examples/basic.zig and examples/header.zig use {} to indicate that the response body should be discarded, but this will cause a compile error because the type of {} is an anonymous struct struct {}, which will fall into the else branch of your switch statement, triggering the @compileError.

To fix this, you should add a case for .Struct to handle empty structs explicitly. This will make the function behave as intended by the examples and also provide more specific compile-time error messages for other invalid types.

fn setWriteContextInner(self: Self, context: anytype) !void {
    const T = @TypeOf(context);
    const ctxType = @typeInfo(T);
    switch (ctxType) {
        .void => {
            // No write context, do nothing.
            return;
        },
        .pointer => |ptr| {
            if (comptime !@hasDecl(ptr.child, "write")) {
                @compileError("writeContext must have a `write` function");
            }
            try self.setWriteContext(context, @field(ptr.child, "write"));
        },
        .Struct => |s| {
            if (s.fields.len == 0) {
                // empty struct, treat as void
                return;
            }
            @compileError("writeContext must be a pointer, void, or empty struct, but got a non-empty struct: " ++ @typeName(T));
        },
        else => {
            @compileError("writeContext must be a pointer, void, or empty struct, but got: " ++ @typeName(T));
        },
    }
}

@jiacai2050
jiacai2050 merged commit 21878cd into main Jul 27, 2025
3 checks passed
@jiacai2050
jiacai2050 deleted the feat-write-context branch July 27, 2025 11:43
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