Compare commits
No commits in common. "b0cfb5a1c0aa3149c7903a6d0de1f4b96ff4da25" and "6df02b10748b82f7a7300e399e48dc6f98647cf7" have entirely different histories.
b0cfb5a1c0
...
6df02b1074
|
@ -1,83 +0,0 @@
|
|||
name: aws-zig mach nominated build
|
||||
run-name: ${{ github.actor }} building AWS Zig SDK
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 12 * * *' # noon UTC, 4AM Pacific
|
||||
push:
|
||||
branches:
|
||||
- 'zig-develop*'
|
||||
env:
|
||||
ACTIONS_RUNTIME_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
ACTIONS_RUNTIME_URL: ${{ env.GITHUB_SERVER_URL }}/api/actions_pipeline/
|
||||
jobs:
|
||||
build-zig-nominated-mach-latest:
|
||||
runs-on: ubuntu-latest
|
||||
# Need to use the default container with node and all that, so we can
|
||||
# use JS-based actions like actions/checkout@v3...
|
||||
# container:
|
||||
# image: alpine:3.15.0
|
||||
env:
|
||||
ZIG_VERSION: mach-latest
|
||||
ARCH: x86_64
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v3
|
||||
# ARCH is fine, but we can't substitute directly because zig
|
||||
# uses x86_64 instead of amd64. They also use aarch64 instead of arm64.
|
||||
#
|
||||
# However, arm64/linux isn't quite fully tier 1 yet, so this is more of a
|
||||
# TODO: https://github.com/ziglang/zig/issues/2443
|
||||
- name: Install zig
|
||||
run: |
|
||||
apt-get update && apt-get install -y jq
|
||||
file="$(curl -Osw '%{filename_effective}' "$(curl -s https://machengine.org/zig/index.json |jq -r '."'${ZIG_VERSION}'"."x86_64-linux".tarball')")"
|
||||
tar x -C /usr/local -f "${file}"
|
||||
ln -s /usr/local/"${file%%.tar.xz}"/zig /usr/local/bin/zig
|
||||
zig version
|
||||
- name: Run tests
|
||||
run: zig build test --verbose
|
||||
# Zig package manager expects everything to be inside a directory in the archive,
|
||||
# which it then strips out on download. So we need to shove everything inside a directory
|
||||
# the way GitHub/Gitea does for repo archives
|
||||
#
|
||||
# Also, zig tar process doesn't handle gnu format for long names, nor does it seam to
|
||||
# handle posix long name semantics cleanly either. ustar works. This
|
||||
# should be using git archive, but we need our generated code to be part of it
|
||||
- name: Package source code with generated models
|
||||
run: |
|
||||
tar -czf ${{ runner.temp }}/${{ github.sha }}-with-models.tar.gz \
|
||||
--format ustar \
|
||||
--exclude 'zig-*' \
|
||||
--transform 's,^,${{ github.sha }}/,' *
|
||||
# - name: Sign
|
||||
# id: sign
|
||||
# uses: https://git.lerch.org/lobo/action-hsm-sign@v1
|
||||
# with:
|
||||
# pin: ${{ secrets.HSM_USER_PIN }}
|
||||
# files: ???
|
||||
# public_key: 'https://emil.lerch.org/serverpublic.pem'
|
||||
# - run: |
|
||||
# echo "Source 0 should be ./bar: ${{ steps.sign.outputs.SOURCE_0 }}"
|
||||
# - run: |
|
||||
# echo "Signature 0 should be ./bar.sig: ${{ steps.sign.outputs.SIG_0 }}"
|
||||
# - run: echo "URL of bar (0) is ${{ steps.sign.outputs.URL_0 }}"
|
||||
# - run: |
|
||||
# echo "Source 1 should be ./foo: ${{ steps.sign.outputs.SOURCE_1 }}"
|
||||
# - run: |
|
||||
# echo "Signature 1 should be ./foo.sig: ${{ steps.sign.outputs.SIG_1 }}"
|
||||
# - run: echo "URL of foo (1) is ${{ steps.sign.outputs.URL_1 }}"
|
||||
- name: Publish source code with generated models
|
||||
run: |
|
||||
curl --user ${{ github.actor }}:${{ secrets.PACKAGE_PUSH }} \
|
||||
--upload-file ${{ runner.temp }}/${{ github.sha }}-with-models.tar.gz \
|
||||
https://git.lerch.org/api/packages/lobo/generic/aws-sdk-with-models/${{ github.sha }}/${{ github.sha }}-with-models.tar.gz
|
||||
- name: Build example
|
||||
run: ( cd example && zig build ) # Make sure example builds
|
||||
- name: Notify
|
||||
uses: https://git.lerch.org/lobo/action-notify-ntfy@v2
|
||||
if: always()
|
||||
with:
|
||||
host: ${{ secrets.NTFY_HOST }}
|
||||
topic: ${{ secrets.NTFY_TOPIC }}
|
||||
user: ${{ secrets.NTFY_USER }}
|
||||
password: ${{ secrets.NTFY_PASSWORD }}
|
|
@ -1,81 +0,0 @@
|
|||
name: aws-zig nightly build
|
||||
run-name: ${{ github.actor }} building AWS Zig SDK
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'zig-develop*'
|
||||
env:
|
||||
ACTIONS_RUNTIME_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
ACTIONS_RUNTIME_URL: ${{ env.GITHUB_SERVER_URL }}/api/actions_pipeline/
|
||||
jobs:
|
||||
build-zig-nightly:
|
||||
runs-on: ubuntu-latest
|
||||
# Need to use the default container with node and all that, so we can
|
||||
# use JS-based actions like actions/checkout@v3...
|
||||
# container:
|
||||
# image: alpine:3.15.0
|
||||
env:
|
||||
ZIG_VERSION: master
|
||||
ARCH: x86_64
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v3
|
||||
# ARCH is fine, but we can't substitute directly because zig
|
||||
# uses x86_64 instead of amd64. They also use aarch64 instead of arm64.
|
||||
#
|
||||
# However, arm64/linux isn't quite fully tier 1 yet, so this is more of a
|
||||
# TODO: https://github.com/ziglang/zig/issues/2443
|
||||
- name: Install zig
|
||||
run: |
|
||||
apt-get update && apt-get install -y jq
|
||||
file="$(curl -Osw '%{filename_effective}' "$(curl -s https://ziglang.org/download/index.json |jq -r '."'${ZIG_VERSION}'"."x86_64-linux".tarball')")"
|
||||
tar x -C /usr/local -f "${file}"
|
||||
ln -s /usr/local/"${file%%.tar.xz}"/zig /usr/local/bin/zig
|
||||
zig version
|
||||
- name: Run tests
|
||||
run: zig build test --verbose
|
||||
# Zig package manager expects everything to be inside a directory in the archive,
|
||||
# which it then strips out on download. So we need to shove everything inside a directory
|
||||
# the way GitHub/Gitea does for repo archives
|
||||
#
|
||||
# Also, zig tar process doesn't handle gnu format for long names, nor does it seam to
|
||||
# handle posix long name semantics cleanly either. ustar works. This
|
||||
# should be using git archive, but we need our generated code to be part of it
|
||||
- name: Package source code with generated models
|
||||
run: |
|
||||
tar -czf ${{ runner.temp }}/${{ github.sha }}-with-models.tar.gz \
|
||||
--format ustar \
|
||||
--exclude 'zig-*' \
|
||||
--transform 's,^,${{ github.sha }}/,' *
|
||||
# - name: Sign
|
||||
# id: sign
|
||||
# uses: https://git.lerch.org/lobo/action-hsm-sign@v1
|
||||
# with:
|
||||
# pin: ${{ secrets.HSM_USER_PIN }}
|
||||
# files: ???
|
||||
# public_key: 'https://emil.lerch.org/serverpublic.pem'
|
||||
# - run: |
|
||||
# echo "Source 0 should be ./bar: ${{ steps.sign.outputs.SOURCE_0 }}"
|
||||
# - run: |
|
||||
# echo "Signature 0 should be ./bar.sig: ${{ steps.sign.outputs.SIG_0 }}"
|
||||
# - run: echo "URL of bar (0) is ${{ steps.sign.outputs.URL_0 }}"
|
||||
# - run: |
|
||||
# echo "Source 1 should be ./foo: ${{ steps.sign.outputs.SOURCE_1 }}"
|
||||
# - run: |
|
||||
# echo "Signature 1 should be ./foo.sig: ${{ steps.sign.outputs.SIG_1 }}"
|
||||
# - run: echo "URL of foo (1) is ${{ steps.sign.outputs.URL_1 }}"
|
||||
- name: Publish source code with generated models
|
||||
run: |
|
||||
curl --user ${{ github.actor }}:${{ secrets.PACKAGE_PUSH }} \
|
||||
--upload-file ${{ runner.temp }}/${{ github.sha }}-with-models.tar.gz \
|
||||
https://git.lerch.org/api/packages/lobo/generic/aws-sdk-with-models/${{ github.sha }}/${{ github.sha }}-with-models.tar.gz
|
||||
- name: Build example
|
||||
run: ( cd example && zig build ) # Make sure example builds
|
||||
- name: Notify
|
||||
uses: https://git.lerch.org/lobo/action-notify-ntfy@v2
|
||||
if: always()
|
||||
with:
|
||||
host: ${{ secrets.NTFY_HOST }}
|
||||
topic: ${{ secrets.NTFY_TOPIC }}
|
||||
user: ${{ secrets.NTFY_USER }}
|
||||
password: ${{ secrets.NTFY_PASSWORD }}
|
31
.github/workflows/build.yaml
vendored
31
.github/workflows/build.yaml
vendored
|
@ -1,31 +0,0 @@
|
|||
name: AWS-Zig Build
|
||||
run-name: ${{ github.actor }} building AWS Zig SDK
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
- '!zig-develop*'
|
||||
jobs:
|
||||
build-zig-0.11.0-amd64-host:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
ZIG_VERSION: 0.11.0
|
||||
ARCH: x86_64
|
||||
if: ${{ github.env.GITEA_ACTIONS != 'true' }}
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v3
|
||||
# ARCH is fine, but we can't substitute directly because zig
|
||||
# uses x86_64 instead of amd64. They also use aarch64 instead of arm64.
|
||||
#
|
||||
# However, arm64/linux isn't quite fully tier 1 yet, so this is more of a
|
||||
# TODO: https://github.com/ziglang/zig/issues/2443
|
||||
- name: Install zig
|
||||
run: |
|
||||
wget -q https://ziglang.org/download/${ZIG_VERSION}/zig-linux-${ARCH}-${ZIG_VERSION}.tar.xz
|
||||
sudo tar x -C /usr/local -f zig-linux-${ARCH}-${ZIG_VERSION}.tar.xz
|
||||
sudo ln -s /usr/local/zig-linux-${ARCH}-${ZIG_VERSION}/zig /usr/local/bin/zig
|
||||
- name: Run tests
|
||||
run: zig build test --verbose
|
||||
- name: Build example
|
||||
run: ( cd example && zig build ) # Make sure example builds
|
38
.github/workflows/zig-mach.yaml
vendored
38
.github/workflows/zig-mach.yaml
vendored
|
@ -1,38 +0,0 @@
|
|||
name: aws-zig mach nominated build
|
||||
run-name: ${{ github.actor }} building AWS Zig SDK
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 12 * * *' # noon UTC, 4AM Pacific
|
||||
push:
|
||||
branches:
|
||||
- 'zig-develop*'
|
||||
jobs:
|
||||
build-zig-nightly:
|
||||
runs-on: ubuntu-latest
|
||||
# Need to use the default container with node and all that, so we can
|
||||
# use JS-based actions like actions/checkout@v3...
|
||||
# container:
|
||||
# image: alpine:3.15.0
|
||||
env:
|
||||
ZIG_VERSION: mach-latest
|
||||
ARCH: x86_64
|
||||
if: ${{ github.env.GITEA_ACTIONS != 'true' }}
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v3
|
||||
# ARCH is fine, but we can't substitute directly because zig
|
||||
# uses x86_64 instead of amd64. They also use aarch64 instead of arm64.
|
||||
#
|
||||
# However, arm64/linux isn't quite fully tier 1 yet, so this is more of a
|
||||
# TODO: https://github.com/ziglang/zig/issues/2443
|
||||
- name: Install zig
|
||||
run: |
|
||||
apt-get update && apt-get install -y jq
|
||||
file="$(curl -Osw '%{filename_effective}' "$(curl -s https://machengine.org/zig/index.json |jq -r '."'${ZIG_VERSION}'"."x86_64-linux".tarball')")"
|
||||
sudo tar x -C /usr/local -f "${file}"
|
||||
sudo ln -s /usr/local/"${file%%.tar.xz}"/zig /usr/local/bin/zig
|
||||
zig version
|
||||
- name: Run tests
|
||||
run: zig build test --verbose
|
||||
- name: Build example
|
||||
run: ( cd example && zig build ) # Make sure example builds
|
36
.github/workflows/zig-nightly.yaml
vendored
36
.github/workflows/zig-nightly.yaml
vendored
|
@ -1,36 +0,0 @@
|
|||
name: aws-zig nightly build
|
||||
run-name: ${{ github.actor }} building AWS Zig SDK
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'zig-develop*'
|
||||
jobs:
|
||||
build-zig-nightly:
|
||||
runs-on: ubuntu-latest
|
||||
# Need to use the default container with node and all that, so we can
|
||||
# use JS-based actions like actions/checkout@v3...
|
||||
# container:
|
||||
# image: alpine:3.15.0
|
||||
env:
|
||||
ZIG_VERSION: master
|
||||
ARCH: x86_64
|
||||
if: ${{ github.env.GITEA_ACTIONS != 'true' }}
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v3
|
||||
# ARCH is fine, but we can't substitute directly because zig
|
||||
# uses x86_64 instead of amd64. They also use aarch64 instead of arm64.
|
||||
#
|
||||
# However, arm64/linux isn't quite fully tier 1 yet, so this is more of a
|
||||
# TODO: https://github.com/ziglang/zig/issues/2443
|
||||
- name: Install zig
|
||||
run: |
|
||||
apt-get update && apt-get install -y jq
|
||||
file="$(curl -Osw '%{filename_effective}' "$(curl -s https://ziglang.org/download/index.json |jq -r '."'${ZIG_VERSION}'"."x86_64-linux".tarball')")"
|
||||
sudo tar x -C /usr/local -f "${file}"
|
||||
sudo ln -s /usr/local/"${file%%.tar.xz}"/zig /usr/local/bin/zig
|
||||
zig version
|
||||
- name: Run tests
|
||||
run: zig build test --verbose
|
||||
- name: Build example
|
||||
run: ( cd example && zig build ) # Make sure example builds
|
521
Package.zig
Normal file
521
Package.zig
Normal file
|
@ -0,0 +1,521 @@
|
|||
const builtin = @import("builtin");
|
||||
const std = @import("std");
|
||||
const testing = std.testing;
|
||||
const Hasher = @import("codegen/src/Hasher.zig");
|
||||
|
||||
/// This is 128 bits - Even with 2^54 cache entries, the probably of a collision would be under 10^-6
|
||||
const bin_digest_len = 16;
|
||||
const hex_digest_len = bin_digest_len * 2;
|
||||
|
||||
const Package = @This();
|
||||
|
||||
root_src_directory: std.Build.Cache.Directory,
|
||||
|
||||
/// Whether to free `root_src_directory` on `destroy`.
|
||||
root_src_directory_owned: bool = false,
|
||||
allocator: std.mem.Allocator,
|
||||
|
||||
pub const Dependency = struct {
|
||||
url: []const u8,
|
||||
hash: ?[]const u8,
|
||||
};
|
||||
|
||||
pub fn deinit(self: *Package) void {
|
||||
if (self.root_src_directory_owned)
|
||||
self.root_src_directory.closeAndFree(self.allocator);
|
||||
}
|
||||
|
||||
pub fn fetchOneAndUnpack(
|
||||
allocator: std.mem.Allocator,
|
||||
cache_directory: []const u8, // directory to store things
|
||||
dep: Dependency, // thing to download
|
||||
) !*Package {
|
||||
var http_client: std.http.Client = .{ .allocator = allocator };
|
||||
defer http_client.deinit();
|
||||
|
||||
const global_cache_directory: std.Build.Cache.Directory = .{
|
||||
.handle = try std.fs.cwd().makeOpenPath(cache_directory, .{}),
|
||||
.path = cache_directory,
|
||||
};
|
||||
var thread_pool: std.Thread.Pool = undefined;
|
||||
try thread_pool.init(.{ .allocator = allocator });
|
||||
defer thread_pool.deinit();
|
||||
var progress: std.Progress = .{ .dont_print_on_dumb = true };
|
||||
const root_prog_node = progress.start("Fetch Packages", 0);
|
||||
defer root_prog_node.end();
|
||||
return try fetchAndUnpack(
|
||||
&thread_pool,
|
||||
&http_client,
|
||||
global_cache_directory,
|
||||
dep,
|
||||
dep.url,
|
||||
root_prog_node,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn fetchAndUnpack(
|
||||
thread_pool: *std.Thread.Pool, // thread pool for hashing things in parallel
|
||||
http_client: *std.http.Client, // client to download stuff
|
||||
global_cache_directory: std.Build.Cache.Directory, // directory to store things
|
||||
dep: Dependency, // thing to download
|
||||
fqn: []const u8, // used as name for thing downloaded
|
||||
root_prog_node: *std.Progress.Node, // used for outputting to terminal
|
||||
) !*Package {
|
||||
const gpa = http_client.allocator;
|
||||
const s = std.fs.path.sep_str;
|
||||
|
||||
// Check if the expected_hash is already present in the global package
|
||||
// cache, and thereby avoid both fetching and unpacking.
|
||||
if (dep.hash) |h| cached: {
|
||||
const hex_digest = h[0..Hasher.hex_multihash_len];
|
||||
const pkg_dir_sub_path = "p" ++ s ++ hex_digest;
|
||||
|
||||
const build_root = try global_cache_directory.join(gpa, &.{pkg_dir_sub_path});
|
||||
errdefer gpa.free(build_root);
|
||||
|
||||
var pkg_dir = global_cache_directory.handle.openDir(pkg_dir_sub_path, .{}) catch |err| switch (err) {
|
||||
error.FileNotFound => break :cached,
|
||||
else => |e| return e,
|
||||
};
|
||||
errdefer pkg_dir.close();
|
||||
|
||||
root_prog_node.completeOne();
|
||||
|
||||
const ptr = try gpa.create(Package);
|
||||
errdefer gpa.destroy(ptr);
|
||||
|
||||
ptr.* = .{
|
||||
.root_src_directory = .{
|
||||
.path = build_root, // TODO: This leaks memory somehow (should be cleaned in deinit()
|
||||
.handle = pkg_dir,
|
||||
},
|
||||
.root_src_directory_owned = true,
|
||||
.allocator = gpa,
|
||||
};
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
var pkg_prog_node = root_prog_node.start(fqn, 0);
|
||||
defer pkg_prog_node.end();
|
||||
pkg_prog_node.activate();
|
||||
pkg_prog_node.context.refresh();
|
||||
|
||||
const uri = try std.Uri.parse(dep.url);
|
||||
|
||||
const rand_int = std.crypto.random.int(u64);
|
||||
const tmp_dir_sub_path = "tmp" ++ s ++ Hasher.hex64(rand_int);
|
||||
|
||||
const actual_hash = a: {
|
||||
var tmp_directory: std.Build.Cache.Directory = d: {
|
||||
const path = try global_cache_directory.join(gpa, &.{tmp_dir_sub_path});
|
||||
errdefer gpa.free(path);
|
||||
|
||||
const iterable_dir = try global_cache_directory.handle.makeOpenPathIterable(tmp_dir_sub_path, .{});
|
||||
errdefer iterable_dir.close();
|
||||
|
||||
break :d .{
|
||||
.path = path,
|
||||
.handle = iterable_dir.dir,
|
||||
};
|
||||
};
|
||||
defer tmp_directory.closeAndFree(gpa);
|
||||
|
||||
var h = std.http.Headers{ .allocator = gpa };
|
||||
defer h.deinit();
|
||||
|
||||
var req = try http_client.request(.GET, uri, h, .{});
|
||||
defer req.deinit();
|
||||
|
||||
try req.start();
|
||||
try req.wait();
|
||||
|
||||
if (req.response.status != .ok) {
|
||||
std.log.err("Expected response status '200 OK' got '{} {s}'", .{
|
||||
@intFromEnum(req.response.status),
|
||||
req.response.status.phrase() orelse "",
|
||||
});
|
||||
return error.UnexpectedResponseStatus;
|
||||
}
|
||||
|
||||
const content_type = req.response.headers.getFirstValue("Content-Type") orelse
|
||||
return error.MissingContentTypeHeader;
|
||||
|
||||
var prog_reader: ProgressReader(std.http.Client.Request.Reader) = .{
|
||||
.child_reader = req.reader(),
|
||||
.prog_node = &pkg_prog_node,
|
||||
.unit = if (req.response.content_length) |content_length| unit: {
|
||||
const kib = content_length / 1024;
|
||||
const mib = kib / 1024;
|
||||
if (mib > 0) {
|
||||
pkg_prog_node.setEstimatedTotalItems(@intCast(mib));
|
||||
pkg_prog_node.setUnit("MiB");
|
||||
break :unit .mib;
|
||||
} else {
|
||||
pkg_prog_node.setEstimatedTotalItems(@intCast(@max(1, kib)));
|
||||
pkg_prog_node.setUnit("KiB");
|
||||
break :unit .kib;
|
||||
}
|
||||
} else .any,
|
||||
};
|
||||
pkg_prog_node.context.refresh();
|
||||
|
||||
if (std.ascii.eqlIgnoreCase(content_type, "application/gzip") or
|
||||
std.ascii.eqlIgnoreCase(content_type, "application/x-gzip") or
|
||||
std.ascii.eqlIgnoreCase(content_type, "application/tar+gzip"))
|
||||
{
|
||||
// I observed the gzip stream to read 1 byte at a time, so I am using a
|
||||
// buffered reader on the front of it.
|
||||
try unpackTarball(gpa, prog_reader.reader(), tmp_directory.handle, std.compress.gzip);
|
||||
} else if (std.ascii.eqlIgnoreCase(content_type, "application/x-xz")) {
|
||||
// I have not checked what buffer sizes the xz decompression implementation uses
|
||||
// by default, so the same logic applies for buffering the reader as for gzip.
|
||||
try unpackTarball(gpa, prog_reader.reader(), tmp_directory.handle, std.compress.xz);
|
||||
} else if (std.ascii.eqlIgnoreCase(content_type, "application/octet-stream")) {
|
||||
// support gitlab tarball urls such as https://gitlab.com/<namespace>/<project>/-/archive/<sha>/<project>-<sha>.tar.gz
|
||||
// whose content-disposition header is: 'attachment; filename="<project>-<sha>.tar.gz"'
|
||||
const content_disposition = req.response.headers.getFirstValue("Content-Disposition") orelse
|
||||
return error.@"Missing 'Content-Disposition' header for Content-Type=application/octet-stream";
|
||||
if (isTarAttachment(content_disposition)) {
|
||||
try unpackTarball(gpa, prog_reader.reader(), tmp_directory.handle, std.compress.gzip);
|
||||
} else {
|
||||
std.log.err("Unsupported 'Content-Disposition' header value: '{s}' for Content-Type=application/octet-stream", .{content_disposition});
|
||||
return error.UnsupportedContentDispositionHeader;
|
||||
}
|
||||
} else {
|
||||
std.log.err("Unsupported 'Content-Type' header value: '{s}'", .{content_type});
|
||||
return error.UnsupportedContentTypeHeader;
|
||||
}
|
||||
|
||||
// Download completed - stop showing downloaded amount as progress
|
||||
pkg_prog_node.setEstimatedTotalItems(0);
|
||||
pkg_prog_node.setCompletedItems(0);
|
||||
pkg_prog_node.context.refresh();
|
||||
|
||||
// TODO: delete files not included in the package prior to computing the package hash.
|
||||
// for example, if the ini file has directives to include/not include certain files,
|
||||
// apply those rules directly to the filesystem right here. This ensures that files
|
||||
// not protected by the hash are not present on the file system.
|
||||
|
||||
// TODO: raise an error for files that have illegal paths on some operating systems.
|
||||
// For example, on Linux a path with a backslash should raise an error here.
|
||||
// Of course, if the ignore rules above omit the file from the package, then everything
|
||||
// is fine and no error should be raised.
|
||||
|
||||
break :a try Hasher.computeDirectoryHash(thread_pool, .{ .dir = tmp_directory.handle }, &.{});
|
||||
};
|
||||
|
||||
const pkg_dir_sub_path = "p" ++ s ++ Hasher.hexDigest(actual_hash);
|
||||
try renameTmpIntoCache(global_cache_directory.handle, tmp_dir_sub_path, pkg_dir_sub_path);
|
||||
|
||||
const actual_hex = Hasher.hexDigest(actual_hash);
|
||||
if (dep.hash) |h| {
|
||||
if (!std.mem.eql(u8, h, &actual_hex)) {
|
||||
std.log.err("hash mismatch: expected: {s}, found: {s}", .{
|
||||
h, actual_hex,
|
||||
});
|
||||
return error.HashMismatch;
|
||||
}
|
||||
} else {
|
||||
std.log.err("No hash supplied. Expecting hash \"{s}\"", .{actual_hex});
|
||||
return error.NoHashSupplied;
|
||||
}
|
||||
|
||||
const build_root = try global_cache_directory.join(gpa, &.{pkg_dir_sub_path});
|
||||
defer gpa.free(build_root);
|
||||
|
||||
const mod = try createWithDir(gpa, global_cache_directory, pkg_dir_sub_path);
|
||||
return mod;
|
||||
}
|
||||
fn ProgressReader(comptime ReaderType: type) type {
|
||||
return struct {
|
||||
child_reader: ReaderType,
|
||||
bytes_read: u64 = 0,
|
||||
prog_node: *std.Progress.Node,
|
||||
unit: enum {
|
||||
kib,
|
||||
mib,
|
||||
any,
|
||||
},
|
||||
|
||||
pub const Error = ReaderType.Error;
|
||||
pub const Reader = std.io.Reader(*@This(), Error, read);
|
||||
|
||||
pub fn read(self: *@This(), buf: []u8) Error!usize {
|
||||
const amt = try self.child_reader.read(buf);
|
||||
self.bytes_read += amt;
|
||||
const kib = self.bytes_read / 1024;
|
||||
const mib = kib / 1024;
|
||||
switch (self.unit) {
|
||||
.kib => self.prog_node.setCompletedItems(@intCast(kib)),
|
||||
.mib => self.prog_node.setCompletedItems(@intCast(mib)),
|
||||
.any => {
|
||||
if (mib > 0) {
|
||||
self.prog_node.setUnit("MiB");
|
||||
self.prog_node.setCompletedItems(@intCast(mib));
|
||||
} else {
|
||||
self.prog_node.setUnit("KiB");
|
||||
self.prog_node.setCompletedItems(@intCast(kib));
|
||||
}
|
||||
},
|
||||
}
|
||||
self.prog_node.context.maybeRefresh();
|
||||
return amt;
|
||||
}
|
||||
|
||||
pub fn reader(self: *@This()) Reader {
|
||||
return .{ .context = self };
|
||||
}
|
||||
};
|
||||
}
|
||||
fn isTarAttachment(content_disposition: []const u8) bool {
|
||||
const disposition_type_end = std.ascii.indexOfIgnoreCase(content_disposition, "attachment;") orelse return false;
|
||||
|
||||
var value_start = std.ascii.indexOfIgnoreCasePos(content_disposition, disposition_type_end + 1, "filename") orelse return false;
|
||||
value_start += "filename".len;
|
||||
if (content_disposition[value_start] == '*') {
|
||||
value_start += 1;
|
||||
}
|
||||
if (content_disposition[value_start] != '=') return false;
|
||||
value_start += 1;
|
||||
|
||||
var value_end = std.mem.indexOfPos(u8, content_disposition, value_start, ";") orelse content_disposition.len;
|
||||
if (content_disposition[value_end - 1] == '\"') {
|
||||
value_end -= 1;
|
||||
}
|
||||
return std.ascii.endsWithIgnoreCase(content_disposition[value_start..value_end], ".tar.gz");
|
||||
}
|
||||
fn renameTmpIntoCache(
|
||||
cache_dir: std.fs.Dir,
|
||||
tmp_dir_sub_path: []const u8,
|
||||
dest_dir_sub_path: []const u8,
|
||||
) !void {
|
||||
std.debug.assert(dest_dir_sub_path[1] == std.fs.path.sep);
|
||||
var handled_missing_dir = false;
|
||||
while (true) {
|
||||
cache_dir.rename(tmp_dir_sub_path, dest_dir_sub_path) catch |err| switch (err) {
|
||||
error.FileNotFound => {
|
||||
if (handled_missing_dir) return err;
|
||||
cache_dir.makeDir(dest_dir_sub_path[0..1]) catch |mkd_err| switch (mkd_err) {
|
||||
error.PathAlreadyExists => handled_missing_dir = true,
|
||||
else => |e| return e,
|
||||
};
|
||||
continue;
|
||||
},
|
||||
error.PathAlreadyExists, error.AccessDenied => {
|
||||
// Package has been already downloaded and may already be in use on the system.
|
||||
cache_dir.deleteTree(tmp_dir_sub_path) catch |del_err| {
|
||||
std.log.warn("unable to delete temp directory: {s}", .{@errorName(del_err)});
|
||||
};
|
||||
},
|
||||
else => |e| return e,
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fn createWithDir(
|
||||
gpa: std.mem.Allocator,
|
||||
directory: std.Build.Cache.Directory,
|
||||
/// Relative to `directory`. If null, means `directory` is the root src dir
|
||||
/// and is owned externally.
|
||||
root_src_dir_path: ?[]const u8,
|
||||
) !*Package {
|
||||
const ptr = try gpa.create(Package);
|
||||
errdefer gpa.destroy(ptr);
|
||||
|
||||
if (root_src_dir_path) |p| {
|
||||
const owned_dir_path = try directory.join(gpa, &[1][]const u8{p});
|
||||
errdefer gpa.free(owned_dir_path);
|
||||
|
||||
ptr.* = .{
|
||||
.root_src_directory = .{
|
||||
.path = owned_dir_path,
|
||||
.handle = try directory.handle.openDir(p, .{}),
|
||||
},
|
||||
.root_src_directory_owned = true,
|
||||
.allocator = gpa,
|
||||
};
|
||||
} else {
|
||||
ptr.* = .{
|
||||
.root_src_directory = directory,
|
||||
.root_src_directory_owned = false,
|
||||
.allocator = gpa,
|
||||
};
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
// Create/Write a file, close it, then grab its stat.mtime timestamp.
|
||||
fn testGetCurrentFileTimestamp(dir: std.fs.Dir) !i128 {
|
||||
const test_out_file = "test-filetimestamp.tmp";
|
||||
|
||||
var file = try dir.createFile(test_out_file, .{
|
||||
.read = true,
|
||||
.truncate = true,
|
||||
});
|
||||
defer {
|
||||
file.close();
|
||||
dir.deleteFile(test_out_file) catch {};
|
||||
}
|
||||
|
||||
return (try file.stat()).mtime;
|
||||
}
|
||||
|
||||
// These functions come from src/Package.zig, src/Manifest.zig in the compiler,
|
||||
// not the standard library
|
||||
fn unpackTarball(
|
||||
gpa: std.mem.Allocator,
|
||||
req_reader: anytype,
|
||||
out_dir: std.fs.Dir,
|
||||
comptime compression: type,
|
||||
) !void {
|
||||
var br = std.io.bufferedReaderSize(std.crypto.tls.max_ciphertext_record_len, req_reader);
|
||||
|
||||
var decompress = try compression.decompress(gpa, br.reader());
|
||||
defer decompress.deinit();
|
||||
|
||||
try std.tar.pipeToFileSystem(out_dir, decompress.reader(), .{
|
||||
.strip_components = 1,
|
||||
// TODO: we would like to set this to executable_bit_only, but two
|
||||
// things need to happen before that:
|
||||
// 1. the tar implementation needs to support it
|
||||
// 2. the hashing algorithm here needs to support detecting the is_executable
|
||||
// bit on Windows from the ACLs (see the isExecutable function).
|
||||
.mode_mode = .ignore,
|
||||
});
|
||||
}
|
||||
|
||||
test {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
test "cache a file and recall it" {
|
||||
if (builtin.os.tag == .wasi) {
|
||||
// https://github.com/ziglang/zig/issues/5437
|
||||
|
||||
return error.SkipZigTest;
|
||||
}
|
||||
|
||||
var tmp = testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
|
||||
const temp_file = "test.txt";
|
||||
const temp_file2 = "test2.txt";
|
||||
const temp_manifest_dir = "temp_manifest_dir";
|
||||
|
||||
try tmp.dir.writeFile(temp_file, "Hello, world!\n");
|
||||
try tmp.dir.writeFile(temp_file2, "yo mamma\n");
|
||||
|
||||
// Wait for file timestamps to tick
|
||||
|
||||
const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
|
||||
while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) {
|
||||
std.time.sleep(1);
|
||||
}
|
||||
|
||||
var digest1: [hex_digest_len]u8 = undefined;
|
||||
var digest2: [hex_digest_len]u8 = undefined;
|
||||
|
||||
{
|
||||
var cache = std.build.Cache{
|
||||
.gpa = testing.allocator,
|
||||
.manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
|
||||
};
|
||||
cache.addPrefix(.{ .path = null, .handle = tmp.dir });
|
||||
defer cache.manifest_dir.close();
|
||||
|
||||
{
|
||||
var ch = cache.obtain();
|
||||
defer ch.deinit();
|
||||
|
||||
ch.hash.add(true);
|
||||
ch.hash.add(@as(u16, 1234));
|
||||
ch.hash.addBytes("1234");
|
||||
_ = try ch.addFile(temp_file, null);
|
||||
|
||||
// There should be nothing in the cache
|
||||
|
||||
try testing.expectEqual(false, try ch.hit());
|
||||
|
||||
digest1 = ch.final();
|
||||
try ch.writeManifest();
|
||||
}
|
||||
{
|
||||
var ch = cache.obtain();
|
||||
defer ch.deinit();
|
||||
|
||||
ch.hash.add(true);
|
||||
ch.hash.add(@as(u16, 1234));
|
||||
ch.hash.addBytes("1234");
|
||||
_ = try ch.addFile(temp_file, null);
|
||||
|
||||
// Cache hit! We just "built" the same file
|
||||
|
||||
try testing.expect(try ch.hit());
|
||||
digest2 = ch.final();
|
||||
|
||||
try testing.expectEqual(false, ch.have_exclusive_lock);
|
||||
}
|
||||
|
||||
try testing.expectEqual(digest1, digest2);
|
||||
}
|
||||
}
|
||||
test "fetch and unpack" {
|
||||
const alloc = std.testing.allocator;
|
||||
var http_client: std.http.Client = .{ .allocator = alloc };
|
||||
defer http_client.deinit();
|
||||
|
||||
const global_cache_directory: std.Build.Cache.Directory = .{
|
||||
.handle = try std.fs.cwd().makeOpenPath("test-pkg", .{}),
|
||||
.path = "test-pkg",
|
||||
};
|
||||
var thread_pool: std.Thread.Pool = undefined;
|
||||
try thread_pool.init(.{ .allocator = alloc });
|
||||
defer thread_pool.deinit();
|
||||
var progress: std.Progress = .{ .dont_print_on_dumb = true };
|
||||
const root_prog_node = progress.start("Fetch Packages", 0);
|
||||
defer root_prog_node.end();
|
||||
const pkg = try fetchAndUnpack(
|
||||
&thread_pool,
|
||||
&http_client,
|
||||
global_cache_directory,
|
||||
.{
|
||||
.url = "https://github.com/aws/aws-sdk-go-v2/archive/7502ff360b1c3b79cbe117437327f6ff5fb89f65.tar.gz",
|
||||
.hash = "1220a414719bff14c9362fb1c695e3346fa12ec2e728bae5757a57aae7738916ffd2",
|
||||
},
|
||||
"https://github.com/aws/aws-sdk-go-v2/archive/7502ff360b1c3b79cbe117437327f6ff5fb89f65.tar.gz",
|
||||
root_prog_node,
|
||||
);
|
||||
defer alloc.destroy(pkg);
|
||||
defer pkg.deinit();
|
||||
}
|
||||
test "fetch one and unpack" {
|
||||
const pkg = try fetchOneAndUnpack(
|
||||
std.testing.allocator,
|
||||
"test-pkg",
|
||||
.{
|
||||
.url = "https://github.com/aws/aws-sdk-go-v2/archive/7502ff360b1c3b79cbe117437327f6ff5fb89f65.tar.gz",
|
||||
.hash = "1220a414719bff14c9362fb1c695e3346fa12ec2e728bae5757a57aae7738916ffd2",
|
||||
},
|
||||
);
|
||||
defer std.testing.allocator.destroy(pkg);
|
||||
defer pkg.deinit();
|
||||
try std.testing.expectEqualStrings(
|
||||
"test-pkg/p/1220a414719bff14c9362fb1c695e3346fa12ec2e728bae5757a57aae7738916ffd2",
|
||||
pkg.root_src_directory.path.?,
|
||||
);
|
||||
}
|
||||
test "isTarAttachment" {
|
||||
try std.testing.expect(isTarAttachment("attaChment; FILENAME=\"stuff.tar.gz\"; size=42"));
|
||||
try std.testing.expect(isTarAttachment("attachment; filename*=\"stuff.tar.gz\""));
|
||||
try std.testing.expect(isTarAttachment("ATTACHMENT; filename=\"stuff.tar.gz\""));
|
||||
try std.testing.expect(isTarAttachment("attachment; FileName=\"stuff.tar.gz\""));
|
||||
try std.testing.expect(isTarAttachment("attachment; FileName*=UTF-8\'\'xyz%2Fstuff.tar.gz"));
|
||||
|
||||
try std.testing.expect(!isTarAttachment("attachment FileName=\"stuff.tar.gz\""));
|
||||
try std.testing.expect(!isTarAttachment("attachment; FileName=\"stuff.tar\""));
|
||||
try std.testing.expect(!isTarAttachment("attachment; FileName\"stuff.gz\""));
|
||||
try std.testing.expect(!isTarAttachment("attachment; size=42"));
|
||||
try std.testing.expect(!isTarAttachment("inline; size=42"));
|
||||
try std.testing.expect(!isTarAttachment("FileName=\"stuff.tar.gz\"; attachment;"));
|
||||
try std.testing.expect(!isTarAttachment("FileName=\"stuff.tar.gz\";"));
|
||||
}
|
33
README.md
33
README.md
|
@ -1,13 +1,7 @@
|
|||
AWS SDK for Zig
|
||||
===============
|
||||
|
||||
[Last Mach Nominated Zig Version](https://machengine.org/about/nominated-zig/):
|
||||
|
||||
[![Build Status: Zig 0.12.0-dev.3180+83e578a18](https://actions-status.lerch.org/lobo/aws-sdk-for-zig/zig-mach)](https://git.lerch.org/lobo/aws-sdk-for-zig/actions?workflow=zig-mach.yaml&state=closed)
|
||||
|
||||
[Nightly Zig](https://ziglang.org/download/):
|
||||
|
||||
[![Build Status: Zig Nightly](https://actions-status.lerch.org/lobo/aws-sdk-for-zig/zig-nightly)](https://git.lerch.org/lobo/aws-sdk-for-zig/actions?workflow=zig-nightly.yaml&state=closed)
|
||||
[![Build Status](https://actions-status.lerch.org/lobo/aws-sdk-for-zig/build)](https://git.lerch.org/lobo/aws-sdk-for-zig/actions?workflow=build.yaml&state=closed)
|
||||
|
||||
**NOTE: TLS 1.3 support is still deploying across AWS. Some services, especially S3,
|
||||
may or may not be available without a proxy, depending on the region.
|
||||
|
@ -17,30 +11,15 @@ Current executable size for the demo is 980k after compiling with -Doptimize=Rel
|
|||
in x86_linux, and will vary based on services used. Tested targets:
|
||||
|
||||
* x86_64-linux
|
||||
* riscv64-linux\*
|
||||
* riscv64-linux
|
||||
* aarch64-linux
|
||||
* x86_64-windows\*\*
|
||||
* x86_64-windows
|
||||
* arm-linux
|
||||
* aarch64-macos
|
||||
* x86_64-macos
|
||||
|
||||
Tested targets are built, but not continuously tested, by CI.
|
||||
|
||||
\* On Zig 0.12, riscv64-linux tests take a significant time to compile (each aws.zig test takes approximately 1min, 45 seconds to compile on Intel i9 10th gen)
|
||||
|
||||
\*\* On Zig 0.12, x86_64-windows tests have one test skipped as LLVM consumes all available RAM on the system
|
||||
|
||||
|
||||
Zig-Develop Branch
|
||||
------------------
|
||||
|
||||
This branch is intended for use with the in-development version of Zig. This
|
||||
starts with 0.12.0-dev.3180+83e578a18. I will try to keep this branch up to date
|
||||
with latest, but with a special eye towards aligning with [Mach Engine's Nominated
|
||||
Zig Versions](https://machengine.org/about/nominated-zig/). As nightly zig versions
|
||||
disappear off the downloads page (and back end server), we can use the mirroring
|
||||
that the Mach Engine participates in to pull these versions.
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
|
@ -71,7 +50,7 @@ Limitations
|
|||
|
||||
The zig 0.11 HTTP client supports TLS 1.3 only. AWS has committed to
|
||||
[TLS 1.3 support across all services by the end of 2023](https://aws.amazon.com/blogs/security/faster-aws-cloud-connections-with-tls-1-3/),
|
||||
but a few services as of April 1, 2024 have not been upgraded, and S3 is
|
||||
but a few services as of February 28, 2024 have not been upgraded, and S3 is
|
||||
a bit intermittent. Proxy support has been added, so to get to the services that
|
||||
do not yet support TLS 1.3, you can use something like [mitmproxy](https://mitmproxy.org/)
|
||||
to proxy those requests until roll out is complete.
|
||||
|
@ -102,13 +81,13 @@ this point.
|
|||
|
||||
NOTE ON S3: For me, S3 is currently intermittently available using TLS 1.3, so
|
||||
it appears deployments are in progress. The last couple days it has been
|
||||
not been available consistently, so I have added it back to the list.
|
||||
available consistently, so I have removed it from the list.
|
||||
|
||||
```
|
||||
data.iot
|
||||
models.lex
|
||||
opsworks
|
||||
s3
|
||||
support
|
||||
```
|
||||
|
||||
Dependency tree
|
||||
|
|
121
build.zig
121
build.zig
|
@ -1,8 +1,12 @@
|
|||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const Builder = @import("std").Build;
|
||||
const Builder = @import("std").build.Builder;
|
||||
const Package = @import("Package.zig");
|
||||
|
||||
const models_url = "https://github.com/aws/aws-sdk-go-v2/archive/58cf6509525a12d64fd826da883bfdbacbd2f00e.tar.gz";
|
||||
const models_hash: ?[]const u8 = "122017a2f3081ce83c23e0c832feb1b8b4176d507b6077f522855dc774bcf83ee315";
|
||||
const models_subdir = "codegen/sdk-codegen/aws-models/"; // note will probably not work on windows
|
||||
const models_dir = "p" ++ std.fs.path.sep_str ++ (models_hash orelse "") ++ std.fs.path.sep_str ++ models_subdir;
|
||||
|
||||
const test_targets = [_]std.zig.CrossTarget{
|
||||
.{}, // native
|
||||
|
@ -14,12 +18,10 @@ const test_targets = [_]std.zig.CrossTarget{
|
|||
.cpu_arch = .aarch64,
|
||||
.os_tag = .linux,
|
||||
},
|
||||
// // The test executable just spins forever in LLVM using nominated zig 0.12 March 2024
|
||||
// // This is likely a LLVM problem unlikely to be fixed in zig 0.12
|
||||
// .{
|
||||
// .cpu_arch = .riscv64,
|
||||
// .os_tag = .linux,
|
||||
// },
|
||||
.{
|
||||
.cpu_arch = .riscv64,
|
||||
.os_tag = .linux,
|
||||
},
|
||||
.{
|
||||
.cpu_arch = .arm,
|
||||
.os_tag = .linux,
|
||||
|
@ -77,18 +79,22 @@ pub fn build(b: *Builder) !void {
|
|||
.optimize = optimize,
|
||||
});
|
||||
const smithy_module = smithy_dep.module("smithy");
|
||||
exe.root_module.addImport("smithy", smithy_module); // not sure this should be here...
|
||||
exe.addModule("smithy", smithy_module); // not sure this should be here...
|
||||
|
||||
// Expose module to others
|
||||
_ = b.addModule("aws", .{
|
||||
.root_source_file = .{ .path = "src/aws.zig" },
|
||||
.imports = &.{.{ .name = "smithy", .module = smithy_module }},
|
||||
.source_file = .{ .path = "src/aws.zig" },
|
||||
.dependencies = &[_]std.build.ModuleDependency{
|
||||
.{ .name = "smithy", .module = smithy_module },
|
||||
},
|
||||
});
|
||||
|
||||
// Expose module to others
|
||||
_ = b.addModule("aws-signing", .{
|
||||
.root_source_file = .{ .path = "src/aws_signing.zig" },
|
||||
.imports = &.{.{ .name = "smithy", .module = smithy_module }},
|
||||
.source_file = .{ .path = "src/aws_signing.zig" },
|
||||
.dependencies = &[_]std.build.ModuleDependency{
|
||||
.{ .name = "smithy", .module = smithy_module },
|
||||
},
|
||||
});
|
||||
// TODO: This does not work correctly due to https://github.com/ziglang/zig/issues/16354
|
||||
//
|
||||
|
@ -112,6 +118,9 @@ pub fn build(b: *Builder) !void {
|
|||
const run_step = b.step("run", "Run the app");
|
||||
run_step.dependOn(&run_cmd.step);
|
||||
|
||||
const fm = b.step("fetch", "Fetch model files");
|
||||
var fetch_step = FetchStep.create(b, models_url, models_hash);
|
||||
fm.dependOn(&fetch_step.step);
|
||||
const gen_step = blk: {
|
||||
const cg = b.step("gen", "Generate zig service code from smithy models");
|
||||
|
||||
|
@ -119,35 +128,21 @@ pub fn build(b: *Builder) !void {
|
|||
.name = "codegen",
|
||||
.root_source_file = .{ .path = "codegen/src/main.zig" },
|
||||
// We need this generated for the host, not the real target
|
||||
.target = b.host,
|
||||
// .target = target,
|
||||
.optimize = if (b.verbose) .Debug else .ReleaseSafe,
|
||||
});
|
||||
cg_exe.root_module.addImport("smithy", smithy_dep.module("smithy"));
|
||||
cg_exe.addModule("smithy", smithy_dep.module("smithy"));
|
||||
var cg_cmd = b.addRunArtifact(cg_exe);
|
||||
cg_cmd.addArg("--models");
|
||||
const hash = hash_blk: {
|
||||
for (b.available_deps) |dep| {
|
||||
const dep_name = dep.@"0";
|
||||
const dep_hash = dep.@"1";
|
||||
if (std.mem.eql(u8, dep_name, "models"))
|
||||
break :hash_blk dep_hash;
|
||||
}
|
||||
return error.DependencyNamedModelsNotFoundInBuildZigZon;
|
||||
};
|
||||
cg_cmd.addArg(try std.fs.path.join(
|
||||
b.allocator,
|
||||
&[_][]const u8{
|
||||
b.graph.global_cache_root.path.?,
|
||||
"p",
|
||||
hash,
|
||||
models_subdir,
|
||||
},
|
||||
&[_][]const u8{ b.global_cache_root.path.?, models_dir },
|
||||
));
|
||||
cg_cmd.addArg("--output");
|
||||
cg_cmd.addDirectoryArg(b.path("src/models"));
|
||||
cg_cmd.addDirectoryArg(std.Build.FileSource.relative("src/models"));
|
||||
if (b.verbose)
|
||||
cg_cmd.addArg("--verbose");
|
||||
// cg_cmd.step.dependOn(&fetch_step.step);
|
||||
cg_cmd.step.dependOn(&fetch_step.step);
|
||||
// TODO: this should use zig_exe from std.Build
|
||||
// codegen should store a hash in a comment
|
||||
// this would be hash of the exe that created the file
|
||||
|
@ -173,29 +168,15 @@ pub fn build(b: *Builder) !void {
|
|||
// running the unit tests.
|
||||
const test_step = b.step("test", "Run unit tests");
|
||||
|
||||
// // Creates a step for unit testing. This only builds the test executable
|
||||
// // but does not run it.
|
||||
// const unit_tests = b.addTest(.{
|
||||
// .root_source_file = .{ .path = "src/aws.zig" },
|
||||
// .target = target,
|
||||
// .optimize = optimize,
|
||||
// });
|
||||
// unit_tests.root_module.addImport("smithy", smithy_dep.module("smithy"));
|
||||
// unit_tests.step.dependOn(gen_step);
|
||||
//
|
||||
// const run_unit_tests = b.addRunArtifact(unit_tests);
|
||||
// run_unit_tests.skip_foreign_checks = true;
|
||||
|
||||
// test_step.dependOn(&run_unit_tests.step);
|
||||
for (test_targets) |t| {
|
||||
// Creates a step for unit testing. This only builds the test executable
|
||||
// but does not run it.
|
||||
const unit_tests = b.addTest(.{
|
||||
.root_source_file = .{ .path = "src/aws.zig" },
|
||||
.target = b.resolveTargetQuery(t),
|
||||
.target = t,
|
||||
.optimize = optimize,
|
||||
});
|
||||
unit_tests.root_module.addImport("smithy", smithy_dep.module("smithy"));
|
||||
unit_tests.addModule("smithy", smithy_dep.module("smithy"));
|
||||
unit_tests.step.dependOn(gen_step);
|
||||
|
||||
const run_unit_tests = b.addRunArtifact(unit_tests);
|
||||
|
@ -205,3 +186,49 @@ pub fn build(b: *Builder) !void {
|
|||
}
|
||||
b.installArtifact(exe);
|
||||
}
|
||||
const FetchStep = struct {
|
||||
step: std.Build.Step,
|
||||
url: []const u8,
|
||||
hash: ?[]const u8,
|
||||
|
||||
pub fn create(owner: *std.Build, url: []const u8, hash: ?[]const u8) *FetchStep {
|
||||
const fs = owner.allocator.create(FetchStep) catch @panic("OOM");
|
||||
fs.* = .{
|
||||
.step = std.Build.Step.init(.{
|
||||
.id = .custom,
|
||||
.name = "FetchStep",
|
||||
.owner = owner,
|
||||
.makeFn = make,
|
||||
}),
|
||||
.url = url,
|
||||
.hash = hash,
|
||||
};
|
||||
return fs;
|
||||
}
|
||||
|
||||
fn make(step: *std.Build.Step, prog_node: *std.Progress.Node) !void {
|
||||
const b = step.owner;
|
||||
const self = @fieldParentPtr(FetchStep, "step", step);
|
||||
|
||||
const alloc = b.allocator;
|
||||
var http_client: std.http.Client = .{ .allocator = alloc };
|
||||
defer http_client.deinit();
|
||||
|
||||
var thread_pool: std.Thread.Pool = undefined;
|
||||
try thread_pool.init(.{ .allocator = alloc });
|
||||
defer thread_pool.deinit();
|
||||
const pkg = try Package.fetchAndUnpack(
|
||||
&thread_pool,
|
||||
&http_client,
|
||||
b.global_cache_root,
|
||||
.{
|
||||
.url = self.url,
|
||||
.hash = self.hash,
|
||||
},
|
||||
self.url,
|
||||
prog_node,
|
||||
);
|
||||
defer alloc.destroy(pkg);
|
||||
defer pkg.deinit();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,16 +1,11 @@
|
|||
.{
|
||||
.name = "aws-zig",
|
||||
.version = "0.0.1",
|
||||
.paths = .{""},
|
||||
|
||||
.dependencies = .{
|
||||
.smithy = .{
|
||||
.url = "https://git.lerch.org/lobo/smithy/archive/1e534201c4df5ea4f615faeedc69d414adbec0b1.tar.gz",
|
||||
.hash = "1220af63ae0498010004af79936cedf3fe6702f516daab77ebbd97a274eba1b42aad",
|
||||
},
|
||||
.models = .{
|
||||
.url = "https://github.com/aws/aws-sdk-go-v2/archive/58cf6509525a12d64fd826da883bfdbacbd2f00e.tar.gz",
|
||||
.hash = "122017a2f3081ce83c23e0c832feb1b8b4176d507b6077f522855dc774bcf83ee315",
|
||||
.url = "https://git.lerch.org/lobo/smithy/archive/d6b6331defdfd33f36258caf26b0b82ce794cd28.tar.gz",
|
||||
.hash = "1220695f5be11b7bd714f6181c60b0e590da5da7411de111ca51cacf1ea4a8169669",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -77,13 +77,13 @@ pub fn hex64(x: u64) [16]u8 {
|
|||
return result;
|
||||
}
|
||||
|
||||
pub const walkerFn = *const fn (std.fs.Dir.Walker.Entry) bool;
|
||||
pub const walkerFn = *const fn (std.fs.IterableDir.Walker.WalkerEntry) bool;
|
||||
|
||||
fn included(entry: std.fs.Dir.Walker.Entry) bool {
|
||||
fn included(entry: std.fs.IterableDir.Walker.WalkerEntry) bool {
|
||||
_ = entry;
|
||||
return true;
|
||||
}
|
||||
fn excluded(entry: std.fs.Dir.Walker.Entry) bool {
|
||||
fn excluded(entry: std.fs.IterableDir.Walker.WalkerEntry) bool {
|
||||
_ = entry;
|
||||
return false;
|
||||
}
|
||||
|
@ -96,7 +96,7 @@ pub const ComputeDirectoryOptions = struct {
|
|||
|
||||
pub fn computeDirectoryHash(
|
||||
thread_pool: *std.Thread.Pool,
|
||||
dir: std.fs.Dir,
|
||||
dir: std.fs.IterableDir,
|
||||
options: *ComputeDirectoryOptions,
|
||||
) ![Hash.digest_length]u8 {
|
||||
const gpa = thread_pool.allocator;
|
||||
|
@ -138,7 +138,7 @@ pub fn computeDirectoryHash(
|
|||
.failure = undefined, // to be populated by the worker
|
||||
};
|
||||
wait_group.start();
|
||||
try thread_pool.spawn(workerHashFile, .{ dir, hashed_file, &wait_group });
|
||||
try thread_pool.spawn(workerHashFile, .{ dir.dir, hashed_file, &wait_group });
|
||||
|
||||
try all_files.append(hashed_file);
|
||||
}
|
||||
|
@ -206,6 +206,6 @@ fn isExecutable(file: std.fs.File) !bool {
|
|||
return false;
|
||||
} else {
|
||||
const stat = try file.stat();
|
||||
return (stat.mode & std.posix.S.IXUSR) != 0;
|
||||
return (stat.mode & std.os.S.IXUSR) != 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ pub fn main() anyerror!void {
|
|||
|
||||
var output_dir = std.fs.cwd();
|
||||
defer if (output_dir.fd > 0) output_dir.close();
|
||||
var models_dir: ?std.fs.Dir = null;
|
||||
var models_dir: ?std.fs.IterableDir = null;
|
||||
defer if (models_dir) |*m| m.close();
|
||||
for (args, 0..) |arg, i| {
|
||||
if (std.mem.eql(u8, "--help", arg) or
|
||||
|
@ -31,7 +31,7 @@ pub fn main() anyerror!void {
|
|||
if (std.mem.eql(u8, "--output", arg))
|
||||
output_dir = try output_dir.makeOpenPath(args[i + 1], .{});
|
||||
if (std.mem.eql(u8, "--models", arg))
|
||||
models_dir = try std.fs.cwd().openDir(args[i + 1], .{ .iterate = true });
|
||||
models_dir = try std.fs.cwd().openIterableDir(args[i + 1], .{});
|
||||
}
|
||||
// TODO: Seems like we should remove this in favor of a package
|
||||
try output_dir.writeFile("json.zig", json_zig);
|
||||
|
@ -75,7 +75,7 @@ pub fn main() anyerror!void {
|
|||
defer cwd.close();
|
||||
defer cwd.setAsCwd() catch unreachable;
|
||||
|
||||
try m.setAsCwd();
|
||||
try m.dir.setAsCwd();
|
||||
try processDirectories(m, output_dir);
|
||||
}
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ const OutputManifest = struct {
|
|||
model_dir_hash_digest: [Hasher.hex_multihash_len]u8,
|
||||
output_dir_hash_digest: [Hasher.hex_multihash_len]u8,
|
||||
};
|
||||
fn processDirectories(models_dir: std.fs.Dir, output_dir: std.fs.Dir) !void {
|
||||
fn processDirectories(models_dir: std.fs.IterableDir, output_dir: std.fs.Dir) !void {
|
||||
// Let's get ready to hash!!
|
||||
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
||||
defer arena.deinit();
|
||||
|
@ -131,15 +131,15 @@ fn processDirectories(models_dir: std.fs.Dir, output_dir: std.fs.Dir) !void {
|
|||
}
|
||||
|
||||
var model_digest: ?[Hasher.hex_multihash_len]u8 = null;
|
||||
fn calculateDigests(models_dir: std.fs.Dir, output_dir: std.fs.Dir, thread_pool: *std.Thread.Pool) !OutputManifest {
|
||||
fn calculateDigests(models_dir: std.fs.IterableDir, output_dir: std.fs.Dir, thread_pool: *std.Thread.Pool) !OutputManifest {
|
||||
const model_hash = if (model_digest) |m| m[0..Hasher.digest_len].* else try Hasher.computeDirectoryHash(thread_pool, models_dir, @constCast(&Hasher.ComputeDirectoryOptions{
|
||||
.isIncluded = struct {
|
||||
pub fn include(entry: std.fs.Dir.Walker.Entry) bool {
|
||||
pub fn include(entry: std.fs.IterableDir.Walker.WalkerEntry) bool {
|
||||
return std.mem.endsWith(u8, entry.basename, ".json");
|
||||
}
|
||||
}.include,
|
||||
.isExcluded = struct {
|
||||
pub fn exclude(entry: std.fs.Dir.Walker.Entry) bool {
|
||||
pub fn exclude(entry: std.fs.IterableDir.Walker.WalkerEntry) bool {
|
||||
_ = entry;
|
||||
return false;
|
||||
}
|
||||
|
@ -148,14 +148,14 @@ fn calculateDigests(models_dir: std.fs.Dir, output_dir: std.fs.Dir, thread_pool:
|
|||
}));
|
||||
if (verbose) std.log.info("Model directory hash: {s}", .{model_digest orelse Hasher.hexDigest(model_hash)});
|
||||
|
||||
const output_hash = try Hasher.computeDirectoryHash(thread_pool, try output_dir.openDir(".", .{ .iterate = true }), @constCast(&Hasher.ComputeDirectoryOptions{
|
||||
const output_hash = try Hasher.computeDirectoryHash(thread_pool, try output_dir.openIterableDir(".", .{}), @constCast(&Hasher.ComputeDirectoryOptions{
|
||||
.isIncluded = struct {
|
||||
pub fn include(entry: std.fs.Dir.Walker.Entry) bool {
|
||||
pub fn include(entry: std.fs.IterableDir.Walker.WalkerEntry) bool {
|
||||
return std.mem.endsWith(u8, entry.basename, ".zig");
|
||||
}
|
||||
}.include,
|
||||
.isExcluded = struct {
|
||||
pub fn exclude(entry: std.fs.Dir.Walker.Entry) bool {
|
||||
pub fn exclude(entry: std.fs.IterableDir.Walker.WalkerEntry) bool {
|
||||
_ = entry;
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -30,13 +30,12 @@ pub fn build(b: *std.Build) void {
|
|||
// .optimize = optimize,
|
||||
// });
|
||||
// exe.addModule("smithy", smithy_dep.module("smithy"));
|
||||
const aws_dep = b.dependency("aws-zig", .{
|
||||
const aws_dep = b.dependency("aws", .{
|
||||
// These are the two arguments to the dependency. It expects a target and optimization level.
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
const aws_module = aws_dep.module("aws");
|
||||
exe.root_module.addImport("aws", aws_module);
|
||||
exe.addModule("aws", aws_dep.module("aws"));
|
||||
// This declares intent for the executable to be installed into the
|
||||
// standard location when the user invokes the "install" step (the default
|
||||
// step when running `zig build`).
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
.{
|
||||
.name = "myapp",
|
||||
.version = "0.0.1",
|
||||
.paths = .{""},
|
||||
|
||||
.dependencies = .{
|
||||
.smithy = .{
|
||||
.url = "https://git.lerch.org/lobo/smithy/archive/1e534201c4df5ea4f615faeedc69d414adbec0b1.tar.gz",
|
||||
.hash = "1220af63ae0498010004af79936cedf3fe6702f516daab77ebbd97a274eba1b42aad",
|
||||
.aws = .{
|
||||
.url = "https://git.lerch.org/api/packages/lobo/generic/aws-sdk-with-models/d08d0f338fb86f7d679a998ff4f65f4e2d0db595/d08d0f338fb86f7d679a998ff4f65f4e2d0db595-with-models.tar.gz",
|
||||
.hash = "1220c8871d93592680ea2dcc88cc52fb4f0effabeed0584d2a5c54f93825741b7c66",
|
||||
},
|
||||
.@"aws-zig" = .{
|
||||
.url = "https://git.lerch.org/api/packages/lobo/generic/aws-sdk-with-models/b1b2a6cc7a6104f5e1f6ee4cae33e6ef3ed2ec1c/b1b2a6cc7a6104f5e1f6ee4cae33e6ef3ed2ec1c-with-models.tar.gz",
|
||||
.hash = "12203d7c7aea80fd5e1f892b4928b89b2c70d5688cf1843e0d03e5af79632c5a9146",
|
||||
.smithy = .{
|
||||
.url = "https://git.lerch.org/lobo/smithy/archive/41b61745d25a65817209dd5dddbb5f9b66896a99.tar.gz",
|
||||
.hash = "122087deb0ae309b2258d59b40d82fe5921fdfc35b420bb59033244851f7f276fa34",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
const std = @import("std");
|
||||
const aws = @import("aws");
|
||||
|
||||
pub const std_options: std.Options = .{
|
||||
.log_level = .info,
|
||||
pub const std_options = struct {
|
||||
pub const log_level: std.log.Level = .info;
|
||||
|
||||
// usually log_level is enough, but log_scope_levels can be used
|
||||
// for finer grained control
|
||||
.log_scope_levels = &[_]std.log.ScopeLevel{
|
||||
pub const log_scope_levels = &[_]std.log.ScopeLevel{
|
||||
.{ .scope = .awshttp, .level = .warn },
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
pub fn main() anyerror!void {
|
||||
|
|
305
src/aws.zig
305
src/aws.zig
|
@ -1,4 +1,3 @@
|
|||
const builtin = @import("builtin");
|
||||
const std = @import("std");
|
||||
|
||||
const awshttp = @import("aws_http.zig");
|
||||
|
@ -32,7 +31,7 @@ pub const services = servicemodel.services;
|
|||
pub const Services = servicemodel.Services;
|
||||
|
||||
pub const ClientOptions = struct {
|
||||
proxy: ?std.http.Client.Proxy = null,
|
||||
proxy: ?std.http.Client.HttpProxy = null,
|
||||
};
|
||||
pub const Client = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
|
@ -227,7 +226,7 @@ pub fn Request(comptime request_action: anytype) type {
|
|||
defer buffer.deinit();
|
||||
const writer = buffer.writer();
|
||||
try url.encode(options.client.allocator, request, writer, .{
|
||||
.field_name_transformer = queryFieldTransformer,
|
||||
.field_name_transformer = &queryFieldTransformer,
|
||||
});
|
||||
const continuation = if (buffer.items.len > 0) "&" else "";
|
||||
|
||||
|
@ -735,7 +734,7 @@ fn headersFor(allocator: std.mem.Allocator, request: anytype) ![]awshttp.Header
|
|||
return headers.toOwnedSlice();
|
||||
}
|
||||
|
||||
fn freeHeadersFor(allocator: std.mem.Allocator, request: anytype, headers: []const awshttp.Header) void {
|
||||
fn freeHeadersFor(allocator: std.mem.Allocator, request: anytype, headers: []awshttp.Header) void {
|
||||
if (!@hasDecl(@TypeOf(request), "http_header")) return;
|
||||
const http_header = @TypeOf(request).http_header;
|
||||
const fields = std.meta.fields(@TypeOf(http_header));
|
||||
|
@ -762,7 +761,7 @@ fn firstJsonKey(data: []const u8) []const u8 {
|
|||
log.debug("First json key: {s}", .{key});
|
||||
return key;
|
||||
}
|
||||
fn isJsonResponse(headers: []const awshttp.Header) !bool {
|
||||
fn isJsonResponse(headers: []awshttp.Header) !bool {
|
||||
// EC2 ignores our accept type, but technically query protocol only
|
||||
// returns XML as well. So, we'll ignore the protocol here and just
|
||||
// look at the return type
|
||||
|
@ -827,7 +826,7 @@ fn ServerResponse(comptime action: anytype) type {
|
|||
};
|
||||
const Result = @Type(.{
|
||||
.Struct = .{
|
||||
.layout = .auto,
|
||||
.layout = .Auto,
|
||||
.fields = &[_]std.builtin.Type.StructField{
|
||||
.{
|
||||
.name = action.action_name ++ "Result",
|
||||
|
@ -850,7 +849,7 @@ fn ServerResponse(comptime action: anytype) type {
|
|||
});
|
||||
return @Type(.{
|
||||
.Struct = .{
|
||||
.layout = .auto,
|
||||
.layout = .Auto,
|
||||
.fields = &[_]std.builtin.Type.StructField{
|
||||
.{
|
||||
.name = action.action_name ++ "Response",
|
||||
|
@ -920,7 +919,8 @@ fn safeFree(allocator: std.mem.Allocator, obj: anytype) void {
|
|||
else => {},
|
||||
}
|
||||
}
|
||||
fn queryFieldTransformer(allocator: std.mem.Allocator, field_name: []const u8) anyerror![]const u8 {
|
||||
fn queryFieldTransformer(allocator: std.mem.Allocator, field_name: []const u8, options: url.EncodingOptions) anyerror![]const u8 {
|
||||
_ = options;
|
||||
return try case.snakeToPascal(allocator, field_name);
|
||||
}
|
||||
|
||||
|
@ -1363,17 +1363,16 @@ test {
|
|||
}
|
||||
const TestOptions = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
arena: ?*std.heap.ArenaAllocator = null,
|
||||
server_port: ?u16 = null,
|
||||
server_remaining_requests: usize = 1,
|
||||
server_response: []const u8 = "unset",
|
||||
server_response_status: std.http.Status = .ok,
|
||||
server_response_headers: []const std.http.Header = &.{},
|
||||
server_response_headers: [][2][]const u8 = &[_][2][]const u8{},
|
||||
server_response_transfer_encoding: ?std.http.TransferEncoding = null,
|
||||
request_body: []u8 = "",
|
||||
request_method: std.http.Method = undefined,
|
||||
request_target: []const u8 = undefined,
|
||||
request_headers: []std.http.Header = undefined,
|
||||
request_headers: *std.http.Headers = undefined,
|
||||
test_server_runtime_uri: ?[]u8 = null,
|
||||
server_ready: bool = false,
|
||||
requests_processed: usize = 0,
|
||||
|
@ -1381,7 +1380,7 @@ const TestOptions = struct {
|
|||
const Self = @This();
|
||||
|
||||
fn expectHeader(self: *Self, name: []const u8, value: []const u8) !void {
|
||||
for (self.request_headers) |h|
|
||||
for (self.request_headers.list.items) |h|
|
||||
if (std.ascii.eqlIgnoreCase(name, h.name) and
|
||||
std.mem.eql(u8, value, h.value)) return;
|
||||
return error.HeaderOrValueNotFound;
|
||||
|
@ -1392,6 +1391,17 @@ const TestOptions = struct {
|
|||
while (!self.server_ready)
|
||||
std.time.sleep(100);
|
||||
}
|
||||
|
||||
fn deinit(self: Self) void {
|
||||
if (self.requests_processed > 0) {
|
||||
self.allocator.free(self.request_body);
|
||||
self.allocator.free(self.request_target);
|
||||
self.request_headers.deinit();
|
||||
self.allocator.destroy(self.request_headers);
|
||||
}
|
||||
if (self.test_server_runtime_uri) |_|
|
||||
self.allocator.free(self.test_server_runtime_uri.?);
|
||||
}
|
||||
};
|
||||
|
||||
/// This starts a test server. We're not testing the server itself,
|
||||
|
@ -1399,19 +1409,16 @@ const TestOptions = struct {
|
|||
/// whole thing so we can just deallocate everything at once at the end,
|
||||
/// leaks be damned
|
||||
fn threadMain(options: *TestOptions) !void {
|
||||
// https://github.com/ziglang/zig/blob/d2be725e4b14c33dbd39054e33d926913eee3cd4/lib/compiler/std-docs.zig#L22-L54
|
||||
|
||||
options.arena = try options.allocator.create(std.heap.ArenaAllocator);
|
||||
options.arena.?.* = std.heap.ArenaAllocator.init(options.allocator);
|
||||
const allocator = options.arena.?.allocator();
|
||||
options.allocator = allocator;
|
||||
var server = std.http.Server.init(options.allocator, .{ .reuse_address = true });
|
||||
// defer server.deinit();
|
||||
|
||||
const address = try std.net.Address.parseIp("127.0.0.1", 0);
|
||||
var http_server = try address.listen(.{});
|
||||
options.server_port = http_server.listen_address.in.getPort();
|
||||
// TODO: remove
|
||||
try server.listen(address);
|
||||
options.server_port = server.socket.listen_address.in.getPort();
|
||||
|
||||
options.test_server_runtime_uri = try std.fmt.allocPrint(options.allocator, "http://127.0.0.1:{d}", .{options.server_port.?});
|
||||
log.debug("server listening at {s}", .{options.test_server_runtime_uri.?});
|
||||
defer server.deinit();
|
||||
log.info("starting server thread, tid {d}", .{std.Thread.getCurrentId()});
|
||||
// var arena = std.heap.ArenaAllocator.init(options.allocator);
|
||||
// defer arena.deinit();
|
||||
|
@ -1420,7 +1427,7 @@ fn threadMain(options: *TestOptions) !void {
|
|||
// when it's time to shut down
|
||||
while (options.server_remaining_requests > 0) {
|
||||
options.server_remaining_requests -= 1;
|
||||
processRequest(options, &http_server) catch |e| {
|
||||
processRequest(options, &server) catch |e| {
|
||||
log.err("Unexpected error processing request: {any}", .{e});
|
||||
if (@errorReturnTrace()) |trace| {
|
||||
std.debug.dumpStackTrace(trace.*);
|
||||
|
@ -1429,63 +1436,76 @@ fn threadMain(options: *TestOptions) !void {
|
|||
}
|
||||
}
|
||||
|
||||
fn processRequest(options: *TestOptions, net_server: *std.net.Server) !void {
|
||||
fn processRequest(options: *TestOptions, server: *std.http.Server) !void {
|
||||
options.server_ready = true;
|
||||
errdefer options.server_ready = false;
|
||||
log.debug(
|
||||
"tid {d} (server): server waiting to accept. requests remaining: {d}",
|
||||
.{ std.Thread.getCurrentId(), options.server_remaining_requests + 1 },
|
||||
);
|
||||
var connection = try net_server.accept();
|
||||
defer connection.stream.close();
|
||||
var read_buffer: [1024 * 16]u8 = undefined;
|
||||
var http_server = std.http.Server.init(connection, &read_buffer);
|
||||
while (http_server.state == .ready) {
|
||||
var request = http_server.receiveHead() catch |err| switch (err) {
|
||||
error.HttpConnectionClosing => return,
|
||||
else => {
|
||||
std.log.err("closing http connection: {s}", .{@errorName(err)});
|
||||
std.log.debug("Error occurred from this request: \n{s}", .{read_buffer[0..http_server.read_buffer_len]});
|
||||
return;
|
||||
},
|
||||
};
|
||||
try serveRequest(options, &request);
|
||||
}
|
||||
}
|
||||
|
||||
fn serveRequest(options: *TestOptions, request: *std.http.Server.Request) !void {
|
||||
var res = try server.accept(.{ .allocator = options.allocator });
|
||||
options.server_ready = false;
|
||||
defer res.deinit();
|
||||
defer if (res.headers.owned and res.headers.list.items.len > 0) res.headers.deinit();
|
||||
defer _ = res.reset();
|
||||
try res.wait(); // wait for client to send a complete request head
|
||||
|
||||
const errstr = "Internal Server Error\n";
|
||||
var errbuf: [errstr.len]u8 = undefined;
|
||||
@memcpy(&errbuf, errstr);
|
||||
var response_bytes: []const u8 = errbuf[0..];
|
||||
|
||||
options.requests_processed += 1;
|
||||
options.request_body = try (try request.reader()).readAllAlloc(options.allocator, std.math.maxInt(usize));
|
||||
options.request_method = request.head.method;
|
||||
options.request_target = try options.allocator.dupe(u8, request.head.target);
|
||||
var req_headers = std.ArrayList(std.http.Header).init(options.allocator);
|
||||
defer req_headers.deinit();
|
||||
var it = request.iterateHeaders();
|
||||
while (it.next()) |f| {
|
||||
const h = try options.allocator.create(std.http.Header);
|
||||
h.* = .{ .name = try options.allocator.dupe(u8, f.name), .value = try options.allocator.dupe(u8, f.value) };
|
||||
try req_headers.append(h.*);
|
||||
}
|
||||
options.request_headers = try req_headers.toOwnedSlice();
|
||||
if (res.request.content_length) |l|
|
||||
options.request_body = try res.reader().readAllAlloc(options.allocator, @as(usize, @intCast(l)))
|
||||
else
|
||||
options.request_body = try options.allocator.dupe(u8, "");
|
||||
options.request_method = res.request.method;
|
||||
options.request_target = try options.allocator.dupe(u8, res.request.target);
|
||||
options.request_headers = try options.allocator.create(std.http.Headers);
|
||||
options.request_headers.allocator = options.allocator;
|
||||
options.request_headers.list = .{};
|
||||
options.request_headers.index = .{};
|
||||
options.request_headers.owned = true;
|
||||
for (res.request.headers.list.items) |f|
|
||||
try options.request_headers.append(f.name, f.value);
|
||||
log.debug(
|
||||
"tid {d} (server): {d} bytes read from request",
|
||||
.{ std.Thread.getCurrentId(), options.request_body.len },
|
||||
);
|
||||
|
||||
// try response.headers.append("content-type", "text/plain");
|
||||
try request.respond(options.server_response, .{
|
||||
.status = options.server_response_status,
|
||||
.extra_headers = options.server_response_headers,
|
||||
});
|
||||
response_bytes = serve(options, &res) catch |e| brk: {
|
||||
res.status = .internal_server_error;
|
||||
// TODO: more about this particular request
|
||||
log.err("Unexpected error from executor processing request: {any}", .{e});
|
||||
if (@errorReturnTrace()) |trace| {
|
||||
std.debug.dumpStackTrace(trace.*);
|
||||
}
|
||||
break :brk "Unexpected error generating request to lambda";
|
||||
};
|
||||
if (options.server_response_transfer_encoding == null)
|
||||
res.transfer_encoding = .{ .content_length = response_bytes.len }
|
||||
else
|
||||
res.transfer_encoding = .chunked;
|
||||
|
||||
try res.do();
|
||||
_ = try res.writer().writeAll(response_bytes);
|
||||
try res.finish();
|
||||
log.debug(
|
||||
"tid {d} (server): sent response",
|
||||
.{std.Thread.getCurrentId()},
|
||||
);
|
||||
}
|
||||
|
||||
fn serve(options: *TestOptions, res: *std.http.Server.Response) ![]const u8 {
|
||||
res.status = options.server_response_status;
|
||||
for (options.server_response_headers) |h|
|
||||
try res.headers.append(h[0], h[1]);
|
||||
// try res.headers.append("content-length", try std.fmt.allocPrint(allocator, "{d}", .{server_response.len}));
|
||||
return options.server_response;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// These will replicate the tests that were in src/main.zig
|
||||
// The server_response and server_response_headers come from logs of
|
||||
|
@ -1507,10 +1527,10 @@ const TestSetup = struct {
|
|||
const signing_time =
|
||||
date.dateTimeToTimestamp(date.parseIso8601ToDateTime("20230908T170252Z") catch @compileError("Cannot parse date")) catch @compileError("Cannot parse date");
|
||||
|
||||
fn init(options: TestOptions) Self {
|
||||
fn init(allocator: std.mem.Allocator, options: TestOptions) Self {
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
.request_options = options,
|
||||
.allocator = options.allocator,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1522,10 +1542,7 @@ const TestSetup = struct {
|
|||
);
|
||||
self.started = true;
|
||||
try self.request_options.waitForReady();
|
||||
// Not sure why we're getting sprayed here, but we have an arena allocator, and this
|
||||
// is testing, so yolo
|
||||
awshttp.endpoint_override = self.request_options.test_server_runtime_uri;
|
||||
log.debug("endpoint override set to {?s}", .{awshttp.endpoint_override});
|
||||
self.creds = aws_auth.Credentials.init(
|
||||
self.allocator,
|
||||
try self.allocator.dupe(u8, "ACCESS"),
|
||||
|
@ -1546,11 +1563,9 @@ const TestSetup = struct {
|
|||
self.server_thread.join();
|
||||
}
|
||||
|
||||
fn deinit(self: *Self) void {
|
||||
if (self.request_options.arena) |a| {
|
||||
a.deinit();
|
||||
self.allocator.destroy(a);
|
||||
}
|
||||
fn deinit(self: Self) void {
|
||||
self.request_options.deinit();
|
||||
|
||||
if (!self.started) return;
|
||||
awshttp.endpoint_override = null;
|
||||
// creds.deinit(); Creds will get deinited in the course of the call. We don't want to do it twice
|
||||
|
@ -1561,15 +1576,15 @@ const TestSetup = struct {
|
|||
|
||||
test "query_no_input: sts getCallerIdentity comptime" {
|
||||
const allocator = std.testing.allocator;
|
||||
var test_harness = TestSetup.init(.{
|
||||
var test_harness = TestSetup.init(allocator, .{
|
||||
.allocator = allocator,
|
||||
.server_response =
|
||||
\\{"GetCallerIdentityResponse":{"GetCallerIdentityResult":{"Account":"123456789012","Arn":"arn:aws:iam::123456789012:user/admin","UserId":"AIDAYAM4POHXHRVANDQBQ"},"ResponseMetadata":{"RequestId":"8f0d54da-1230-40f7-b4ac-95015c4b84cd"}}}
|
||||
,
|
||||
.server_response_headers = &.{
|
||||
.{ .name = "Content-Type", .value = "application/json" },
|
||||
.{ .name = "x-amzn-RequestId", .value = "8f0d54da-1230-40f7-b4ac-95015c4b84cd" },
|
||||
},
|
||||
.server_response_headers = @constCast(&[_][2][]const u8{
|
||||
.{ "Content-Type", "application/json" },
|
||||
.{ "x-amzn-RequestId", "8f0d54da-1230-40f7-b4ac-95015c4b84cd" },
|
||||
}),
|
||||
});
|
||||
defer test_harness.deinit();
|
||||
const options = try test_harness.start();
|
||||
|
@ -1596,7 +1611,7 @@ test "query_no_input: sts getCallerIdentity comptime" {
|
|||
test "query_with_input: sts getAccessKeyInfo runtime" {
|
||||
// sqs switched from query to json in aws sdk for go v2 commit f5a08768ef820ff5efd62a49ba50c61c9ca5dbcb
|
||||
const allocator = std.testing.allocator;
|
||||
var test_harness = TestSetup.init(.{
|
||||
var test_harness = TestSetup.init(allocator, .{
|
||||
.allocator = allocator,
|
||||
.server_response =
|
||||
\\<GetAccessKeyInfoResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
|
||||
|
@ -1608,10 +1623,10 @@ test "query_with_input: sts getAccessKeyInfo runtime" {
|
|||
\\ </ResponseMetadata>
|
||||
\\</GetAccessKeyInfoResponse>
|
||||
,
|
||||
.server_response_headers = &.{
|
||||
.{ .name = "Content-Type", .value = "text/xml" },
|
||||
.{ .name = "x-amzn-RequestId", .value = "ec85bf29-1ef0-459a-930e-6446dd14a286" },
|
||||
},
|
||||
.server_response_headers = @constCast(&[_][2][]const u8{
|
||||
.{ "Content-Type", "text/xml" },
|
||||
.{ "x-amzn-RequestId", "ec85bf29-1ef0-459a-930e-6446dd14a286" },
|
||||
}),
|
||||
});
|
||||
defer test_harness.deinit();
|
||||
const options = try test_harness.start();
|
||||
|
@ -1634,15 +1649,15 @@ test "query_with_input: sts getAccessKeyInfo runtime" {
|
|||
}
|
||||
test "json_1_0_query_with_input: dynamodb listTables runtime" {
|
||||
const allocator = std.testing.allocator;
|
||||
var test_harness = TestSetup.init(.{
|
||||
var test_harness = TestSetup.init(allocator, .{
|
||||
.allocator = allocator,
|
||||
.server_response =
|
||||
\\{"LastEvaluatedTableName":"Customer","TableNames":["Customer"]}
|
||||
,
|
||||
.server_response_headers = &.{
|
||||
.{ .name = "Content-Type", .value = "application/json" },
|
||||
.{ .name = "x-amzn-RequestId", .value = "QBI72OUIN8U9M9AG6PCSADJL4JVV4KQNSO5AEMVJF66Q9ASUAAJG" },
|
||||
},
|
||||
.server_response_headers = @constCast(&[_][2][]const u8{
|
||||
.{ "Content-Type", "application/json" },
|
||||
.{ "x-amzn-RequestId", "QBI72OUIN8U9M9AG6PCSADJL4JVV4KQNSO5AEMVJF66Q9ASUAAJG" },
|
||||
}),
|
||||
});
|
||||
defer test_harness.deinit();
|
||||
const options = try test_harness.start();
|
||||
|
@ -1670,15 +1685,15 @@ test "json_1_0_query_with_input: dynamodb listTables runtime" {
|
|||
|
||||
test "json_1_0_query_no_input: dynamodb listTables runtime" {
|
||||
const allocator = std.testing.allocator;
|
||||
var test_harness = TestSetup.init(.{
|
||||
var test_harness = TestSetup.init(allocator, .{
|
||||
.allocator = allocator,
|
||||
.server_response =
|
||||
\\{"AccountMaxReadCapacityUnits":80000,"AccountMaxWriteCapacityUnits":80000,"TableMaxReadCapacityUnits":40000,"TableMaxWriteCapacityUnits":40000}
|
||||
,
|
||||
.server_response_headers = &.{
|
||||
.{ .name = "Content-Type", .value = "application/json" },
|
||||
.{ .name = "x-amzn-RequestId", .value = "QBI72OUIN8U9M9AG6PCSADJL4JVV4KQNSO5AEMVJF66Q9ASUAAJG" },
|
||||
},
|
||||
.server_response_headers = @constCast(&[_][2][]const u8{
|
||||
.{ "Content-Type", "application/json" },
|
||||
.{ "x-amzn-RequestId", "QBI72OUIN8U9M9AG6PCSADJL4JVV4KQNSO5AEMVJF66Q9ASUAAJG" },
|
||||
}),
|
||||
});
|
||||
defer test_harness.deinit();
|
||||
const options = try test_harness.start();
|
||||
|
@ -1699,15 +1714,15 @@ test "json_1_0_query_no_input: dynamodb listTables runtime" {
|
|||
}
|
||||
test "json_1_1_query_with_input: ecs listClusters runtime" {
|
||||
const allocator = std.testing.allocator;
|
||||
var test_harness = TestSetup.init(.{
|
||||
var test_harness = TestSetup.init(allocator, .{
|
||||
.allocator = allocator,
|
||||
.server_response =
|
||||
\\{"clusterArns":["arn:aws:ecs:us-west-2:550620852718:cluster/web-applicationehjaf-cluster"],"nextToken":"czE0Og=="}
|
||||
,
|
||||
.server_response_headers = &.{
|
||||
.{ .name = "Content-Type", .value = "application/json" },
|
||||
.{ .name = "x-amzn-RequestId", .value = "b2420066-ff67-4237-b782-721c4df60744" },
|
||||
},
|
||||
.server_response_headers = @constCast(&[_][2][]const u8{
|
||||
.{ "Content-Type", "application/json" },
|
||||
.{ "x-amzn-RequestId", "b2420066-ff67-4237-b782-721c4df60744" },
|
||||
}),
|
||||
});
|
||||
defer test_harness.deinit();
|
||||
const options = try test_harness.start();
|
||||
|
@ -1733,19 +1748,16 @@ test "json_1_1_query_with_input: ecs listClusters runtime" {
|
|||
try std.testing.expectEqualStrings("arn:aws:ecs:us-west-2:550620852718:cluster/web-applicationehjaf-cluster", call.response.cluster_arns.?[0]);
|
||||
}
|
||||
test "json_1_1_query_no_input: ecs listClusters runtime" {
|
||||
// const old = std.testing.log_level;
|
||||
// defer std.testing.log_level = old;
|
||||
// std.testing.log_level = .debug;
|
||||
const allocator = std.testing.allocator;
|
||||
var test_harness = TestSetup.init(.{
|
||||
var test_harness = TestSetup.init(allocator, .{
|
||||
.allocator = allocator,
|
||||
.server_response =
|
||||
\\{"clusterArns":["arn:aws:ecs:us-west-2:550620852718:cluster/web-applicationehjaf-cluster"],"nextToken":"czE0Og=="}
|
||||
,
|
||||
.server_response_headers = &.{
|
||||
.{ .name = "Content-Type", .value = "application/json" },
|
||||
.{ .name = "x-amzn-RequestId", .value = "e65322b2-0065-45f2-ba37-f822bb5ce395" },
|
||||
},
|
||||
.server_response_headers = @constCast(&[_][2][]const u8{
|
||||
.{ "Content-Type", "application/json" },
|
||||
.{ "x-amzn-RequestId", "e65322b2-0065-45f2-ba37-f822bb5ce395" },
|
||||
}),
|
||||
});
|
||||
defer test_harness.deinit();
|
||||
const options = try test_harness.start();
|
||||
|
@ -1770,15 +1782,15 @@ test "json_1_1_query_no_input: ecs listClusters runtime" {
|
|||
}
|
||||
test "rest_json_1_query_with_input: lambda listFunctions runtime" {
|
||||
const allocator = std.testing.allocator;
|
||||
var test_harness = TestSetup.init(.{
|
||||
var test_harness = TestSetup.init(allocator, .{
|
||||
.allocator = allocator,
|
||||
.server_response =
|
||||
\\{"Functions":[{"Description":"AWS CDK resource provider framework - onEvent (DevelopmentFrontendStack-g650u/com.amazonaws.cdk.custom-resources.amplify-asset-deployment-provider/amplify-asset-deployment-handler-provider)","TracingConfig":{"Mode":"PassThrough"},"VpcConfig":null,"SigningJobArn":null,"SnapStart":{"OptimizationStatus":"Off","ApplyOn":"None"},"RevisionId":"0c62fc74-a692-403d-9206-5fcbad406424","LastModified":"2023-03-01T18:13:15.704+0000","FileSystemConfigs":null,"FunctionName":"DevelopmentFrontendStack--amplifyassetdeploymentha-aZqB9IbZLIKU","Runtime":"nodejs14.x","Version":"$LATEST","PackageType":"Zip","LastUpdateStatus":null,"Layers":null,"FunctionArn":"arn:aws:lambda:us-west-2:550620852718:function:DevelopmentFrontendStack--amplifyassetdeploymentha-aZqB9IbZLIKU","KMSKeyArn":null,"MemorySize":128,"ImageConfigResponse":null,"LastUpdateStatusReason":null,"DeadLetterConfig":null,"Timeout":900,"Handler":"framework.onEvent","CodeSha256":"m4tt+M0l3p8bZvxIDj83dwGrwRW6atCfS/q8AiXCD3o=","Role":"arn:aws:iam::550620852718:role/DevelopmentFrontendStack-amplifyassetdeploymentha-1782JF7WAPXZ3","SigningProfileVersionArn":null,"MasterArn":null,"RuntimeVersionConfig":null,"CodeSize":4307,"State":null,"StateReason":null,"Environment":{"Variables":{"USER_ON_EVENT_FUNCTION_ARN":"arn:aws:lambda:us-west-2:550620852718:function:DevelopmentFrontendStack--amplifyassetdeploymenton-X9iZJSCSPYDH","WAITER_STATE_MACHINE_ARN":"arn:aws:states:us-west-2:550620852718:stateMachine:amplifyassetdeploymenthandlerproviderwaiterstatemachineB3C2FCBE-Ltggp5wBcHWO","USER_IS_COMPLETE_FUNCTION_ARN":"arn:aws:lambda:us-west-2:550620852718:function:DevelopmentFrontendStack--amplifyassetdeploymentis-jaHopLrSSARV"},"Error":null},"EphemeralStorage":{"Size":512},"StateReasonCode":null,"LastUpdateStatusReasonCode":null,"Architectures":["x86_64"]}],"NextMarker":"lslTXFcbLQKkb0vP9Kgh5hUL7C3VghELNGbWgZfxrRCk3eiDRMkct7D8EmptWfHSXssPdS7Bo66iQPTMpVOHZgANewpgGgFGGr4pVjd6VgLUO6qPe2EMAuNDBjUTxm8z6N28yhlUwEmKbrAV/m0k5qVzizwoxFwvyruMbuMx9kADFACSslcabxXl3/jDI4rfFnIsUVdzTLBgPF1hzwrE1f3lcdkBvUp+QgY+Pn3w5QuJmwsp/di8COzFemY89GgOHbLNqsrBsgR/ee2eXoJp0ZkKM4EcBK3HokqBzefLfgR02PnfNOdXwqTlhkSPW0TKiKGIYu3Bw7lSNrLd+q3+wEr7ZakqOQf0BVo3FMRhMHlVYgwUJzwi3ActyH2q6fuqGG1sS0B8Oa/prUpe5fmp3VaA3WpazioeHtrKF78JwCi6/nfQsrj/8ZtXGQOxlwEgvT1CIUaF+CdHY3biezrK0tRZNpkCtHnkPtF9lq2U7+UiKXSW9yzxT8P2b0M/Qh4IVdnw4rncQK/doYriAeOdrs1wjMEJnHWq9lAaEyipoxYcVr/z5+yaC6Gwxdg45p9X1vIAaYMf6IZxyFuua43SYi0Ls+IBk4VvpR2io7T0dCxHAr3WAo3D2dm0y8OsbM59"}
|
||||
,
|
||||
.server_response_headers = &.{
|
||||
.{ .name = "Content-Type", .value = "application/json" },
|
||||
.{ .name = "x-amzn-RequestId", .value = "c4025199-226f-4a16-bb1f-48618e9d2ea6" },
|
||||
},
|
||||
.server_response_headers = @constCast(&[_][2][]const u8{
|
||||
.{ "Content-Type", "application/json" },
|
||||
.{ "x-amzn-RequestId", "c4025199-226f-4a16-bb1f-48618e9d2ea6" },
|
||||
}),
|
||||
});
|
||||
defer test_harness.deinit();
|
||||
const options = try test_harness.start();
|
||||
|
@ -1804,13 +1816,13 @@ test "rest_json_1_query_with_input: lambda listFunctions runtime" {
|
|||
}
|
||||
test "rest_json_1_query_no_input: lambda listFunctions runtime" {
|
||||
const allocator = std.testing.allocator;
|
||||
var test_harness = TestSetup.init(.{
|
||||
var test_harness = TestSetup.init(allocator, .{
|
||||
.allocator = allocator,
|
||||
.server_response = @embedFile("test_rest_json_1_query_no_input.response"),
|
||||
.server_response_headers = &.{
|
||||
.{ .name = "Content-Type", .value = "application/json" },
|
||||
.{ .name = "x-amzn-RequestId", .value = "b2aad11f-36fc-4d0d-ae92-fe0167fb0f40" },
|
||||
},
|
||||
.server_response_headers = @constCast(&[_][2][]const u8{
|
||||
.{ "Content-Type", "application/json" },
|
||||
.{ "x-amzn-RequestId", "b2aad11f-36fc-4d0d-ae92-fe0167fb0f40" },
|
||||
}),
|
||||
});
|
||||
defer test_harness.deinit();
|
||||
const options = try test_harness.start();
|
||||
|
@ -1838,14 +1850,14 @@ test "rest_json_1_query_no_input: lambda listFunctions runtime" {
|
|||
}
|
||||
test "rest_json_1_work_with_lambda: lambda tagResource (only), to excercise zig issue 17015" {
|
||||
const allocator = std.testing.allocator;
|
||||
var test_harness = TestSetup.init(.{
|
||||
var test_harness = TestSetup.init(allocator, .{
|
||||
.allocator = allocator,
|
||||
.server_response = "",
|
||||
.server_response_status = .no_content,
|
||||
.server_response_headers = &.{
|
||||
.{ .name = "Content-Type", .value = "application/json" },
|
||||
.{ .name = "x-amzn-RequestId", .value = "a521e152-6e32-4e67-9fb3-abc94e34551b" },
|
||||
},
|
||||
.server_response_headers = @constCast(&[_][2][]const u8{
|
||||
.{ "Content-Type", "application/json" },
|
||||
.{ "x-amzn-RequestId", "a521e152-6e32-4e67-9fb3-abc94e34551b" },
|
||||
}),
|
||||
});
|
||||
defer test_harness.deinit();
|
||||
const options = try test_harness.start();
|
||||
|
@ -1874,13 +1886,13 @@ test "rest_json_1_work_with_lambda: lambda tagResource (only), to excercise zig
|
|||
}
|
||||
test "ec2_query_no_input: EC2 describe regions" {
|
||||
const allocator = std.testing.allocator;
|
||||
var test_harness = TestSetup.init(.{
|
||||
var test_harness = TestSetup.init(allocator, .{
|
||||
.allocator = allocator,
|
||||
.server_response = @embedFile("test_ec2_query_no_input.response"),
|
||||
.server_response_headers = &.{
|
||||
.{ .name = "Content-Type", .value = "text/xml;charset=UTF-8" },
|
||||
.{ .name = "x-amzn-RequestId", .value = "4cdbdd69-800c-49b5-8474-ae4c17709782" },
|
||||
},
|
||||
.server_response_headers = @constCast(&[_][2][]const u8{
|
||||
.{ "Content-Type", "text/xml;charset=UTF-8" },
|
||||
.{ "x-amzn-RequestId", "4cdbdd69-800c-49b5-8474-ae4c17709782" },
|
||||
}),
|
||||
.server_response_transfer_encoding = .chunked,
|
||||
});
|
||||
defer test_harness.deinit();
|
||||
|
@ -1899,21 +1911,15 @@ test "ec2_query_no_input: EC2 describe regions" {
|
|||
try std.testing.expectEqualStrings("4cdbdd69-800c-49b5-8474-ae4c17709782", call.response_metadata.request_id);
|
||||
try std.testing.expectEqual(@as(usize, 17), call.response.regions.?.len);
|
||||
}
|
||||
// LLVM hates this test. Depending on the platform, it will consume all memory
|
||||
// on the compilation host. Windows x86_64 and Linux riscv64 seem to be a problem so far
|
||||
// riscv64-linux also seems to have another problem with LLVM basically infinitely
|
||||
// doing something. My guess is the @embedFile is freaking out LLVM
|
||||
test "ec2_query_with_input: EC2 describe instances" {
|
||||
if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows) return error.SkipZigTest;
|
||||
if (builtin.cpu.arch == .riscv64 and builtin.os.tag == .linux) return error.SkipZigTest;
|
||||
const allocator = std.testing.allocator;
|
||||
var test_harness = TestSetup.init(.{
|
||||
var test_harness = TestSetup.init(allocator, .{
|
||||
.allocator = allocator,
|
||||
.server_response = @embedFile("test_ec2_query_with_input.response"),
|
||||
.server_response_headers = &.{
|
||||
.{ .name = "Content-Type", .value = "text/xml;charset=UTF-8" },
|
||||
.{ .name = "x-amzn-RequestId", .value = "150a14cc-785d-476f-a4c9-2aa4d03b14e2" },
|
||||
},
|
||||
.server_response_headers = @constCast(&[_][2][]const u8{
|
||||
.{ "Content-Type", "text/xml;charset=UTF-8" },
|
||||
.{ "x-amzn-RequestId", "150a14cc-785d-476f-a4c9-2aa4d03b14e2" },
|
||||
}),
|
||||
});
|
||||
defer test_harness.deinit();
|
||||
const options = try test_harness.start();
|
||||
|
@ -1937,15 +1943,15 @@ test "ec2_query_with_input: EC2 describe instances" {
|
|||
}
|
||||
test "rest_xml_no_input: S3 list buckets" {
|
||||
const allocator = std.testing.allocator;
|
||||
var test_harness = TestSetup.init(.{
|
||||
var test_harness = TestSetup.init(allocator, .{
|
||||
.allocator = allocator,
|
||||
.server_response =
|
||||
\\<ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Owner><ID>3367189aa775bd98da38e55093705f2051443c1e775fc0971d6d77387a47c8d0</ID><DisplayName>emilerch+sub1</DisplayName></Owner><Buckets><Bucket><Name>550620852718-backup</Name><CreationDate>2020-06-17T16:26:51.000Z</CreationDate></Bucket><Bucket><Name>amplify-letmework-staging-185741-deployment</Name><CreationDate>2023-03-10T18:57:49.000Z</CreationDate></Bucket><Bucket><Name>aws-cloudtrail-logs-550620852718-224022a7</Name><CreationDate>2021-06-21T18:32:44.000Z</CreationDate></Bucket><Bucket><Name>aws-sam-cli-managed-default-samclisourcebucket-1gy0z00mj47xe</Name><CreationDate>2021-10-05T16:38:07.000Z</CreationDate></Bucket><Bucket><Name>awsomeprojectstack-pipelineartifactsbucketaea9a05-1uzwo6c86ecr</Name><CreationDate>2021-10-05T22:55:09.000Z</CreationDate></Bucket><Bucket><Name>cdk-hnb659fds-assets-550620852718-us-west-2</Name><CreationDate>2023-02-28T21:49:36.000Z</CreationDate></Bucket><Bucket><Name>cf-templates-12iy6putgdxtk-us-west-2</Name><CreationDate>2020-06-26T02:31:59.000Z</CreationDate></Bucket><Bucket><Name>codepipeline-us-west-2-46714083637</Name><CreationDate>2021-09-14T18:43:07.000Z</CreationDate></Bucket><Bucket><Name>elasticbeanstalk-us-west-2-550620852718</Name><CreationDate>2022-04-15T16:22:42.000Z</CreationDate></Bucket><Bucket><Name>lobo-west</Name><CreationDate>2021-06-21T17:17:22.000Z</CreationDate></Bucket><Bucket><Name>lobo-west-2</Name><CreationDate>2021-11-19T20:12:31.000Z</CreationDate></Bucket><Bucket><Name>logging-backup-550620852718-us-east-2</Name><CreationDate>2022-05-29T21:55:16.000Z</CreationDate></Bucket><Bucket><Name>mysfitszj3t6webstack-hostingbucketa91a61fe-1ep3ezkgwpxr0</Name><CreationDate>2023-03-01T04:53:55.000Z</CreationDate></Bucket></Buckets></ListAllMyBucketsResult>
|
||||
,
|
||||
.server_response_headers = &.{
|
||||
.{ .name = "Content-Type", .value = "application/xml" },
|
||||
.{ .name = "x-amzn-RequestId", .value = "9PEYBAZ9J7TPRX43" },
|
||||
},
|
||||
.server_response_headers = @constCast(&[_][2][]const u8{
|
||||
.{ "Content-Type", "application/xml" },
|
||||
.{ "x-amzn-RequestId", "9PEYBAZ9J7TPRX43" },
|
||||
}),
|
||||
});
|
||||
defer test_harness.deinit();
|
||||
const options = try test_harness.start();
|
||||
|
@ -1968,15 +1974,15 @@ test "rest_xml_no_input: S3 list buckets" {
|
|||
}
|
||||
test "rest_xml_anything_but_s3: CloudFront list key groups" {
|
||||
const allocator = std.testing.allocator;
|
||||
var test_harness = TestSetup.init(.{
|
||||
var test_harness = TestSetup.init(allocator, .{
|
||||
.allocator = allocator,
|
||||
.server_response =
|
||||
\\{"Items":null,"MaxItems":100,"NextMarker":null,"Quantity":0}
|
||||
,
|
||||
.server_response_headers = &.{
|
||||
.{ .name = "Content-Type", .value = "application/json" },
|
||||
.{ .name = "x-amzn-RequestId", .value = "d3382082-5291-47a9-876b-8df3accbb7ea" },
|
||||
},
|
||||
.server_response_headers = @constCast(&[_][2][]const u8{
|
||||
.{ "Content-Type", "application/json" },
|
||||
.{ "x-amzn-RequestId", "d3382082-5291-47a9-876b-8df3accbb7ea" },
|
||||
}),
|
||||
});
|
||||
defer test_harness.deinit();
|
||||
const options = try test_harness.start();
|
||||
|
@ -1994,16 +2000,16 @@ test "rest_xml_anything_but_s3: CloudFront list key groups" {
|
|||
}
|
||||
test "rest_xml_with_input: S3 put object" {
|
||||
const allocator = std.testing.allocator;
|
||||
var test_harness = TestSetup.init(.{
|
||||
var test_harness = TestSetup.init(allocator, .{
|
||||
.allocator = allocator,
|
||||
.server_response = "",
|
||||
.server_response_headers = &.{
|
||||
.server_response_headers = @constCast(&[_][2][]const u8{
|
||||
// .{ "Content-Type", "application/xml" },
|
||||
.{ .name = "x-amzn-RequestId", .value = "9PEYBAZ9J7TPRX43" },
|
||||
.{ .name = "x-amz-id-2", .value = "jdRDo30t7Ge9lf6F+4WYpg+YKui8z0mz2+rwinL38xDZzvloJqrmpCAiKG375OSvHA9OBykJS44=" },
|
||||
.{ .name = "x-amz-server-side-encryption", .value = "AES256" },
|
||||
.{ .name = "ETag", .value = "37b51d194a7513e45b56f6524f2d51f2" },
|
||||
},
|
||||
.{ "x-amzn-RequestId", "9PEYBAZ9J7TPRX43" },
|
||||
.{ "x-amz-id-2", "jdRDo30t7Ge9lf6F+4WYpg+YKui8z0mz2+rwinL38xDZzvloJqrmpCAiKG375OSvHA9OBykJS44=" },
|
||||
.{ "x-amz-server-side-encryption", "AES256" },
|
||||
.{ "ETag", "37b51d194a7513e45b56f6524f2d51f2" },
|
||||
}),
|
||||
});
|
||||
defer test_harness.deinit();
|
||||
const options = try test_harness.start();
|
||||
|
@ -2012,6 +2018,7 @@ test "rest_xml_with_input: S3 put object" {
|
|||
.client = options.client,
|
||||
.signing_time = TestSetup.signing_time,
|
||||
};
|
||||
// std.testing.log_level = .debug;
|
||||
const result = try Request(services.s3.put_object).call(.{
|
||||
.bucket = "mysfitszj3t6webstack-hostingbucketa91a61fe-1ep3ezkgwpxr0",
|
||||
.key = "i/am/a/teapot/foo",
|
||||
|
@ -2019,7 +2026,7 @@ test "rest_xml_with_input: S3 put object" {
|
|||
.body = "bar",
|
||||
.storage_class = "STANDARD",
|
||||
}, s3opts);
|
||||
for (test_harness.request_options.request_headers) |header| {
|
||||
for (test_harness.request_options.request_headers.list.items) |header| {
|
||||
std.log.info("Request header: {s}: {s}", .{ header.name, header.value });
|
||||
}
|
||||
std.log.info("PutObject Request id: {s}", .{result.response_metadata.request_id});
|
||||
|
|
|
@ -122,22 +122,29 @@ fn getContainerCredentials(allocator: std.mem.Allocator) !?auth.Credentials {
|
|||
const container_uri = try std.fmt.allocPrint(allocator, "http://169.254.170.2{s}", .{container_relative_uri});
|
||||
defer allocator.free(container_uri);
|
||||
|
||||
var empty_headers = std.http.Headers.init(allocator);
|
||||
defer empty_headers.deinit();
|
||||
var cl = std.http.Client{ .allocator = allocator };
|
||||
defer cl.deinit(); // I don't belive connection pooling would help much here as it's non-ssl and local
|
||||
var resp_payload = std.ArrayList(u8).init(allocator);
|
||||
defer resp_payload.deinit();
|
||||
const req = try cl.fetch(.{
|
||||
.location = .{ .url = container_uri },
|
||||
.response_storage = .{ .dynamic = &resp_payload },
|
||||
});
|
||||
if (req.status != .ok and req.status != .not_found) {
|
||||
log.warn("Bad status code received from container credentials endpoint: {}", .{@intFromEnum(req.status)});
|
||||
var req = try cl.request(.GET, try std.Uri.parse(container_uri), empty_headers, .{});
|
||||
defer req.deinit();
|
||||
try req.start();
|
||||
try req.wait();
|
||||
if (req.response.status != .ok and req.response.status != .not_found) {
|
||||
log.warn("Bad status code received from container credentials endpoint: {}", .{@intFromEnum(req.response.status)});
|
||||
return null;
|
||||
}
|
||||
if (req.status == .not_found) return null;
|
||||
if (req.response.status == .not_found) return null;
|
||||
if (req.response.content_length == null or req.response.content_length.? == 0) return null;
|
||||
|
||||
log.debug("Read {d} bytes from container credentials endpoint", .{resp_payload.items.len});
|
||||
if (resp_payload.items.len == 0) return null;
|
||||
var resp_payload = try std.ArrayList(u8).initCapacity(allocator, @intCast(req.response.content_length.?));
|
||||
defer resp_payload.deinit();
|
||||
try resp_payload.resize(@intCast(req.response.content_length.?));
|
||||
const response_data = try resp_payload.toOwnedSlice();
|
||||
defer allocator.free(response_data);
|
||||
_ = try req.readAll(response_data);
|
||||
log.debug("Read {d} bytes from container credentials endpoint", .{response_data.len});
|
||||
if (response_data.len == 0) return null;
|
||||
|
||||
const CredsResponse = struct {
|
||||
AccessKeyId: []const u8,
|
||||
|
@ -147,8 +154,8 @@ fn getContainerCredentials(allocator: std.mem.Allocator) !?auth.Credentials {
|
|||
Token: []const u8,
|
||||
};
|
||||
const creds_response = blk: {
|
||||
const res = std.json.parseFromSlice(CredsResponse, allocator, resp_payload.items, .{}) catch |e| {
|
||||
log.err("Unexpected Json response from container credentials endpoint: {s}", .{resp_payload.items});
|
||||
const res = std.json.parseFromSlice(CredsResponse, allocator, response_data, .{}) catch |e| {
|
||||
log.err("Unexpected Json response from container credentials endpoint: {s}", .{response_data});
|
||||
log.err("Error parsing json: {}", .{e});
|
||||
if (@errorReturnTrace()) |trace| {
|
||||
std.debug.dumpStackTrace(trace.*);
|
||||
|
@ -175,27 +182,28 @@ fn getImdsv2Credentials(allocator: std.mem.Allocator) !?auth.Credentials {
|
|||
defer cl.deinit(); // I don't belive connection pooling would help much here as it's non-ssl and local
|
||||
// Get token
|
||||
{
|
||||
var resp_payload = std.ArrayList(u8).init(allocator);
|
||||
defer resp_payload.deinit();
|
||||
const req = try cl.fetch(.{
|
||||
.method = .PUT,
|
||||
.location = .{ .url = "http://169.254.169.254/latest/api/token" },
|
||||
.extra_headers = &[_]std.http.Header{
|
||||
.{ .name = "X-aws-ec2-metadata-token-ttl-seconds", .value = "21600" },
|
||||
},
|
||||
.response_storage = .{ .dynamic = &resp_payload },
|
||||
});
|
||||
if (req.status != .ok) {
|
||||
log.warn("Bad status code received from IMDS v2: {}", .{@intFromEnum(req.status)});
|
||||
var headers = std.http.Headers.init(allocator);
|
||||
defer headers.deinit();
|
||||
try headers.append("X-aws-ec2-metadata-token-ttl-seconds", "21600");
|
||||
var req = try cl.request(.PUT, try std.Uri.parse("http://169.254.169.254/latest/api/token"), headers, .{});
|
||||
defer req.deinit();
|
||||
try req.start();
|
||||
try req.wait();
|
||||
if (req.response.status != .ok) {
|
||||
log.warn("Bad status code received from IMDS v2: {}", .{@intFromEnum(req.response.status)});
|
||||
return null;
|
||||
}
|
||||
if (resp_payload.items.len == 0) {
|
||||
if (req.response.content_length == null or req.response.content_length == 0) {
|
||||
log.warn("Unexpected zero response from IMDS v2", .{});
|
||||
return null;
|
||||
}
|
||||
|
||||
var resp_payload = try std.ArrayList(u8).initCapacity(allocator, @intCast(req.response.content_length.?));
|
||||
defer resp_payload.deinit();
|
||||
try resp_payload.resize(@intCast(req.response.content_length.?));
|
||||
token = try resp_payload.toOwnedSlice();
|
||||
errdefer if (token) |t| allocator.free(t);
|
||||
_ = try req.readAll(token.?);
|
||||
}
|
||||
std.debug.assert(token != null);
|
||||
log.debug("Got token from IMDSv2: {s}", .{token.?});
|
||||
|
@ -216,26 +224,28 @@ fn getImdsRoleName(allocator: std.mem.Allocator, client: *std.http.Client, imds_
|
|||
// "InstanceProfileArn" : "arn:aws:iam::550620852718:instance-profile/ec2-dev",
|
||||
// "InstanceProfileId" : "AIPAYAM4POHXCFNKZ7HU2"
|
||||
// }
|
||||
var resp_payload = std.ArrayList(u8).init(allocator);
|
||||
defer resp_payload.deinit();
|
||||
const req = try client.fetch(.{
|
||||
.method = .GET,
|
||||
.location = .{ .url = "http://169.254.169.254/latest/meta-data/iam/info" },
|
||||
.extra_headers = &[_]std.http.Header{
|
||||
.{ .name = "X-aws-ec2-metadata-token", .value = imds_token },
|
||||
},
|
||||
.response_storage = .{ .dynamic = &resp_payload },
|
||||
});
|
||||
var headers = std.http.Headers.init(allocator);
|
||||
defer headers.deinit();
|
||||
try headers.append("X-aws-ec2-metadata-token", imds_token);
|
||||
|
||||
if (req.status != .ok and req.status != .not_found) {
|
||||
log.warn("Bad status code received from IMDS iam endpoint: {}", .{@intFromEnum(req.status)});
|
||||
var req = try client.request(.GET, try std.Uri.parse("http://169.254.169.254/latest/meta-data/iam/info"), headers, .{});
|
||||
defer req.deinit();
|
||||
|
||||
try req.start();
|
||||
try req.wait();
|
||||
|
||||
if (req.response.status != .ok and req.response.status != .not_found) {
|
||||
log.warn("Bad status code received from IMDS iam endpoint: {}", .{@intFromEnum(req.response.status)});
|
||||
return null;
|
||||
}
|
||||
if (req.status == .not_found) return null;
|
||||
if (resp_payload.items.len == 0) {
|
||||
if (req.response.status == .not_found) return null;
|
||||
if (req.response.content_length == null or req.response.content_length.? == 0) {
|
||||
log.warn("Unexpected empty response from IMDS endpoint post token", .{});
|
||||
return null;
|
||||
}
|
||||
const resp = try allocator.alloc(u8, @intCast(req.response.content_length.?));
|
||||
defer allocator.free(resp);
|
||||
_ = try req.readAll(resp);
|
||||
|
||||
const ImdsResponse = struct {
|
||||
Code: []const u8,
|
||||
|
@ -243,8 +253,8 @@ fn getImdsRoleName(allocator: std.mem.Allocator, client: *std.http.Client, imds_
|
|||
InstanceProfileArn: []const u8,
|
||||
InstanceProfileId: []const u8,
|
||||
};
|
||||
const imds_response = std.json.parseFromSlice(ImdsResponse, allocator, resp_payload.items, .{}) catch |e| {
|
||||
log.err("Unexpected Json response from IMDS endpoint: {s}", .{resp_payload.items});
|
||||
const imds_response = std.json.parseFromSlice(ImdsResponse, allocator, resp, .{}) catch |e| {
|
||||
log.err("Unexpected Json response from IMDS endpoint: {s}", .{resp});
|
||||
log.err("Error parsing json: {}", .{e});
|
||||
if (@errorReturnTrace()) |trace| {
|
||||
std.debug.dumpStackTrace(trace.*);
|
||||
|
@ -264,28 +274,31 @@ fn getImdsRoleName(allocator: std.mem.Allocator, client: *std.http.Client, imds_
|
|||
|
||||
/// Note - this internal function assumes zfetch is initialized prior to use
|
||||
fn getImdsCredentials(allocator: std.mem.Allocator, client: *std.http.Client, role_name: []const u8, imds_token: []u8) !?auth.Credentials {
|
||||
var headers = std.http.Headers.init(allocator);
|
||||
defer headers.deinit();
|
||||
try headers.append("X-aws-ec2-metadata-token", imds_token);
|
||||
|
||||
const url = try std.fmt.allocPrint(allocator, "http://169.254.169.254/latest/meta-data/iam/security-credentials/{s}/", .{role_name});
|
||||
defer allocator.free(url);
|
||||
var resp_payload = std.ArrayList(u8).init(allocator);
|
||||
defer resp_payload.deinit();
|
||||
const req = try client.fetch(.{
|
||||
.method = .GET,
|
||||
.location = .{ .url = url },
|
||||
.extra_headers = &[_]std.http.Header{
|
||||
.{ .name = "X-aws-ec2-metadata-token", .value = imds_token },
|
||||
},
|
||||
.response_storage = .{ .dynamic = &resp_payload },
|
||||
});
|
||||
|
||||
if (req.status != .ok and req.status != .not_found) {
|
||||
log.warn("Bad status code received from IMDS role endpoint: {}", .{@intFromEnum(req.status)});
|
||||
var req = try client.request(.GET, try std.Uri.parse(url), headers, .{});
|
||||
defer req.deinit();
|
||||
|
||||
try req.start();
|
||||
try req.wait();
|
||||
|
||||
if (req.response.status != .ok and req.response.status != .not_found) {
|
||||
log.warn("Bad status code received from IMDS role endpoint: {}", .{@intFromEnum(req.response.status)});
|
||||
return null;
|
||||
}
|
||||
if (req.status == .not_found) return null;
|
||||
if (resp_payload.items.len == 0) {
|
||||
if (req.response.status == .not_found) return null;
|
||||
if (req.response.content_length == null or req.response.content_length.? == 0) {
|
||||
log.warn("Unexpected empty response from IMDS role endpoint", .{});
|
||||
return null;
|
||||
}
|
||||
const resp = try allocator.alloc(u8, @intCast(req.response.content_length.?));
|
||||
defer allocator.free(resp);
|
||||
_ = try req.readAll(resp);
|
||||
|
||||
// log.debug("Read {d} bytes from imds v2 credentials endpoint", .{read});
|
||||
const ImdsResponse = struct {
|
||||
|
@ -297,8 +310,8 @@ fn getImdsCredentials(allocator: std.mem.Allocator, client: *std.http.Client, ro
|
|||
Token: []const u8,
|
||||
Expiration: []const u8,
|
||||
};
|
||||
const imds_response = std.json.parseFromSlice(ImdsResponse, allocator, resp_payload.items, .{}) catch |e| {
|
||||
log.err("Unexpected Json response from IMDS endpoint: {s}", .{resp_payload.items});
|
||||
const imds_response = std.json.parseFromSlice(ImdsResponse, allocator, resp, .{}) catch |e| {
|
||||
log.err("Unexpected Json response from IMDS endpoint: {s}", .{resp});
|
||||
log.err("Error parsing json: {}", .{e});
|
||||
if (@errorReturnTrace()) |trace| {
|
||||
std.debug.dumpStackTrace(trace.*);
|
||||
|
@ -568,13 +581,32 @@ fn getDefaultPath(allocator: std.mem.Allocator, home_dir: ?[]const u8, dir: []co
|
|||
fn getHomeDir(allocator: std.mem.Allocator) ![]const u8 {
|
||||
switch (builtin.os.tag) {
|
||||
.windows => {
|
||||
return std.process.getEnvVarOwned(allocator, "USERPROFILE") catch |err| switch (err) {
|
||||
error.OutOfMemory => |e| return e,
|
||||
else => return error.HomeDirUnavailable,
|
||||
var dir_path_ptr: [*:0]u16 = undefined;
|
||||
// https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid
|
||||
const FOLDERID_Profile = std.os.windows.GUID.parse("{5E6C858F-0E22-4760-9AFE-EA3317B67173}");
|
||||
switch (std.os.windows.shell32.SHGetKnownFolderPath(
|
||||
&FOLDERID_Profile,
|
||||
std.os.windows.KF_FLAG_CREATE,
|
||||
null,
|
||||
&dir_path_ptr,
|
||||
)) {
|
||||
std.os.windows.S_OK => {
|
||||
defer std.os.windows.ole32.CoTaskMemFree(@as(*anyopaque, @ptrCast(dir_path_ptr)));
|
||||
const global_dir = std.unicode.utf16leToUtf8Alloc(allocator, std.mem.sliceTo(dir_path_ptr, 0)) catch |err| switch (err) {
|
||||
error.UnexpectedSecondSurrogateHalf => return error.HomeDirUnavailable,
|
||||
error.ExpectedSecondSurrogateHalf => return error.HomeDirUnavailable,
|
||||
error.DanglingSurrogateHalf => return error.HomeDirUnavailable,
|
||||
error.OutOfMemory => return error.OutOfMemory,
|
||||
};
|
||||
return global_dir;
|
||||
// defer allocator.free(global_dir);
|
||||
},
|
||||
std.os.windows.E_OUTOFMEMORY => return error.OutOfMemory,
|
||||
else => return error.HomeDirUnavailable,
|
||||
}
|
||||
},
|
||||
.macos, .linux, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris => {
|
||||
const home_dir = std.posix.getenv("HOME") orelse {
|
||||
const home_dir = std.os.getenv("HOME") orelse {
|
||||
// TODO look in /etc/passwd
|
||||
return error.HomeDirUnavailable;
|
||||
};
|
||||
|
|
135
src/aws_http.zig
135
src/aws_http.zig
|
@ -44,7 +44,7 @@ pub const Options = struct {
|
|||
signing_time: ?i64 = null,
|
||||
};
|
||||
|
||||
pub const Header = std.http.Header;
|
||||
pub const Header = base.Header;
|
||||
pub const HttpRequest = base.Request;
|
||||
pub const HttpResult = base.Result;
|
||||
|
||||
|
@ -64,11 +64,11 @@ const EndPoint = struct {
|
|||
};
|
||||
pub const AwsHttp = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
proxy: ?std.http.Client.Proxy,
|
||||
proxy: ?std.http.Client.HttpProxy,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, proxy: ?std.http.Client.Proxy) Self {
|
||||
pub fn init(allocator: std.mem.Allocator, proxy: ?std.http.Client.HttpProxy) Self {
|
||||
return Self{
|
||||
.allocator = allocator,
|
||||
.proxy = proxy,
|
||||
|
@ -149,7 +149,7 @@ pub const AwsHttp = struct {
|
|||
// We will use endpoint instead
|
||||
request_cp.path = endpoint.path;
|
||||
|
||||
var request_headers = std.ArrayList(std.http.Header).init(self.allocator);
|
||||
var request_headers = std.ArrayList(base.Header).init(self.allocator);
|
||||
defer request_headers.deinit();
|
||||
|
||||
const len = try addHeaders(self.allocator, &request_headers, endpoint.host, request_cp.body, request_cp.content_type, request_cp.headers);
|
||||
|
@ -163,75 +163,108 @@ pub const AwsHttp = struct {
|
|||
}
|
||||
}
|
||||
|
||||
var headers = std.ArrayList(std.http.Header).init(self.allocator);
|
||||
var headers = std.http.Headers.init(self.allocator);
|
||||
defer headers.deinit();
|
||||
for (request_cp.headers) |header|
|
||||
try headers.append(.{ .name = header.name, .value = header.value });
|
||||
try headers.append(header.name, header.value);
|
||||
log.debug("All Request Headers:", .{});
|
||||
for (headers.items) |h| {
|
||||
for (headers.list.items) |h| {
|
||||
log.debug("\t{s}: {s}", .{ h.name, h.value });
|
||||
}
|
||||
|
||||
const url = try std.fmt.allocPrint(self.allocator, "{s}{s}{s}", .{ endpoint.uri, request_cp.path, request_cp.query });
|
||||
defer self.allocator.free(url);
|
||||
log.debug("Request url: {s}", .{url});
|
||||
// TODO: Fix this proxy stuff. This is all a kludge just to compile, but std.http.Client has it all built in now
|
||||
var cl = std.http.Client{ .allocator = self.allocator, .https_proxy = if (self.proxy) |*p| @constCast(p) else null };
|
||||
var cl = std.http.Client{ .allocator = self.allocator, .proxy = self.proxy };
|
||||
defer cl.deinit(); // TODO: Connection pooling
|
||||
//
|
||||
// var req = try zfetch.Request.init(self.allocator, url, self.trust_chain);
|
||||
// defer req.deinit();
|
||||
|
||||
const method = std.meta.stringToEnum(std.http.Method, request_cp.method).?;
|
||||
var server_header_buffer: [16 * 1024]u8 = undefined;
|
||||
var resp_payload = std.ArrayList(u8).init(self.allocator);
|
||||
defer resp_payload.deinit();
|
||||
const req = try cl.fetch(.{
|
||||
.server_header_buffer = &server_header_buffer,
|
||||
.method = method,
|
||||
.payload = if (request_cp.body.len > 0) request_cp.body else null,
|
||||
.response_storage = .{ .dynamic = &resp_payload },
|
||||
.raw_uri = true,
|
||||
.location = .{ .url = url },
|
||||
.extra_headers = headers.items,
|
||||
});
|
||||
// TODO: Need to test for payloads > 2^14. I believe one of our tests does this, but not sure
|
||||
// if (request_cp.body.len > 0) {
|
||||
// // Workaround for https://github.com/ziglang/zig/issues/15626
|
||||
// const max_bytes: usize = 1 << 14;
|
||||
// var inx: usize = 0;
|
||||
// while (request_cp.body.len > inx) {
|
||||
// try req.writeAll(request_cp.body[inx..@min(request_cp.body.len, inx + max_bytes)]);
|
||||
// inx += max_bytes;
|
||||
// }
|
||||
// std.Uri has a format function here that is used by start() (below)
|
||||
// to escape the string we're about to send. But we don't want that...
|
||||
// we need the control, because the signing above relies on the url above.
|
||||
// We can't seem to have our cake and eat it too, because we need escaped
|
||||
// ':' characters, but if we escape them, we'll get them double encoded.
|
||||
// If we don't escape them, they won't get encoded at all. I believe the
|
||||
// only answer may be to copy the Request.start function from the
|
||||
// standard library and tweak the print statements such that they don't
|
||||
// escape (but do still handle full uri (in proxy) vs path only (normal)
|
||||
//
|
||||
// try req.finish();
|
||||
// }
|
||||
// try req.wait();
|
||||
// Bug report filed here:
|
||||
// https://github.com/ziglang/zig/issues/17015
|
||||
//
|
||||
// https://github.com/ziglang/zig/blob/0.11.0/lib/std/http/Client.zig#L538-L636
|
||||
//
|
||||
// Look at lines 551 and 553:
|
||||
// https://github.com/ziglang/zig/blob/0.11.0/lib/std/http/Client.zig#L551
|
||||
//
|
||||
// This ends up executing the format function here:
|
||||
// https://github.com/ziglang/zig/blob/0.11.0/lib/std/http/Client.zig#L551
|
||||
//
|
||||
// Which is basically the what we want, without the escaping on lines
|
||||
// 249, 254, and 260:
|
||||
// https://github.com/ziglang/zig/blob/0.11.0/lib/std/Uri.zig#L249
|
||||
//
|
||||
// const unescaped_url = try std.Uri.unescapeString(self.allocator, url);
|
||||
// defer self.allocator.free(unescaped_url);
|
||||
var req = try cl.request(method, try std.Uri.parse(url), headers, .{});
|
||||
defer req.deinit();
|
||||
if (request_cp.body.len > 0)
|
||||
req.transfer_encoding = .{ .content_length = request_cp.body.len };
|
||||
try @import("http_client_17015_issue.zig").start(&req);
|
||||
// try req.start();
|
||||
if (request_cp.body.len > 0) {
|
||||
// Workaround for https://github.com/ziglang/zig/issues/15626
|
||||
const max_bytes: usize = 1 << 14;
|
||||
var inx: usize = 0;
|
||||
while (request_cp.body.len > inx) {
|
||||
try req.writeAll(request_cp.body[inx..@min(request_cp.body.len, inx + max_bytes)]);
|
||||
inx += max_bytes;
|
||||
}
|
||||
|
||||
try req.finish();
|
||||
}
|
||||
try req.wait();
|
||||
|
||||
// TODO: Timeout - is this now above us?
|
||||
log.debug(
|
||||
"Request Complete. Response code {d}: {?s}",
|
||||
.{ @intFromEnum(req.status), req.status.phrase() },
|
||||
.{ @intFromEnum(req.response.status), req.response.status.phrase() },
|
||||
);
|
||||
log.debug("Response headers:", .{});
|
||||
var resp_headers = std.ArrayList(Header).init(
|
||||
var resp_headers = try std.ArrayList(Header).initCapacity(
|
||||
self.allocator,
|
||||
req.response.headers.list.items.len,
|
||||
);
|
||||
defer resp_headers.deinit();
|
||||
var it = std.http.HeaderIterator.init(server_header_buffer[0..]);
|
||||
while (it.next()) |h| { // even though we don't expect to fill the buffer,
|
||||
// we don't get a length, but looks via stdlib source
|
||||
// it should be ok to call next on the undefined memory
|
||||
var content_length: usize = 0;
|
||||
for (req.response.headers.list.items) |h| {
|
||||
log.debug(" {s}: {s}", .{ h.name, h.value });
|
||||
try resp_headers.append(.{
|
||||
resp_headers.appendAssumeCapacity(.{
|
||||
.name = try (self.allocator.dupe(u8, h.name)),
|
||||
.value = try (self.allocator.dupe(u8, h.value)),
|
||||
});
|
||||
if (content_length == 0 and std.ascii.eqlIgnoreCase("content-length", h.name))
|
||||
content_length = std.fmt.parseInt(usize, h.value, 10) catch 0;
|
||||
}
|
||||
|
||||
log.debug("raw response body:\n{s}", .{resp_payload.items});
|
||||
var response_data: []u8 =
|
||||
if (req.response.transfer_encoding) |_| // the only value here is "chunked"
|
||||
try req.reader().readAllAlloc(self.allocator, std.math.maxInt(usize))
|
||||
else blk: {
|
||||
// content length
|
||||
const tmp_data = try self.allocator.alloc(u8, content_length);
|
||||
errdefer self.allocator.free(tmp_data);
|
||||
_ = try req.readAll(tmp_data);
|
||||
break :blk tmp_data;
|
||||
};
|
||||
log.debug("raw response body:\n{s}", .{response_data});
|
||||
|
||||
const rc = HttpResult{
|
||||
.response_code = @intFromEnum(req.status),
|
||||
.body = try resp_payload.toOwnedSlice(),
|
||||
.response_code = @intFromEnum(req.response.status),
|
||||
.body = response_data,
|
||||
.headers = try resp_headers.toOwnedSlice(),
|
||||
.allocator = self.allocator,
|
||||
};
|
||||
|
@ -244,16 +277,7 @@ fn getRegion(service: []const u8, region: []const u8) []const u8 {
|
|||
return region;
|
||||
}
|
||||
|
||||
fn addHeaders(allocator: std.mem.Allocator, headers: *std.ArrayList(std.http.Header), host: []const u8, body: []const u8, content_type: []const u8, additional_headers: []const Header) !?[]const u8 {
|
||||
// We don't need allocator and body because they were to add a
|
||||
// Content-Length header. But that is being added by the client send()
|
||||
// function, so we don't want it on the request twice. But I also feel
|
||||
// pretty strongly that send() should be providing us control, because
|
||||
// I think if we don't add it here, it won't get signed, and we would
|
||||
// really prefer it to be signed. So, we will wait and watch for this
|
||||
// situation to change in stdlib
|
||||
_ = allocator;
|
||||
_ = body;
|
||||
fn addHeaders(allocator: std.mem.Allocator, headers: *std.ArrayList(base.Header), host: []const u8, body: []const u8, content_type: []const u8, additional_headers: []Header) !?[]const u8 {
|
||||
var has_content_type = false;
|
||||
for (additional_headers) |h| {
|
||||
if (std.ascii.eqlIgnoreCase(h.name, "Content-Type")) {
|
||||
|
@ -267,6 +291,11 @@ fn addHeaders(allocator: std.mem.Allocator, headers: *std.ArrayList(std.http.Hea
|
|||
if (!has_content_type)
|
||||
try headers.append(.{ .name = "Content-Type", .value = content_type });
|
||||
try headers.appendSlice(additional_headers);
|
||||
if (body.len > 0) {
|
||||
const len = try std.fmt.allocPrint(allocator, "{d}", .{body.len});
|
||||
try headers.append(.{ .name = "Content-Length", .value = len });
|
||||
return len;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,12 +7,12 @@ pub const Request = struct {
|
|||
body: []const u8 = "",
|
||||
method: []const u8 = "POST",
|
||||
content_type: []const u8 = "application/json", // Can we get away with this?
|
||||
headers: []const std.http.Header = &.{},
|
||||
headers: []Header = &[_]Header{},
|
||||
};
|
||||
pub const Result = struct {
|
||||
response_code: u16, // actually 3 digits can fit in u10
|
||||
body: []const u8,
|
||||
headers: []const std.http.Header,
|
||||
headers: []Header,
|
||||
allocator: std.mem.Allocator,
|
||||
|
||||
pub fn deinit(self: Result) void {
|
||||
|
@ -26,3 +26,8 @@ pub const Result = struct {
|
|||
return;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Header = struct {
|
||||
name: []const u8,
|
||||
value: []const u8,
|
||||
};
|
||||
|
|
|
@ -169,19 +169,19 @@ pub fn signRequest(allocator: std.mem.Allocator, request: base.Request, config:
|
|||
additional_header_count += 1;
|
||||
if (config.signed_body_header == .none)
|
||||
additional_header_count -= 1;
|
||||
const newheaders = try allocator.alloc(std.http.Header, rc.headers.len + additional_header_count);
|
||||
const newheaders = try allocator.alloc(base.Header, rc.headers.len + additional_header_count);
|
||||
errdefer allocator.free(newheaders);
|
||||
const oldheaders = rc.headers;
|
||||
if (config.credentials.session_token) |t| {
|
||||
newheaders[newheaders.len - additional_header_count] = std.http.Header{
|
||||
newheaders[newheaders.len - additional_header_count] = base.Header{
|
||||
.name = "X-Amz-Security-Token",
|
||||
.value = try allocator.dupe(u8, t),
|
||||
};
|
||||
additional_header_count -= 1;
|
||||
}
|
||||
errdefer freeSignedRequest(allocator, &rc, config);
|
||||
@memcpy(newheaders[0..oldheaders.len], oldheaders);
|
||||
newheaders[newheaders.len - additional_header_count] = std.http.Header{
|
||||
std.mem.copy(base.Header, newheaders, oldheaders);
|
||||
newheaders[newheaders.len - additional_header_count] = base.Header{
|
||||
.name = "X-Amz-Date",
|
||||
.value = signing_iso8601,
|
||||
};
|
||||
|
@ -200,7 +200,7 @@ pub fn signRequest(allocator: std.mem.Allocator, request: base.Request, config:
|
|||
// may not add this header
|
||||
// This will be freed in freeSignedRequest
|
||||
// defer allocator.free(payload_hash);
|
||||
newheaders[newheaders.len - additional_header_count] = std.http.Header{
|
||||
newheaders[newheaders.len - additional_header_count] = base.Header{
|
||||
.name = "x-amz-content-sha256",
|
||||
.value = payload_hash,
|
||||
};
|
||||
|
@ -259,7 +259,7 @@ pub fn signRequest(allocator: std.mem.Allocator, request: base.Request, config:
|
|||
|
||||
const signature = try hmac(allocator, signing_key, string_to_sign);
|
||||
defer allocator.free(signature);
|
||||
newheaders[newheaders.len - 1] = std.http.Header{
|
||||
newheaders[newheaders.len - 1] = base.Header{
|
||||
.name = "Authorization",
|
||||
.value = try std.fmt.allocPrint(
|
||||
allocator,
|
||||
|
@ -299,51 +299,27 @@ pub fn freeSignedRequest(allocator: std.mem.Allocator, request: *base.Request, c
|
|||
|
||||
pub const credentialsFn = *const fn ([]const u8) ?Credentials;
|
||||
|
||||
pub fn verifyServerRequest(allocator: std.mem.Allocator, request: *std.http.Server.Request, request_body_reader: anytype, credentials_fn: credentialsFn) !bool {
|
||||
var unverified_request = try UnverifiedRequest.init(allocator, request);
|
||||
defer unverified_request.deinit();
|
||||
pub fn verifyServerRequest(allocator: std.mem.Allocator, request: std.http.Server.Request, request_body_reader: anytype, credentials_fn: credentialsFn) !bool {
|
||||
const unverified_request = UnverifiedRequest{
|
||||
.headers = request.headers,
|
||||
.target = request.target,
|
||||
.method = request.method,
|
||||
};
|
||||
return verify(allocator, unverified_request, request_body_reader, credentials_fn);
|
||||
}
|
||||
|
||||
pub const UnverifiedRequest = struct {
|
||||
headers: []std.http.Header,
|
||||
headers: std.http.Headers,
|
||||
target: []const u8,
|
||||
method: std.http.Method,
|
||||
allocator: std.mem.Allocator,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, request: *std.http.Server.Request) !UnverifiedRequest {
|
||||
var al = std.ArrayList(std.http.Header).init(allocator);
|
||||
defer al.deinit();
|
||||
var it = request.iterateHeaders();
|
||||
while (it.next()) |h| try al.append(h);
|
||||
return .{
|
||||
.target = request.head.target,
|
||||
.method = request.head.method,
|
||||
.headers = try al.toOwnedSlice(),
|
||||
.allocator = allocator,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getFirstHeaderValue(self: UnverifiedRequest, name: []const u8) ?[]const u8 {
|
||||
for (self.headers) |*h| {
|
||||
if (std.ascii.eqlIgnoreCase(name, h.name))
|
||||
return h.value; // I don't think this is the whole story here, but should suffice for now
|
||||
// We need to return the value before the first ';' IIRC
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *UnverifiedRequest) void {
|
||||
self.allocator.free(self.headers);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn verify(allocator: std.mem.Allocator, request: UnverifiedRequest, request_body_reader: anytype, credentials_fn: credentialsFn) !bool {
|
||||
var arena = std.heap.ArenaAllocator.init(allocator);
|
||||
defer arena.deinit();
|
||||
const aa = arena.allocator();
|
||||
var aa = arena.allocator();
|
||||
// Authorization: AWS4-HMAC-SHA256 Credential=ACCESS/20230908/us-west-2/s3/aws4_request, SignedHeaders=accept;content-length;content-type;host;x-amz-content-sha256;x-amz-date;x-amz-storage-class, Signature=fcc43ce73a34c9bd1ddf17e8a435f46a859812822f944f9eeb2aabcd64b03523
|
||||
const auth_header_or_null = request.getFirstHeaderValue("Authorization");
|
||||
const auth_header_or_null = request.headers.getFirstValue("Authorization");
|
||||
const auth_header = if (auth_header_or_null) |a| a else return error.AuthorizationHeaderMissing;
|
||||
if (!std.mem.startsWith(u8, auth_header, "AWS4-HMAC-SHA256")) return error.UnsupportedAuthorizationType;
|
||||
var credential: ?[]const u8 = null;
|
||||
|
@ -397,8 +373,8 @@ fn verifyParsedAuthorization(
|
|||
const credentials = credentials_fn(access_key) orelse return error.CredentialsNotFound;
|
||||
// TODO: https://stackoverflow.com/questions/29276609/aws-authentication-requires-a-valid-date-or-x-amz-date-header-curl
|
||||
// For now I want to see this test pass
|
||||
const normalized_iso_date = request.getFirstHeaderValue("x-amz-date") orelse
|
||||
request.getFirstHeaderValue("Date").?;
|
||||
const normalized_iso_date = request.headers.getFirstValue("x-amz-date") orelse
|
||||
request.headers.getFirstValue("Date").?;
|
||||
log.debug("Got date: {s}", .{normalized_iso_date});
|
||||
_ = credential_iterator.next().?; // skip the date...I don't think we need this
|
||||
const region = credential_iterator.next().?;
|
||||
|
@ -416,7 +392,7 @@ fn verifyParsedAuthorization(
|
|||
.signing_time = try date.dateTimeToTimestamp(try date.parseIso8601ToDateTime(normalized_iso_date)),
|
||||
};
|
||||
|
||||
var headers = try allocator.alloc(std.http.Header, std.mem.count(u8, signed_headers, ";") + 1);
|
||||
var headers = try allocator.alloc(base.Header, std.mem.count(u8, signed_headers, ";") + 1);
|
||||
defer allocator.free(headers);
|
||||
var signed_headers_iterator = std.mem.splitSequence(u8, signed_headers, ";");
|
||||
var inx: usize = 0;
|
||||
|
@ -433,7 +409,7 @@ fn verifyParsedAuthorization(
|
|||
if (is_forbidden) continue;
|
||||
headers[inx] = .{
|
||||
.name = signed_header,
|
||||
.value = request.getFirstHeaderValue(signed_header).?,
|
||||
.value = request.headers.getFirstValue(signed_header).?,
|
||||
};
|
||||
inx += 1;
|
||||
}
|
||||
|
@ -442,7 +418,7 @@ fn verifyParsedAuthorization(
|
|||
.path = target_iterator.first(),
|
||||
.headers = headers[0..inx],
|
||||
.method = @tagName(request.method),
|
||||
.content_type = request.getFirstHeaderValue("content-type").?,
|
||||
.content_type = request.headers.getFirstValue("content-type").?,
|
||||
};
|
||||
signed_request.query = request.target[signed_request.path.len..]; // TODO: should this be +1? query here would include '?'
|
||||
signed_request.body = try request_body_reader.readAllAlloc(allocator, std.math.maxInt(usize));
|
||||
|
@ -804,7 +780,7 @@ const CanonicalHeaders = struct {
|
|||
str: []const u8,
|
||||
signed_headers: []const u8,
|
||||
};
|
||||
fn canonicalHeaders(allocator: std.mem.Allocator, headers: []const std.http.Header, service: []const u8) !CanonicalHeaders {
|
||||
fn canonicalHeaders(allocator: std.mem.Allocator, headers: []base.Header, service: []const u8) !CanonicalHeaders {
|
||||
//
|
||||
// Doc example. Original:
|
||||
//
|
||||
|
@ -820,7 +796,7 @@ fn canonicalHeaders(allocator: std.mem.Allocator, headers: []const std.http.Head
|
|||
// my-header1:a b c\n
|
||||
// my-header2:"a b c"\n
|
||||
// x-amz-date:20150830T123600Z\n
|
||||
var dest = try std.ArrayList(std.http.Header).initCapacity(allocator, headers.len);
|
||||
var dest = try std.ArrayList(base.Header).initCapacity(allocator, headers.len);
|
||||
defer {
|
||||
for (dest.items) |h| {
|
||||
allocator.free(h.name);
|
||||
|
@ -859,7 +835,7 @@ fn canonicalHeaders(allocator: std.mem.Allocator, headers: []const std.http.Head
|
|||
try dest.append(.{ .name = n, .value = v });
|
||||
}
|
||||
|
||||
std.sort.pdq(std.http.Header, dest.items, {}, lessThan);
|
||||
std.sort.pdq(base.Header, dest.items, {}, lessThan);
|
||||
|
||||
var dest_str = try std.ArrayList(u8).initCapacity(allocator, total_len);
|
||||
defer dest_str.deinit();
|
||||
|
@ -907,7 +883,7 @@ fn canonicalHeaderValue(allocator: std.mem.Allocator, value: []const u8) ![]cons
|
|||
_ = allocator.resize(rc, rc_inx);
|
||||
return rc[0..rc_inx];
|
||||
}
|
||||
fn lessThan(context: void, lhs: std.http.Header, rhs: std.http.Header) bool {
|
||||
fn lessThan(context: void, lhs: base.Header, rhs: base.Header) bool {
|
||||
_ = context;
|
||||
return std.ascii.lessThanIgnoreCase(lhs.name, rhs.name);
|
||||
}
|
||||
|
@ -959,7 +935,7 @@ test "canonical query" {
|
|||
}
|
||||
test "canonical headers" {
|
||||
const allocator = std.testing.allocator;
|
||||
var headers = try std.ArrayList(std.http.Header).initCapacity(allocator, 5);
|
||||
var headers = try std.ArrayList(base.Header).initCapacity(allocator, 5);
|
||||
defer headers.deinit();
|
||||
try headers.append(.{ .name = "Host", .value = "iam.amazonaws.com" });
|
||||
try headers.append(.{ .name = "Content-Type", .value = "application/x-www-form-urlencoded; charset=utf-8" });
|
||||
|
@ -984,7 +960,7 @@ test "canonical headers" {
|
|||
|
||||
test "canonical request" {
|
||||
const allocator = std.testing.allocator;
|
||||
var headers = try std.ArrayList(std.http.Header).initCapacity(allocator, 5);
|
||||
var headers = try std.ArrayList(base.Header).initCapacity(allocator, 5);
|
||||
defer headers.deinit();
|
||||
try headers.append(.{ .name = "User-agent", .value = "c sdk v1.0" });
|
||||
// In contrast to AWS CRT (aws-c-auth), we add the date as part of the
|
||||
|
@ -1044,7 +1020,7 @@ test "can sign" {
|
|||
// [debug] (awshttp): Content-Length: 43
|
||||
|
||||
const allocator = std.testing.allocator;
|
||||
var headers = try std.ArrayList(std.http.Header).initCapacity(allocator, 5);
|
||||
var headers = try std.ArrayList(base.Header).initCapacity(allocator, 5);
|
||||
defer headers.deinit();
|
||||
try headers.append(.{ .name = "Content-Type", .value = "application/x-www-form-urlencoded; charset=utf-8" });
|
||||
try headers.append(.{ .name = "Content-Length", .value = "13" });
|
||||
|
@ -1101,39 +1077,34 @@ test "can verify server request" {
|
|||
test_credential = Credentials.init(allocator, access_key, secret_key, null);
|
||||
defer test_credential.?.deinit();
|
||||
|
||||
const req =
|
||||
"PUT /mysfitszj3t6webstack-hostingbucketa91a61fe-1ep3ezkgwpxr0/i/am/a/teapot/foo?x-id=PutObject HTTP/1.1\r\n" ++
|
||||
"Connection: keep-alive\r\n" ++
|
||||
"Accept-Encoding: gzip, deflate, zstd\r\n" ++
|
||||
"TE: gzip, deflate, trailers\r\n" ++
|
||||
"Accept: application/json\r\n" ++
|
||||
"Host: 127.0.0.1\r\n" ++
|
||||
"User-Agent: zig-aws 1.0\r\n" ++
|
||||
"Content-Type: text/plain\r\n" ++
|
||||
"x-amz-storage-class: STANDARD\r\n" ++
|
||||
"Content-Length: 3\r\n" ++
|
||||
"X-Amz-Date: 20230908T170252Z\r\n" ++
|
||||
"x-amz-content-sha256: fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9\r\n" ++
|
||||
"Authorization: AWS4-HMAC-SHA256 Credential=ACCESS/20230908/us-west-2/s3/aws4_request, SignedHeaders=accept;content-length;content-type;host;x-amz-content-sha256;x-amz-date;x-amz-storage-class, Signature=fcc43ce73a34c9bd1ddf17e8a435f46a859812822f944f9eeb2aabcd64b03523\r\n\r\nbar";
|
||||
var read_buffer: [1024]u8 = undefined;
|
||||
@memcpy(read_buffer[0..req.len], req);
|
||||
var server: std.http.Server = .{
|
||||
.connection = undefined,
|
||||
.state = .ready,
|
||||
.read_buffer = &read_buffer,
|
||||
.read_buffer_len = req.len,
|
||||
.next_request_start = 0,
|
||||
};
|
||||
var request: std.http.Server.Request = .{
|
||||
.server = &server,
|
||||
.head_end = req.len - 3,
|
||||
.head = try std.http.Server.Request.Head.parse(read_buffer[0 .. req.len - 3]),
|
||||
.reader_state = undefined,
|
||||
var headers = std.http.Headers.init(allocator);
|
||||
defer headers.deinit();
|
||||
try headers.append("Connection", "keep-alive");
|
||||
try headers.append("Accept-Encoding", "gzip, deflate, zstd");
|
||||
try headers.append("TE", "gzip, deflate, trailers");
|
||||
try headers.append("Accept", "application/json");
|
||||
try headers.append("Host", "127.0.0.1");
|
||||
try headers.append("User-Agent", "zig-aws 1.0");
|
||||
try headers.append("Content-Type", "text/plain");
|
||||
try headers.append("x-amz-storage-class", "STANDARD");
|
||||
try headers.append("Content-Length", "3");
|
||||
try headers.append("X-Amz-Date", "20230908T170252Z");
|
||||
try headers.append("x-amz-content-sha256", "fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9");
|
||||
try headers.append("Authorization", "AWS4-HMAC-SHA256 Credential=ACCESS/20230908/us-west-2/s3/aws4_request, SignedHeaders=accept;content-length;content-type;host;x-amz-content-sha256;x-amz-date;x-amz-storage-class, Signature=fcc43ce73a34c9bd1ddf17e8a435f46a859812822f944f9eeb2aabcd64b03523");
|
||||
|
||||
var buf = "bar".*;
|
||||
var fis = std.io.fixedBufferStream(&buf);
|
||||
const request = std.http.Server.Request{
|
||||
.method = std.http.Method.PUT,
|
||||
.target = "/mysfitszj3t6webstack-hostingbucketa91a61fe-1ep3ezkgwpxr0/i/am/a/teapot/foo?x-id=PutObject",
|
||||
.version = .@"HTTP/1.1",
|
||||
.content_length = 3,
|
||||
.headers = headers,
|
||||
.parser = std.http.protocol.HeadersParser.initDynamic(std.math.maxInt(usize)),
|
||||
};
|
||||
|
||||
// std.testing.log_level = .debug;
|
||||
var fbs = std.io.fixedBufferStream("bar");
|
||||
try std.testing.expect(try verifyServerRequest(allocator, &request, fbs.reader(), struct {
|
||||
try std.testing.expect(try verifyServerRequest(allocator, request, fis.reader(), struct {
|
||||
cred: Credentials,
|
||||
|
||||
const Self = @This();
|
||||
|
@ -1151,51 +1122,34 @@ test "can verify server request without x-amz-content-sha256" {
|
|||
test_credential = Credentials.init(allocator, access_key, secret_key, null);
|
||||
defer test_credential.?.deinit();
|
||||
|
||||
const head =
|
||||
"POST / HTTP/1.1\r\n" ++
|
||||
"Connection: keep-alive\r\n" ++
|
||||
"Accept-Encoding: gzip, deflate, zstd\r\n" ++
|
||||
"TE: gzip, deflate, trailers\r\n" ++
|
||||
"Accept: application/json\r\n" ++
|
||||
"X-Amz-Target: DynamoDB_20120810.CreateTable\r\n" ++
|
||||
"Host: dynamodb.us-west-2.amazonaws.com\r\n" ++
|
||||
"User-Agent: zig-aws 1.0\r\n" ++
|
||||
"Content-Type: application/x-amz-json-1.0\r\n" ++
|
||||
"Content-Length: 403\r\n" ++
|
||||
"X-Amz-Date: 20240224T154944Z\r\n" ++
|
||||
"x-amz-content-sha256: fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9\r\n" ++
|
||||
"Authorization: AWS4-HMAC-SHA256 Credential=ACCESS/20240224/us-west-2/dynamodb/aws4_request, SignedHeaders=content-type;host;x-amz-date;x-amz-target, Signature=8fd23dc7dbcb36c4aa54207a7118f8b9fcd680da73a0590b498e9577ff68ec33\r\n\r\n";
|
||||
var headers = std.http.Headers.init(allocator);
|
||||
defer headers.deinit();
|
||||
try headers.append("Connection", "keep-alive");
|
||||
try headers.append("Accept-Encoding", "gzip, deflate, zstd");
|
||||
try headers.append("TE", "gzip, deflate, trailers");
|
||||
try headers.append("Accept", "application/json");
|
||||
try headers.append("X-Amz-Target", "DynamoDB_20120810.CreateTable");
|
||||
try headers.append("Host", "dynamodb.us-west-2.amazonaws.com");
|
||||
try headers.append("User-Agent", "zig-aws 1.0");
|
||||
try headers.append("Content-Type", "application/x-amz-json-1.0");
|
||||
try headers.append("Content-Length", "403");
|
||||
try headers.append("X-Amz-Date", "20240224T154944Z");
|
||||
try headers.append("x-amz-content-sha256", "fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9");
|
||||
try headers.append("Authorization", "AWS4-HMAC-SHA256 Credential=ACCESS/20240224/us-west-2/dynamodb/aws4_request, SignedHeaders=content-type;host;x-amz-date;x-amz-target, Signature=8fd23dc7dbcb36c4aa54207a7118f8b9fcd680da73a0590b498e9577ff68ec33");
|
||||
const body =
|
||||
\\{"AttributeDefinitions": [{"AttributeName": "Artist", "AttributeType": "S"}, {"AttributeName": "SongTitle", "AttributeType": "S"}], "TableName": "MusicCollection", "KeySchema": [{"AttributeName": "Artist", "KeyType": "HASH"}, {"AttributeName": "SongTitle", "KeyType": "RANGE"}], "ProvisionedThroughput": {"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, "Tags": [{"Key": "Owner", "Value": "blueTeam"}]}
|
||||
;
|
||||
const req_data = head ++ body;
|
||||
var read_buffer: [2048]u8 = undefined;
|
||||
@memcpy(read_buffer[0..req_data.len], req_data);
|
||||
var server: std.http.Server = .{
|
||||
.connection = undefined,
|
||||
.state = .ready,
|
||||
.read_buffer = &read_buffer,
|
||||
.read_buffer_len = req_data.len,
|
||||
.next_request_start = 0,
|
||||
};
|
||||
var request: std.http.Server.Request = .{
|
||||
.server = &server,
|
||||
.head_end = head.len,
|
||||
.head = try std.http.Server.Request.Head.parse(read_buffer[0..head.len]),
|
||||
.reader_state = undefined,
|
||||
};
|
||||
{
|
||||
var h = std.ArrayList(std.http.Header).init(allocator);
|
||||
var h = try std.ArrayList(base.Header).initCapacity(allocator, headers.list.items.len);
|
||||
defer h.deinit();
|
||||
const signed_headers = &[_][]const u8{ "content-type", "host", "x-amz-date", "x-amz-target" };
|
||||
var it = request.iterateHeaders();
|
||||
while (it.next()) |source| {
|
||||
for (headers.list.items) |source| {
|
||||
var match = false;
|
||||
for (signed_headers) |s| {
|
||||
match = std.ascii.eqlIgnoreCase(s, source.name);
|
||||
if (match) break;
|
||||
}
|
||||
if (match) try h.append(.{ .name = source.name, .value = source.value });
|
||||
if (match) h.appendAssumeCapacity(.{ .name = source.name, .value = source.value });
|
||||
}
|
||||
const req = base.Request{
|
||||
.path = "/",
|
||||
|
@ -1233,8 +1187,16 @@ test "can verify server request without x-amz-content-sha256" {
|
|||
|
||||
{ // verification
|
||||
var fis = std.io.fixedBufferStream(body[0..]);
|
||||
const request = std.http.Server.Request{
|
||||
.method = std.http.Method.POST,
|
||||
.target = "/",
|
||||
.version = .@"HTTP/1.1",
|
||||
.content_length = 403,
|
||||
.headers = headers,
|
||||
.parser = std.http.protocol.HeadersParser.initDynamic(std.math.maxInt(usize)),
|
||||
};
|
||||
|
||||
try std.testing.expect(try verifyServerRequest(allocator, &request, fis.reader(), struct {
|
||||
try std.testing.expect(try verifyServerRequest(allocator, request, fis.reader(), struct {
|
||||
cred: Credentials,
|
||||
|
||||
const Self = @This();
|
||||
|
|
155
src/http_client_17015_issue.zig
Normal file
155
src/http_client_17015_issue.zig
Normal file
|
@ -0,0 +1,155 @@
|
|||
const std = @import("std");
|
||||
const Uri = std.Uri;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
/// This function imported from:
|
||||
/// https://github.com/ziglang/zig/blob/0.11.0/lib/std/http/Client.zig#L538-L636
|
||||
///
|
||||
/// The first commit of this file will be unchanged from 0.11.0 to more
|
||||
/// clearly indicate changes moving forward. The plan is to change
|
||||
/// only the two w.print lines for req.uri 16 and 18 lines down from this comment
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
/// Send the request to the server.
|
||||
pub fn start(req: *std.http.Client.Request) std.http.Client.Request.StartError!void {
|
||||
var buffered = std.io.bufferedWriter(req.connection.?.data.writer());
|
||||
const w = buffered.writer();
|
||||
|
||||
try w.writeAll(@tagName(req.method));
|
||||
try w.writeByte(' ');
|
||||
|
||||
if (req.method == .CONNECT) {
|
||||
try w.writeAll(req.uri.host.?);
|
||||
try w.writeByte(':');
|
||||
try w.print("{}", .{req.uri.port.?});
|
||||
} else if (req.connection.?.data.proxied) {
|
||||
// proxied connections require the full uri
|
||||
try format(req.uri, "+/", .{}, w);
|
||||
} else {
|
||||
try format(req.uri, "/", .{}, w);
|
||||
}
|
||||
|
||||
try w.writeByte(' ');
|
||||
try w.writeAll(@tagName(req.version));
|
||||
try w.writeAll("\r\n");
|
||||
|
||||
if (!req.headers.contains("host")) {
|
||||
try w.writeAll("Host: ");
|
||||
try w.writeAll(req.uri.host.?);
|
||||
try w.writeAll("\r\n");
|
||||
}
|
||||
|
||||
if (!req.headers.contains("user-agent")) {
|
||||
try w.writeAll("User-Agent: zig/");
|
||||
try w.writeAll(@import("builtin").zig_version_string);
|
||||
try w.writeAll(" (std.http)\r\n");
|
||||
}
|
||||
|
||||
if (!req.headers.contains("connection")) {
|
||||
try w.writeAll("Connection: keep-alive\r\n");
|
||||
}
|
||||
|
||||
if (!req.headers.contains("accept-encoding")) {
|
||||
try w.writeAll("Accept-Encoding: gzip, deflate, zstd\r\n");
|
||||
}
|
||||
|
||||
if (!req.headers.contains("te")) {
|
||||
try w.writeAll("TE: gzip, deflate, trailers\r\n");
|
||||
}
|
||||
|
||||
const has_transfer_encoding = req.headers.contains("transfer-encoding");
|
||||
const has_content_length = req.headers.contains("content-length");
|
||||
|
||||
if (!has_transfer_encoding and !has_content_length) {
|
||||
switch (req.transfer_encoding) {
|
||||
.chunked => try w.writeAll("Transfer-Encoding: chunked\r\n"),
|
||||
.content_length => |content_length| try w.print("Content-Length: {d}\r\n", .{content_length}),
|
||||
.none => {},
|
||||
}
|
||||
} else {
|
||||
if (has_content_length) {
|
||||
const content_length = std.fmt.parseInt(u64, req.headers.getFirstValue("content-length").?, 10) catch return error.InvalidContentLength;
|
||||
|
||||
req.transfer_encoding = .{ .content_length = content_length };
|
||||
} else if (has_transfer_encoding) {
|
||||
const transfer_encoding = req.headers.getFirstValue("transfer-encoding").?;
|
||||
if (std.mem.eql(u8, transfer_encoding, "chunked")) {
|
||||
req.transfer_encoding = .chunked;
|
||||
} else {
|
||||
return error.UnsupportedTransferEncoding;
|
||||
}
|
||||
} else {
|
||||
req.transfer_encoding = .none;
|
||||
}
|
||||
}
|
||||
|
||||
try w.print("{}", .{req.headers});
|
||||
|
||||
try w.writeAll("\r\n");
|
||||
|
||||
try buffered.flush();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
/// This function imported from:
|
||||
/// https://github.com/ziglang/zig/blob/0.11.0/lib/std/Uri.zig#L209-L264
|
||||
///
|
||||
/// The first commit of this file will be unchanged from 0.11.0 to more
|
||||
/// clearly indicate changes moving forward. The plan is to change
|
||||
/// only the writeEscapedPath call 42 lines down from this comment
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
pub fn format(
|
||||
uri: Uri,
|
||||
comptime fmt: []const u8,
|
||||
options: std.fmt.FormatOptions,
|
||||
writer: anytype,
|
||||
) @TypeOf(writer).Error!void {
|
||||
_ = options;
|
||||
|
||||
const needs_absolute = comptime std.mem.indexOf(u8, fmt, "+") != null;
|
||||
const needs_path = comptime std.mem.indexOf(u8, fmt, "/") != null or fmt.len == 0;
|
||||
const needs_fragment = comptime std.mem.indexOf(u8, fmt, "#") != null;
|
||||
|
||||
if (needs_absolute) {
|
||||
try writer.writeAll(uri.scheme);
|
||||
try writer.writeAll(":");
|
||||
if (uri.host) |host| {
|
||||
try writer.writeAll("//");
|
||||
|
||||
if (uri.user) |user| {
|
||||
try writer.writeAll(user);
|
||||
if (uri.password) |password| {
|
||||
try writer.writeAll(":");
|
||||
try writer.writeAll(password);
|
||||
}
|
||||
try writer.writeAll("@");
|
||||
}
|
||||
|
||||
try writer.writeAll(host);
|
||||
|
||||
if (uri.port) |port| {
|
||||
try writer.writeAll(":");
|
||||
try std.fmt.formatInt(port, 10, .lower, .{}, writer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (needs_path) {
|
||||
if (uri.path.len == 0) {
|
||||
try writer.writeAll("/");
|
||||
} else {
|
||||
try writer.writeAll(uri.path); // do not mess with our path
|
||||
}
|
||||
|
||||
if (uri.query) |q| {
|
||||
try writer.writeAll("?");
|
||||
try Uri.writeEscapedQuery(writer, q);
|
||||
}
|
||||
|
||||
if (needs_fragment) {
|
||||
if (uri.fragment) |f| {
|
||||
try writer.writeAll("#");
|
||||
try Uri.writeEscapedQuery(writer, f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
26
src/json.zig
26
src/json.zig
|
@ -1361,7 +1361,7 @@ test "Value.jsonStringify" {
|
|||
var buffer: [10]u8 = undefined;
|
||||
var fbs = std.io.fixedBufferStream(&buffer);
|
||||
try (Value{ .Float = 42 }).jsonStringify(.{}, fbs.writer());
|
||||
try testing.expectEqualSlices(u8, fbs.getWritten(), "4.2e1");
|
||||
try testing.expectEqualSlices(u8, fbs.getWritten(), "4.2e+01");
|
||||
}
|
||||
{
|
||||
var buffer: [10]u8 = undefined;
|
||||
|
@ -1762,7 +1762,7 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options:
|
|||
var r: T = undefined;
|
||||
const source_slice = stringToken.slice(tokens.slice, tokens.i - 1);
|
||||
switch (stringToken.escapes) {
|
||||
.None => @memcpy(&r, source_slice),
|
||||
.None => mem.copy(u8, &r, source_slice),
|
||||
.Some => try unescapeValidString(&r, source_slice),
|
||||
}
|
||||
return r;
|
||||
|
@ -2019,7 +2019,7 @@ test "parse into tagged union" {
|
|||
}
|
||||
|
||||
{ // failing allocations should be bubbled up instantly without trying next member
|
||||
var fail_alloc = testing.FailingAllocator.init(testing.allocator, .{ .fail_index = 0 });
|
||||
var fail_alloc = testing.FailingAllocator.init(testing.allocator, 0);
|
||||
const options = ParseOptions{ .allocator = fail_alloc.allocator() };
|
||||
const T = union(enum) {
|
||||
// both fields here match the input
|
||||
|
@ -2067,7 +2067,7 @@ test "parse union bubbles up AllocatorRequired" {
|
|||
}
|
||||
|
||||
test "parseFree descends into tagged union" {
|
||||
var fail_alloc = testing.FailingAllocator.init(testing.allocator, .{ .fail_index = 1 });
|
||||
var fail_alloc = testing.FailingAllocator.init(testing.allocator, 1);
|
||||
const options = ParseOptions{ .allocator = fail_alloc.allocator() };
|
||||
const T = union(enum) {
|
||||
int: i32,
|
||||
|
@ -2808,7 +2808,7 @@ pub fn stringify(
|
|||
const T = @TypeOf(value);
|
||||
switch (@typeInfo(T)) {
|
||||
.Float, .ComptimeFloat => {
|
||||
return std.fmt.format(out_stream, "{e}", .{value});
|
||||
return std.fmt.formatFloatScientific(value, std.fmt.FormatOptions{}, out_stream);
|
||||
},
|
||||
.Int, .ComptimeInt => {
|
||||
return std.fmt.formatIntValue(value, "", std.fmt.FormatOptions{}, out_stream);
|
||||
|
@ -2827,14 +2827,14 @@ pub fn stringify(
|
|||
}
|
||||
},
|
||||
.Enum => {
|
||||
if (comptime std.meta.hasFn(T, "jsonStringify")) {
|
||||
if (comptime std.meta.trait.hasFn("jsonStringify")(T)) {
|
||||
return value.jsonStringify(options, out_stream);
|
||||
}
|
||||
|
||||
@compileError("Unable to stringify enum '" ++ @typeName(T) ++ "'");
|
||||
},
|
||||
.Union => {
|
||||
if (comptime std.meta.hasFn(T, "jsonStringify")) {
|
||||
if (comptime std.meta.trait.hasFn("jsonStringify")(T)) {
|
||||
return value.jsonStringify(options, out_stream);
|
||||
}
|
||||
|
||||
|
@ -2850,7 +2850,7 @@ pub fn stringify(
|
|||
}
|
||||
},
|
||||
.Struct => |S| {
|
||||
if (comptime std.meta.hasFn(T, "jsonStringify")) {
|
||||
if (comptime std.meta.trait.hasFn("jsonStringify")(T)) {
|
||||
return value.jsonStringify(options, out_stream);
|
||||
}
|
||||
|
||||
|
@ -2874,11 +2874,11 @@ pub fn stringify(
|
|||
try child_whitespace.outputIndent(out_stream);
|
||||
}
|
||||
var field_written = false;
|
||||
if (comptime std.meta.hasFn(T, "jsonStringifyField"))
|
||||
if (comptime std.meta.trait.hasFn("jsonStringifyField")(T))
|
||||
field_written = try value.jsonStringifyField(Field.name, child_options, out_stream);
|
||||
|
||||
if (!field_written) {
|
||||
if (comptime std.meta.hasFn(T, "fieldNameFor")) {
|
||||
if (comptime std.meta.trait.hasFn("fieldNameFor")(T)) {
|
||||
const name = value.fieldNameFor(Field.name);
|
||||
try stringify(name, options, out_stream);
|
||||
} else {
|
||||
|
@ -3057,11 +3057,11 @@ test "stringify basic types" {
|
|||
try teststringify("null", @as(?u8, null), StringifyOptions{});
|
||||
try teststringify("null", @as(?*u32, null), StringifyOptions{});
|
||||
try teststringify("42", 42, StringifyOptions{});
|
||||
try teststringify("4.2e1", 42.0, StringifyOptions{});
|
||||
try teststringify("4.2e+01", 42.0, StringifyOptions{});
|
||||
try teststringify("42", @as(u8, 42), StringifyOptions{});
|
||||
try teststringify("42", @as(u128, 42), StringifyOptions{});
|
||||
try teststringify("4.2e1", @as(f32, 42), StringifyOptions{});
|
||||
try teststringify("4.2e1", @as(f64, 42), StringifyOptions{});
|
||||
try teststringify("4.2e+01", @as(f32, 42), StringifyOptions{});
|
||||
try teststringify("4.2e+01", @as(f64, 42), StringifyOptions{});
|
||||
try teststringify("\"ItBroke\"", @as(anyerror, error.ItBroke), StringifyOptions{});
|
||||
}
|
||||
|
||||
|
|
15
src/main.zig
15
src/main.zig
|
@ -38,8 +38,8 @@ pub fn log(
|
|||
nosuspend stderr.print(prefix ++ format ++ "\n", args) catch return;
|
||||
}
|
||||
|
||||
pub const std_options = std.Options{
|
||||
.logFn = log,
|
||||
pub const std_options = struct {
|
||||
pub const logFn = log;
|
||||
};
|
||||
const Tests = enum {
|
||||
query_no_input,
|
||||
|
@ -71,7 +71,7 @@ pub fn main() anyerror!void {
|
|||
defer bw.flush() catch unreachable;
|
||||
const stdout = bw.writer();
|
||||
var arg0: ?[]const u8 = null;
|
||||
var proxy: ?std.http.Client.Proxy = null;
|
||||
var proxy: ?std.http.Client.HttpProxy = null;
|
||||
while (args.next()) |arg| {
|
||||
if (arg0 == null) arg0 = arg;
|
||||
if (std.mem.eql(u8, "-h", arg) or std.mem.eql(u8, "--help", arg)) {
|
||||
|
@ -353,22 +353,17 @@ pub fn main() anyerror!void {
|
|||
std.log.info("===== Tests complete =====", .{});
|
||||
}
|
||||
|
||||
fn proxyFromString(string: []const u8) !std.http.Client.Proxy {
|
||||
var rc = std.http.Client.Proxy{
|
||||
fn proxyFromString(string: []const u8) !std.http.Client.HttpProxy {
|
||||
var rc = std.http.Client.HttpProxy{
|
||||
.protocol = undefined,
|
||||
.host = undefined,
|
||||
.authorization = null,
|
||||
.port = undefined,
|
||||
.supports_connect = true, // TODO: Is this a good default?
|
||||
};
|
||||
var remaining: []const u8 = string;
|
||||
if (std.mem.startsWith(u8, string, "http://")) {
|
||||
remaining = remaining["http://".len..];
|
||||
rc.protocol = .plain;
|
||||
rc.port = 80;
|
||||
} else if (std.mem.startsWith(u8, string, "https://")) {
|
||||
remaining = remaining["https://".len..];
|
||||
rc.port = 443;
|
||||
rc.protocol = .tls;
|
||||
} else return error.InvalidScheme;
|
||||
var split_iterator = std.mem.split(u8, remaining, ":");
|
||||
|
|
|
@ -21,7 +21,7 @@ pub fn Services(comptime service_imports: anytype) type {
|
|||
// finally, generate the type
|
||||
return @Type(.{
|
||||
.Struct = .{
|
||||
.layout = .auto,
|
||||
.layout = .Auto,
|
||||
.fields = &fields,
|
||||
.decls = &[_]std.builtin.Type.Declaration{},
|
||||
.is_tuple = false,
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
const std = @import("std");
|
||||
|
||||
fn defaultTransformer(allocator: std.mem.Allocator, field_name: []const u8) anyerror![]const u8 {
|
||||
fn defaultTransformer(allocator: std.mem.Allocator, field_name: []const u8, options: EncodingOptions) anyerror![]const u8 {
|
||||
_ = options;
|
||||
_ = allocator;
|
||||
return field_name;
|
||||
}
|
||||
|
||||
pub const fieldNameTransformerFn = *const fn (std.mem.Allocator, []const u8) anyerror![]const u8;
|
||||
pub const fieldNameTransformerFn = *const fn (std.mem.Allocator, []const u8, EncodingOptions) anyerror![]const u8;
|
||||
|
||||
pub const EncodingOptions = struct {
|
||||
field_name_transformer: fieldNameTransformerFn = defaultTransformer,
|
||||
field_name_transformer: fieldNameTransformerFn = &defaultTransformer,
|
||||
};
|
||||
|
||||
pub fn encode(allocator: std.mem.Allocator, obj: anytype, writer: anytype, comptime options: EncodingOptions) !void {
|
||||
|
@ -25,7 +26,7 @@ fn encodeStruct(
|
|||
) !bool {
|
||||
var rc = first;
|
||||
inline for (@typeInfo(@TypeOf(obj)).Struct.fields) |field| {
|
||||
const field_name = try options.field_name_transformer(allocator, field.name);
|
||||
const field_name = try options.field_name_transformer(allocator, field.name, options);
|
||||
defer if (options.field_name_transformer.* != defaultTransformer)
|
||||
allocator.free(field_name);
|
||||
// @compileLog(@typeInfo(field.field_type).Pointer);
|
||||
|
|
|
@ -219,9 +219,9 @@ fn parseInternal(comptime T: type, element: *xml.Element, options: ParseOptions)
|
|||
|
||||
log.debug("Processing fields in struct: {s}", .{@typeName(T)});
|
||||
inline for (struct_info.fields, 0..) |field, i| {
|
||||
var name: []const u8 = field.name;
|
||||
var name = field.name;
|
||||
var found_value = false;
|
||||
if (comptime std.meta.hasFn(T, "fieldNameFor"))
|
||||
if (comptime std.meta.trait.hasFn("fieldNameFor")(T))
|
||||
name = r.fieldNameFor(field.name);
|
||||
log.debug("Field name: {s}, Element: {s}, Adjusted field name: {s}", .{ field.name, element.tag, name });
|
||||
var iterator = element.findChildrenByTag(name);
|
||||
|
|
Loading…
Reference in New Issue
Block a user