Recursion Problem Runtime Error

I am having trouble with my recursive function. Could someone help me out with this… thanks.

const std = @import("std");

fn get(a: []const u8, b: std.mem.Allocator, c: *std.ArrayList([]const u8)) !void {
    const uri = try std.Uri.parse(a);
    var client = std.http.Client{ .allocator = b };
    defer client.deinit();
    const server_header_buffer: []u8 = try b.alloc(u8, 1024 * 8);
    defer b.free(server_header_buffer);
    var req = try client.open(.GET, uri, .{
        .server_header_buffer = server_header_buffer,
    });
    defer req.deinit();
    try req.send();
    try req.finish();
    try req.wait();
    // 1024 * 8 of memory.
    const body = try req.reader().readAllAlloc(b, 229000 * 8);
    defer b.free(body);
    var x = std.mem.splitSequence(u8, body, "er\":\"");
    while (x.next()) |i| {
        var h = std.mem.splitSequence(u8, i, "\"");
        try c.append(h.next().?);
    }
    var d = std.mem.splitSequence(u8, body, "next_url\":\"");

    var d_split: []const u8 = "";
    var counter: u8 = 1;
    while (d.next()) |f| {
        if (counter == 2) {
            d_split = f;
        }
        counter += 1;
    }
    if (counter > 0) {
        var w = std.mem.splitSequence(u8, d_split, "\"");
        try get(w.next().?, b, c);
    }
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    var tickers = std.ArrayList([]const u8).init(allocator);
    defer tickers.deinit();
    try get("https://api.polygon.io/v3/reference/tickers?market=stocks&active=true&order=asc&limit=1000&sort=ticker&apiKey=MY_API_KEY", allocator, &tickers);
}

Compiler Error

error: InvalidFormat
C:\Users\first cash\zig-windows-x86_64-0.15.0-dev.369+1a2ceb36c\lib\std\Uri.zig:326:9: 0x7ff6b6d606db in parse (main.exe.obj)
        return error.InvalidFormat;
        ^
C:\Users\first cash\Projects\rh\main.zig:4:17: 0x7ff6b6d5fb5e in get (main.exe.obj)
    const uri = try std.Uri.parse(a);
                ^
C:\Users\first cash\Projects\rh\main.zig:36:9: 0x7ff6b6d605f9 in get (main.exe.obj)
        try get(w.next().?, b, c);
        ^
C:\Users\first cash\Projects\rh\main.zig:36:9: 0x7ff6b6d605f9 in get (main.exe.obj)
        try get(w.next().?, b, c);
        ^
C:\Users\first cash\Projects\rh\main.zig:46:5: 0x7ff6b6d654d3 in main (main.exe.obj)
    try get("https://api.polygon.io/v3/reference/tickers?market=stocks&active=true&order=asc&limit=1000&sort=ticker&apiKey=6bhPQHYax6qkEBNwxj0rnmba3X6zi_C_", allocator, &tickers);
    ^

I’m not sure what more we can add then what the compiler is already telling you.
It looks like the Uri is invalid. You might try printing out the uri so you can see what it is when you get that error.

std.debug.print("Uri? {s}\n", .{a});
const uri = try std.Uri.parse(a);

This is a runtime error, so perhaps the confusion is because you thought it was a compiler error?

Can you look at this again because I cannot figure it out. I guess the try std.uri.parse is failing. It runs correctly the first time but during the recursive call it does not work. I by the recursive function a string, convert it to uri, configure the client, parse another url from the response and use that url in the same function utilizing recursion. I have no idea why the compiler thinks that I did it wrong.

When you call it recursively here:

Are you sure that the value you pass to get above is a valid URI?

Printing out the URI value just before you parse it may help in debugging.

2 Likes

Stop with the manual string splitting and actually parse the JSON you are receiving like @Southporter has already shown you in this topic:

2 Likes

This code did not make sense to me could you post the complete source code please.

I found out the problem. The program during runtime is look at the const u8 as unsigned int values and not a string. How do I write code to convert the u8 array to ascii format?

I thought I gave you enough to be able to piece it together, but apparently not. In any case, here you go. And I threw in env vars as a bonus to hopefully prevent API key leaking.

const std = @import("std");

const log = std.log.scoped(.main);

fn get(url: []const u8, allocator: std.mem.Allocator, tickers: *std.ArrayList(TickerResponse.Ticker)) !void {
    const uri = try std.Uri.parse(url);
    var client = std.http.Client{ .allocator = allocator };
    defer client.deinit();
    const server_header_buffer: []u8 = try allocator.alloc(u8, 1024 * 8);
    defer allocator.free(server_header_buffer);
    var req = try client.open(.GET, uri, .{
        .server_header_buffer = server_header_buffer,
    });
    defer req.deinit();
    try req.send();
    try req.finish();
    try req.wait();

    const reader = req.reader();
    const response_body = try reader.readAllAlloc(allocator, 229000 * 8);
    defer allocator.free(response_body);

    const body = try std.json.parseFromSlice(TickerResponse, allocator, response_body, .{
        .ignore_unknown_fields = true,
        .parse_numbers = true,
    });
    defer body.deinit();

    log.info("Fetched {d} tickers", .{body.value.count});

    try tickers.ensureUnusedCapacity(body.value.results.len);
    for (body.value.results) |r| {
        const dupe = try r.dupe(allocator);
        tickers.appendAssumeCapacity(dupe);
    }

    if (body.value.next_url) |next_url| {
        log.info("Fetching next page: {s}", .{next_url});
        try get(next_url, allocator, tickers);
    }
}

const TickerResponse = struct {
    const Ticker = struct {
        ticker: []const u8,
        name: []const u8,
        // Other fields go here
        pub fn deinit(self: Ticker, allocator: std.mem.Allocator) void {
            allocator.free(self.ticker);
            allocator.free(self.name);
        }
        pub fn dupe(self: Ticker, allocator: std.mem.Allocator) !Ticker {
            return Ticker{
                .ticker = try allocator.dupe(u8, self.ticker),
                .name = try allocator.dupe(u8, self.name),
            };
        }
    };
    count: usize = 0,
    next_url: ?[]const u8 = null,
    results: []Ticker = &[_]Ticker{},
    // other fields go here
};

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    const api_key = std.process.getEnvVarOwned(allocator, "POLYGON_API_KEY") catch {
        log.err("POLYGON_API_KEY environment variable not set", .{});
        return;
    };
    defer allocator.free(api_key);
    var tickers = std.ArrayList(TickerResponse.Ticker).init(allocator);
    defer {
        for (tickers.items) |t| {
            t.deinit(allocator);
        }
        tickers.deinit();
    }
    const url = std.fmt.allocPrint(allocator, "https://api.polygon.io/v3/reference/tickers?market=stocks&active=true&order=asc&limit=1000&sort=ticker&apiKey={s}", .{api_key}) catch {
        log.err("Failed to construct URL", .{});
        return;
    };
    defer allocator.free(url);
    try get(url, allocator, &tickers);
}

Please note the lifetime work to preserve the name and ticker outside of the get call.
Tested it with 0.14.1 which is what it seems like you are using.

2 Likes