Update get responses and aadd tests
This commit is contained in:
parent
f66970b747
commit
dac5504754
3 changed files with 166 additions and 76 deletions
11
docker-compose.yml
Normal file
11
docker-compose.yml
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
services:
|
||||||
|
test-server:
|
||||||
|
image: python:3.15-rc-slim
|
||||||
|
command: >
|
||||||
|
sh -c "mkdir mock_api &&
|
||||||
|
echo '{\"message\":\"hello\"}' > mock_api/hello &&
|
||||||
|
echo 'hello' > mock_api/hello-raw &&
|
||||||
|
python -m http.server 8080 -d mock_api
|
||||||
|
"
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const HttpError = error{UnexpectedStatus};
|
|
||||||
|
|
||||||
pub fn getResponse(comptime T: type, allocator: std.mem.Allocator, url: []const u8) !std.json.Parsed(T) {
|
|
||||||
// Set up single threaded context
|
|
||||||
var threaded: std.Io.Threaded = .init_single_threaded;
|
|
||||||
const io: std.Io = threaded.io();
|
|
||||||
|
|
||||||
// Create a client
|
|
||||||
var client: std.http.Client = .{ .allocator = allocator, .io = io };
|
|
||||||
defer client.deinit();
|
|
||||||
|
|
||||||
//Get the endpoint
|
|
||||||
const endpoint = try std.Uri.parse(url);
|
|
||||||
var request = try client.request(.GET, endpoint, .{});
|
|
||||||
defer request.deinit();
|
|
||||||
|
|
||||||
try request.sendBodiless();
|
|
||||||
var response = try request.receiveHead(&.{});
|
|
||||||
|
|
||||||
var transfer_buffer: [64]u8 = undefined;
|
|
||||||
var decompress: std.http.Decompress = undefined;
|
|
||||||
var decompress_buffer: [std.compress.flate.max_window_len]u8 = undefined;
|
|
||||||
const response_reader = response.readerDecompressing(
|
|
||||||
transfer_buffer[0..],
|
|
||||||
&decompress,
|
|
||||||
decompress_buffer[0..],
|
|
||||||
);
|
|
||||||
|
|
||||||
var json_reader: std.json.Reader = .init(allocator, response_reader);
|
|
||||||
defer json_reader.deinit();
|
|
||||||
|
|
||||||
const parsed: std.json.Parsed(T) = try std.json.parseFromTokenSource(
|
|
||||||
T,
|
|
||||||
allocator,
|
|
||||||
&json_reader,
|
|
||||||
.{
|
|
||||||
.ignore_unknown_fields = true,
|
|
||||||
.allocate = .alloc_always,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.head.status.class() != .success) {
|
|
||||||
return HttpError.UnexpectedStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
test "Test getQuoteResponse" {
|
|
||||||
const QuoteResponse = struct {
|
|
||||||
quote: struct {
|
|
||||||
author: struct {
|
|
||||||
bio: []u8,
|
|
||||||
description: []u8,
|
|
||||||
id: []u8,
|
|
||||||
link: []u8,
|
|
||||||
name: []u8,
|
|
||||||
slug: []u8,
|
|
||||||
},
|
|
||||||
content: []u8,
|
|
||||||
id: []u8,
|
|
||||||
tags: []struct {
|
|
||||||
id: []u8,
|
|
||||||
name: []u8,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const allocator = std.testing.allocator;
|
|
||||||
const url: []const u8 = "https://api.quotable.kurokeita.dev/api/quotes/random";
|
|
||||||
const parsed_quote = try getResponse(QuoteResponse, allocator, url[0..]);
|
|
||||||
defer parsed_quote.deinit();
|
|
||||||
|
|
||||||
std.debug.print("{s}", .{parsed_quote.value.quote.id});
|
|
||||||
}
|
|
||||||
155
src/http_utils/http_client_wrapper.zig
Normal file
155
src/http_utils/http_client_wrapper.zig
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const HttpError = error{UnexpectedStatus};
|
||||||
|
const HttpClientWrapper = @This();
|
||||||
|
|
||||||
|
client: std.http.Client,
|
||||||
|
|
||||||
|
const ResponseOptions = struct {
|
||||||
|
token: ?[]u8 = null,
|
||||||
|
extra_headers: []const std.http.Header = &.{},
|
||||||
|
};
|
||||||
|
|
||||||
|
// The caller is in charge of freeing the memory
|
||||||
|
pub fn makeAuthorizationHeaderFromToken(allocator: std.mem.Allocator, token: []const u8) ![]u8 {
|
||||||
|
return try std.fmt.allocPrint(allocator, "Authorization: Bearer {s}", .{token});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get raw response
|
||||||
|
pub fn getRawResponse(
|
||||||
|
client_wrapper: *HttpClientWrapper,
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
url: []const u8,
|
||||||
|
response_options: ResponseOptions,
|
||||||
|
) ![]u8 {
|
||||||
|
var auth_header: []u8 = undefined;
|
||||||
|
|
||||||
|
defer allocator.free(auth_header);
|
||||||
|
if (response_options.token) |token| {
|
||||||
|
auth_header = try makeAuthorizationHeaderFromToken(allocator, token);
|
||||||
|
} else {
|
||||||
|
auth_header = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get the endpoint
|
||||||
|
const endpoint = try std.Uri.parse(url);
|
||||||
|
var request = try client_wrapper.client.request(.GET, endpoint, .{
|
||||||
|
.headers = .{ .authorization = .{ .override = auth_header } },
|
||||||
|
.extra_headers = response_options.extra_headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
defer request.deinit();
|
||||||
|
|
||||||
|
try request.sendBodiless();
|
||||||
|
var response = try request.receiveHead(&.{});
|
||||||
|
|
||||||
|
var transfer_buffer: [64]u8 = undefined;
|
||||||
|
var decompress: std.http.Decompress = undefined;
|
||||||
|
var decompress_buffer: [std.compress.flate.max_window_len]u8 = undefined;
|
||||||
|
const response_reader = response.readerDecompressing(
|
||||||
|
transfer_buffer[0..],
|
||||||
|
&decompress,
|
||||||
|
decompress_buffer[0..],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.head.status.class() != .success) {
|
||||||
|
return HttpError.UnexpectedStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response_reader.allocRemaining(allocator, .unlimited);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getResponse(
|
||||||
|
client_wrapper: *HttpClientWrapper,
|
||||||
|
comptime T: type,
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
url: []const u8,
|
||||||
|
) !std.json.Parsed(T) {
|
||||||
|
//Get the endpoint
|
||||||
|
const endpoint = try std.Uri.parse(url);
|
||||||
|
var request = try client_wrapper.client.request(.GET, endpoint, .{});
|
||||||
|
defer request.deinit();
|
||||||
|
|
||||||
|
try request.sendBodiless();
|
||||||
|
var response = try request.receiveHead(&.{});
|
||||||
|
|
||||||
|
var transfer_buffer: [64]u8 = undefined;
|
||||||
|
var decompress: std.http.Decompress = undefined;
|
||||||
|
var decompress_buffer: [std.compress.flate.max_window_len]u8 = undefined;
|
||||||
|
const response_reader = response.readerDecompressing(
|
||||||
|
transfer_buffer[0..],
|
||||||
|
&decompress,
|
||||||
|
decompress_buffer[0..],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.head.status.class() != .success) {
|
||||||
|
return HttpError.UnexpectedStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
var json_reader: std.json.Reader = .init(allocator, response_reader);
|
||||||
|
defer json_reader.deinit();
|
||||||
|
|
||||||
|
const parsed: std.json.Parsed(T) = try std.json.parseFromTokenSource(
|
||||||
|
T,
|
||||||
|
allocator,
|
||||||
|
&json_reader,
|
||||||
|
.{
|
||||||
|
.ignore_unknown_fields = true,
|
||||||
|
.allocate = .alloc_always,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(client_wrapper: *HttpClientWrapper) void {
|
||||||
|
client_wrapper.client.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests
|
||||||
|
|
||||||
|
test "makeAuthorizationHeader makes the right header" {
|
||||||
|
const token = "Some Bearer Token";
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
|
||||||
|
const header = try makeAuthorizationHeaderFromToken(allocator, token);
|
||||||
|
defer allocator.free(header);
|
||||||
|
|
||||||
|
try std.testing.expect(std.mem.eql(u8, header, "Authorization: Bearer Some Bearer Token"));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Test getResponse" {
|
||||||
|
const io = std.Io.Threaded.global_single_threaded.io();
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
|
||||||
|
const HelloMessage = struct { message: []u8 };
|
||||||
|
|
||||||
|
const http_client: std.http.Client = .{ .allocator = allocator, .io = io };
|
||||||
|
var test_client: HttpClientWrapper = .{ .client = http_client };
|
||||||
|
defer test_client.deinit();
|
||||||
|
|
||||||
|
const parsed = try test_client.getResponse(
|
||||||
|
HelloMessage,
|
||||||
|
allocator,
|
||||||
|
"http://localhost:8080/hello",
|
||||||
|
);
|
||||||
|
defer parsed.deinit();
|
||||||
|
try std.testing.expect(std.mem.eql(u8, parsed.value.message, "hello"));
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Test getRawResponse" {
|
||||||
|
const io = std.Io.Threaded.global_single_threaded.io();
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
|
||||||
|
const http_client: std.http.Client = .{ .allocator = allocator, .io = io };
|
||||||
|
var test_client: HttpClientWrapper = .{ .client = http_client };
|
||||||
|
defer test_client.deinit();
|
||||||
|
|
||||||
|
const response = try test_client.getRawResponse(
|
||||||
|
allocator,
|
||||||
|
"http://localhost:8080/hello-raw",
|
||||||
|
.{},
|
||||||
|
);
|
||||||
|
defer allocator.free(response);
|
||||||
|
|
||||||
|
try std.testing.expect(std.mem.eql(u8, response, "hello\n"));
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue