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

vendor/curl: Add comprehensive (not-so-minimal) libcurl bindings #4679

Closed
wants to merge 4 commits into from

Conversation

zolk3ri
Copy link
Contributor

@zolk3ri zolk3ri commented Jan 11, 2025

Add minimal comprehensive libcurl bindings providing a simple, memory-safe high-level API to interact with libcurl in Odin.

Key features:

  • Simple high-level API for HTTP(S) requests
  • Memory management via Odin's allocator system
  • Type-safe response handling with custom parsers
  • Comprehensive SSL/TLS support
  • Flexible configuration options
  • Well-documented API with examples

The implementation is based on curl.h version 8.11.1 and provides both low-level libcurl bindings and a high-level request API that follows Odin idioms.

Usage

Import the library:

import "vendor:curl"

Initialize before use:

if !curl.init() {
    fmt.eprintln("Failed to initialize curl")
    return
}
defer curl.cleanup()

Simple GET request:

config := curl.Request_Config{
    headers = []string{
        "User-Agent: odin-curl/0.1",
    },
}

res := curl.get("https://httpbin.org/get", config)
defer curl.destroy_response(&res)

if res.error != .None {
    fmt.eprintln("Request failed:", curl.error_string(res.error))
    return
}

fmt.println("Status:", res.status_code)
fmt.println("Response:", string(res.body))

Simple POST request:

config := curl.Request_Config{
    headers = []string{
        "User-Agent: odin-curl/0.1",
        "Content-Type: application/json",
    },
}

data := `{"test": "data"}`
res := curl.post("https://httpbin.org/post", data, config)
defer curl.destroy_response(&res)

if res.error != .None {
    fmt.eprintln("Request failed:", curl.error_string(res.error))
    return
}

fmt.println("Status:", res.status_code)
fmt.println("Response:", string(res.body))

GET with JSON parsing:

Post :: struct {
    id: int,
    title: string,
    body: string,
    userId: int,
}

config := curl.Request_Config{
    headers = []string{
        "User-Agent: odin-curl/0.1",
        "Accept: application/json",
    },
}

res := curl.get_json(Post, "https://jsonplaceholder.typicode.com/posts/1", config)
defer curl.destroy_response(&res)

if res.error != .None {
    fmt.eprintln("Request failed:", curl.error_string(res.error))
    return
}

fmt.printf("Post %d: %q\n", res.body.id, res.body.title)
fmt.println("Body:", res.body.body)

POST with JSON:

Post :: struct {
    id: int,
    title: string,
    body: string,
    userId: int,
}

config := curl.Request_Config{
    headers = []string{
        "User-Agent: odin-curl/0.1",
        "Content-Type: application/json",
    },
}

data := `{
    "title": "New Post",
    "body": "Example content",
    "userId": 1
}`
res := curl.post_json(Post, "https://jsonplaceholder.typicode.com/posts", data, config)
defer curl.destroy_response(&res)

if res.error != .None {
    fmt.eprintln("Request failed:", curl.error_string(res.error))
    return
}

fmt.printf("Created Post ID: %d\n", res.body.id)
fmt.println("Title:", res.body.title)
fmt.println("Body:", res.body.body)

Examples

The examples/ directory contains:

  • basic.odin

    • Simple GET and POST requests with custom headers
    • Request timeout handling (success and failure cases)
    • Redirect following with configurable limits
    • Timeout and redirect error handling
  • typed.odin

    • Type-safe HTTP request/response handling with built-in and custom parsers
    • Custom response parsing with proper cleanup
    • Working with structured data via JSON API endpoints
  • ssl.odin

    • HTTPS requests with configurable security levels
    • TLS version selection
    • Certificate verification
    • Error handling for SSL/TLS issues
  • allocator.odin

    • Memory allocation tracking with Tracking_Allocator

All examples run successfully and have been tested with valgrind to confirm no memory leaks.

I hope this library is suitable for inclusion in the vendor collection. If not, maintenance will continue here. All suggestions for improvement are welcome, whether they highlight blockers or provide general recommendations.

Thank you for your consideration! :)

Extract common HTTP method logic into do_method() and
do_method_with() to reduce code duplication in request
handling. This consolidates error handling and memory
management while maintaining the existing public API.
Add post_string() wrapper that completes the set of string
response handlers, providing consistent string handling across
request types.
Simplify "Optional request configuration" documentation to
match format used across other parameter descriptions.
- Ubuntu/Debian: `sudo apt install libcurl4-dev`
- Fedora: `sudo dnf install libcurl-devel`
- macOS: `brew install curl`
- Windows: Download from [curl](https://curl.se/windows/)
Copy link
Member

Choose a reason for hiding this comment

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

For vendor, things should "just work", especially on Windows. So why does Windows not bundle with a .lib already?

CURL :: distinct rawptr

// CURL_GLOBAL_ALL combines all initialization flags.
CURL_GLOBAL_ALL: i64 = 3
Copy link
Member

Choose a reason for hiding this comment

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

Maybe remove the CURL_ prefix?

CURL_GLOBAL_ALL: i64 = 3

// CURLOPT represents options for a transfer.
CURLOPT :: enum i32 {
Copy link
Member

Choose a reason for hiding this comment

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

Maybe remove the CURL prefix?

// Error represents libcurl error codes.
//
// This maps directly to CURLcode error values from curl.h.
Error :: enum {
Copy link
Member

Choose a reason for hiding this comment

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

Is this the correct size? Is this meant to be enum c.int?

}

// CURLINFO represents information retrieval options.
CURLINFO :: enum i32 {
Copy link
Member

Choose a reason for hiding this comment

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

Is this meant to be c.int?

CURL_GLOBAL_ALL: i64 = 3

// CURLOPT represents options for a transfer.
CURLOPT :: enum i32 {
Copy link
Member

Choose a reason for hiding this comment

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

Is this meant to be c.int?


// CURLOPT represents options for a transfer.
CURLOPT :: enum i32 {
// Basic options
Copy link
Member

Choose a reason for hiding this comment

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

Please adhere to the Odin conventions of using tabs for indentation.

// - userdata: User provided data pointer
//
// Returns: Number of bytes handled.
Write_Callback :: #type proc "c" (data: rawptr, size: c.size_t, nmemb: c.size_t, userdata: rawptr) -> c.size_t
Copy link
Member

@gingerBill gingerBill Jan 13, 2025

Choose a reason for hiding this comment

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

Please try to keep the original naming conventions and DO NOT change it to Odin's core convention.

// Error represents libcurl error codes.
//
// This maps directly to CURLcode error values from curl.h.
Error :: enum {
Copy link
Member

Choose a reason for hiding this comment

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

I know this wraps the other calls, but why not keep it the same? e.g. enum c.int?

if ctx == nil do return false

result: i32
when T == string {
Copy link
Member

Choose a reason for hiding this comment

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

This will fail with distinct types.

@gingerBill
Copy link
Member

Honestly, the PR has loads of styling issues and is not "minimal" in any sense. The general wrappers are not that great because they rely too much on parapoly and get numerous things wrong in the process too (the first thing being not understanding distinct is a thing).

@gingerBill
Copy link
Member

I am closing this PR because it would be quicker for me to write my own bindings than explain everything wrong with it and then hope they get fixed correctly.

@gingerBill gingerBill closed this Jan 13, 2025
@zolk3ri
Copy link
Contributor Author

zolk3ri commented Jan 13, 2025

Thank you for the feedback. You're right - while the binding started minimal, it grew more complex
with features like parametric polymorphism that I thought might be helpful for others.

As I need these bindings for my active projects, I'll continue maintenance here where I'll work on addressing these points.

@zolk3ri zolk3ri deleted the vendor/curl branch January 13, 2025 12:44
@zolk3ri zolk3ri changed the title vendor/curl: Add minimal libcurl bindings vendor/curl: Add comprehensive (not-so-minimal) libcurl bindings Jan 13, 2025
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