Compare commits

...

24 commits

Author SHA1 Message Date
abd422edb7
changes for zig 0.16.0
Some checks failed
aws-zig nightly build / build-zig-nightly (push) Failing after 23m34s
2026-04-16 16:22:48 -07:00
a3d0718e7b
Merge branch 'master' into zig-develop 2026-04-16 09:30:43 -07:00
1a03250fbe
remove special exception for 404 (not found) responses
All checks were successful
AWS-Zig Build / build-zig-amd64-host (push) Successful in 15m30s
These will now be handled just like any other error,
allowing downstream to catch and deal with through
diagnostics (that did not exist when that code was
written)
2026-02-05 12:49:11 -08:00
45d9a369ae
update versions
All checks were successful
AWS-Zig Build / build-zig-amd64-host (push) Successful in 8m28s
2026-02-04 16:49:17 -08:00
5c7aed071f
switch to http status enum
All checks were successful
AWS-Zig Build / build-zig-amd64-host (push) Successful in 8m10s
2026-02-04 00:37:27 -08:00
c1df6ef3a6
avoid outputting errors if diagnostics field provided
All checks were successful
AWS-Zig Build / build-zig-amd64-host (push) Successful in 8m13s
2026-02-03 15:30:59 -08:00
fd568f26b9
refactor rest calls so all fields are included appropriately
Some checks failed
AWS-Zig Build / build-zig-amd64-host (push) Failing after 13m25s
2026-02-02 18:00:00 -08:00
4df27142d0
fix issue with config file reading the wrong options
Some checks failed
AWS-Zig Build / build-zig-amd64-host (push) Failing after 14m17s
2026-02-02 17:11:44 -08:00
31240cd21e
provide consumers a way to change order of precedence based on cli flag 2026-02-02 17:11:18 -08:00
686b18d1f4
use profile_name in credential options
All checks were successful
AWS-Zig Build / build-zig-amd64-host (push) Successful in 8m6s
2026-02-02 16:13:09 -08:00
742a820eeb
add credential options to aws options so profile can be passed 2026-02-02 16:11:03 -08:00
6e34e83933
do not emit null optional fields
All checks were successful
AWS-Zig Build / build-zig-amd64-host (push) Successful in 15m4s
2026-02-01 17:47:51 -08:00
fdc2089969
remove build.zig aws constant
All checks were successful
AWS-Zig Build / build-zig-amd64-host (push) Successful in 8m5s
This was always problematic, and bypasses the build system. It broke earlier
when moving to proper modules, and really, it is not worth using. The idea was
to allow usage of the SDK in a build context, but that does not work anyway,
because the build operates in a sandboxed environment that effectively bars
things like connecting to TLS endpoints. That is a feature of the build system,
not a bug, and issues like https://github.com/ziglang/zig/issues/14286
demonstrate that the zig team wants to sandbox even further. For downstream,
the right idea here is actually to create an executable that depends on aws
and run it as part of the build. This is where https://git.lerch.org/lobo/lambda-zig
is heading.
2026-01-30 12:04:30 -08:00
f788eed35c
update example 2026-01-30 11:58:27 -08:00
b4eddb6f82
update example 2026-01-30 10:01:52 -08:00
efdef66fdb
bifrucate the service_model module based pre-packaging
All checks were successful
AWS-Zig Build / build-zig-amd64-host (push) Successful in 1m2s
2026-01-30 09:38:10 -08:00
615f92c654
glob expansion needs to be in target dir
All checks were successful
AWS-Zig Build / build-zig-amd64-host (push) Successful in 15m54s
2026-01-30 08:41:06 -08:00
4fed9954cb
avoid ./ prefix on tar paths
Some checks failed
AWS-Zig Build / build-zig-amd64-host (push) Failing after 7m41s
See https://github.com/ziglang/zig/issues/23152
2026-01-30 08:31:16 -08:00
df963c5316
fix project packaging
All checks were successful
AWS-Zig Build / build-zig-amd64-host (push) Successful in 8m2s
I believe this broke in 4a6c84e, but have not bisected.
2026-01-29 15:35:11 -08:00
d4fdd74f5b
uncomment pub const aws in build.zig as this seems to be fixed in 0.15.2
All checks were successful
AWS-Zig Build / build-zig-amd64-host (push) Successful in 9m12s
2026-01-29 15:06:28 -08:00
6d80c2f56b
remove message about zig nightly - issue is resolved
All checks were successful
AWS-Zig Build / build-zig-amd64-host (push) Successful in 7m28s
2025-11-15 11:22:09 -08:00
0dc0154c16
Revert "temporary force nightly to home server"
All checks were successful
AWS-Zig Build / build-zig-amd64-host (push) Successful in 8m46s
This reverts commit 79213a991d.
2025-11-15 11:12:25 -08:00
79213a991d
temporary force nightly to home server
All checks were successful
AWS-Zig Build / build-zig-amd64-host (push) Successful in 7m46s
On the home server, git.lerch.org will resolve to
an A record (split-horizon DNS). This works around
https://github.com/ziglang/zig/issues/25811.
2025-11-06 13:08:21 -08:00
0e63e501db
add note about nightly build
All checks were successful
AWS-Zig Build / build-zig-amd64-host (push) Successful in 7m48s
2025-11-06 12:37:30 -08:00
27 changed files with 589 additions and 356 deletions

View file

@ -18,7 +18,7 @@ jobs:
- name: Check out repository code
uses: actions/checkout@v4
- name: Setup Zig
uses: https://github.com/mlugg/setup-zig@v2.0.5
uses: https://codeberg.org/mlugg/setup-zig@v2.2.1
# We will let setup-zig use minimum_zig_version from build.zig.zon
# setup-zig also sets up the zig cache appropriately
- name: Ulimit
@ -44,11 +44,8 @@ jobs:
# should be using git archive, but we need our generated code to be part of it
- name: Package source code with generated models
run: |
sed -i 's#// UNCOMMENT AFTER MODEL GEN TO USE IN BUILD SCRIPTS //##' build.zig
tar -czf ${{ runner.temp }}/${{ github.sha }}-with-models.tar.gz \
--format ustar \
--exclude 'zig-*' \
*
zig build package
(cd zig-out/package && tar -czf ${{ runner.temp }}/${{ github.sha }}-with-models.tar.gz --format ustar *)
# Something in this PR broke this transform. I don't mind removing it, but
# the PR attempts to handle situations with or without a prefix, but it
# doesn't. I have not yet determined what the problem is, though

View file

@ -26,7 +26,7 @@ jobs:
with:
ref: zig-mach
- name: Setup Zig
uses: https://github.com/mlugg/setup-zig@v2.0.1
uses: https://codeberg.org/mlugg/setup-zig@v2.2.1
with:
version: mach-latest
- name: Restore Zig caches

View file

@ -26,7 +26,7 @@ jobs:
with:
ref: zig-develop
- name: Setup Zig
uses: https://github.com/mlugg/setup-zig@v2.0.5
uses: https://codeberg.org/mlugg/setup-zig@v2.2.1
with:
version: master
- name: Run smoke test

View file

@ -20,7 +20,7 @@ jobs:
with:
ref: zig-0.14.x
- name: Setup Zig
uses: https://github.com/mlugg/setup-zig@v2.0.1
uses: https://codeberg.org/mlugg/setup-zig@v2.2.1
with:
version: 0.14.0
- name: Run smoke test

1
.gitignore vendored
View file

@ -12,3 +12,4 @@ src/git_version.zig
zig-out
core
.zig-cache
zig-pkg/

View file

@ -1,5 +1,5 @@
[tools]
pre-commit = "latest"
"ubi:DonIsaac/zlint" = "latest"
zig = "master"
zls = "0.15.0"
prek = "0.3.1"
"ubi:DonIsaac/zlint" = "0.7.9"
zig = "0.16.0"
zls = "0.15.1"

View file

@ -2,7 +2,7 @@
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer

208
build.zig
View file

@ -3,8 +3,6 @@ const Builder = @import("std").Build;
const models_subdir = "codegen/sdk-codegen/aws-models/"; // note will probably not work on windows
// UNCOMMENT AFTER MODEL GEN TO USE IN BUILD SCRIPTS //pub const aws = @import("src/aws.zig");
const test_targets = [_]std.Target.Query{
.{}, // native
.{ .cpu_arch = .x86_64, .os_tag = .linux },
@ -49,7 +47,7 @@ pub fn build(b: *Builder) !void {
.target = target,
.optimize = optimize,
});
configure(mod_exe, dep_mods, true);
configure(mod_exe, dep_mods);
const exe = b.addExecutable(.{
.name = "demo",
@ -74,7 +72,7 @@ pub fn build(b: *Builder) !void {
.target = b.graph.host,
.optimize = if (b.verbose) .Debug else .ReleaseSafe,
});
configure(cg_mod, dep_mods, false);
configure(cg_mod, dep_mods);
const cg_exe = b.addExecutable(.{
.name = "codegen",
@ -114,16 +112,45 @@ pub fn build(b: *Builder) !void {
cg.dependOn(&cg_cmd.step);
exe.step.dependOn(cg);
// Each module will need access to the generated AWS modules. These
// are all imported by service_manifest.zig, which is a generated list
// of services created by the codegen process.
//
// First, we need to check if pre-generated models exist, which only happens
// for packaged distribution.
//
// The idea here is that if we have a packaged distibution (tarball with
// models available, we are pre-generated, do not need the codegen step
// (and in fact do not have that available), and our service_manifest
// module needs to be the pre-packaged file.
//
// If we do not have a packaged distribution, the file will not exist,
// because it is generated by codegen and will live in the zig cache directory,
// so we depend on the codegen step and the service_manifest module will
// be based on the codegen output itself.
//
// Most of this complication comes from the fact that we want to enable
// consuming build.zig files to be able to use the SDK at build time for
// things like code deployments, e.g. https://git.lerch.org/lobo/lambda-zig
const has_pre_generated =
if (b.build_root.handle.access(b.graph.io, "src/models/service_manifest.zig", .{})) true else |_| false;
// Only depend on codegen if we don't have pre-generated models
if (!has_pre_generated)
exe.step.dependOn(cg);
// Use pre-generated models if available, otherwise use codegen output
const service_manifest_source: std.Build.LazyPath = if (has_pre_generated)
b.path("src/models/service_manifest.zig")
else
cg_output_dir.path(b, "service_manifest.zig");
// This allows us to have each module depend on the
// generated service manifest.
const service_manifest_module = b.createModule(.{
.root_source_file = cg_output_dir.path(b, "service_manifest.zig"),
.root_source_file = service_manifest_source,
.target = target,
.optimize = optimize,
});
configure(service_manifest_module, dep_mods, true);
configure(service_manifest_module, dep_mods);
mod_exe.addImport("service_manifest", service_manifest_module);
@ -134,13 +161,13 @@ pub fn build(b: *Builder) !void {
.optimize = optimize,
});
mod_aws.addImport("service_manifest", service_manifest_module);
configure(mod_aws, dep_mods, true);
configure(mod_aws, dep_mods);
// Expose module to others
const mod_aws_signing = b.addModule("aws-signing", .{
.root_source_file = b.path("src/aws_signing.zig"),
});
configure(mod_aws_signing, dep_mods, false);
configure(mod_aws_signing, dep_mods);
// Similar to creating the run step earlier, this exposes a `test` step to
// the `zig build --help` menu, providing a way for the user to request
@ -170,7 +197,7 @@ pub fn build(b: *Builder) !void {
.optimize = optimize,
});
mod_unit_tests.addImport("service_manifest", service_manifest_module);
configure(mod_unit_tests, dep_mods, true);
configure(mod_unit_tests, dep_mods);
// Creates a step for unit testing. This only builds the test executable
// but does not run it.
@ -179,7 +206,8 @@ pub fn build(b: *Builder) !void {
.filters = test_filters,
});
unit_tests.step.dependOn(cg);
if (!has_pre_generated)
unit_tests.step.dependOn(cg);
unit_tests.use_llvm = !no_llvm;
const run_unit_tests = b.addRunArtifact(unit_tests);
@ -202,7 +230,8 @@ pub fn build(b: *Builder) !void {
.filters = test_filters,
});
smoke_test.use_llvm = !no_llvm;
smoke_test.step.dependOn(cg);
if (!has_pre_generated)
smoke_test.step.dependOn(cg);
const run_smoke_test = b.addRunArtifact(smoke_test);
@ -212,14 +241,20 @@ pub fn build(b: *Builder) !void {
} else {
b.installArtifact(exe);
}
// Package step - creates distribution source directory
const pkg_step = PackageStep.create(b, cg_output_dir);
pkg_step.step.dependOn(cg);
const package = b.step("package", "Copy code to zig-out/package with generated models");
package.dependOn(&pkg_step.step);
}
fn configure(compile: *std.Build.Module, modules: std.StringHashMap(*std.Build.Module), include_time: bool) void {
fn configure(compile: *std.Build.Module, modules: std.StringHashMap(*std.Build.Module)) void {
compile.addImport("smithy", modules.get("smithy").?);
compile.addImport("date", modules.get("date").?);
compile.addImport("json", modules.get("json").?);
compile.addImport("case", modules.get("case").?);
if (include_time) compile.addImport("zeit", modules.get("zeit").?);
}
fn getDependencyModules(b: *std.Build, args: anytype) !std.StringHashMap(*std.Build.Module) {
@ -230,10 +265,6 @@ fn getDependencyModules(b: *std.Build, args: anytype) !std.StringHashMap(*std.Bu
const mod_smithy = dep_smithy.module("smithy");
try result.putNoClobber("smithy", mod_smithy);
const dep_zeit = b.dependency("zeit", args);
const mod_zeit = dep_zeit.module("zeit");
try result.putNoClobber("zeit", mod_zeit);
const dep_case = b.dependency("case", args);
const mod_case = dep_case.module("case");
try result.putNoClobber("case", mod_case);
@ -251,3 +282,140 @@ fn getDependencyModules(b: *std.Build, args: anytype) !std.StringHashMap(*std.Bu
return result;
}
/// Custom build step that creates a distribution source directory
/// This copies all source files plus the generated service models into a
/// package directory suitable for distribution
const PackageStep = struct {
step: std.Build.Step,
cg_output_dir: std.Build.LazyPath,
const base_id: std.Build.Step.Id = .custom;
/// Files to include in the package (relative to build root)
const package_files = [_][]const u8{
"build.zig",
"build.zig.zon",
"README.md",
"LICENSE",
};
/// Directories to include in the package (relative to build root)
const package_dirs = [_][]const u8{
"src",
"lib",
};
pub fn create(owner: *std.Build, cg_output_dir: std.Build.LazyPath) *PackageStep {
const self = owner.allocator.create(PackageStep) catch @panic("OOM");
self.* = .{
.step = std.Build.Step.init(.{
.id = base_id,
.name = "copy generated files",
.owner = owner,
.makeFn = make,
}),
.cg_output_dir = cg_output_dir,
};
return self;
}
fn make(step: *std.Build.Step, options: std.Build.Step.MakeOptions) anyerror!void {
_ = options;
const self: *PackageStep = @fieldParentPtr("step", step);
const b = step.owner;
const io = b.graph.io;
// Get the path to generated models
const models_path = self.cg_output_dir.getPath2(b, &self.step);
// Create output directory for packaging
const package_dir = b.pathJoin(&.{ "zig-out", "package" });
const models_dest_dir = b.pathJoin(&.{ package_dir, "src", "models" });
std.Io.Dir.cwd().createDirPath(io, models_dest_dir) catch |err| {
return step.fail("Failed to create package directory: {}", .{err});
};
// Copy all source files to package directory
for (package_files) |file_name|
copyFile(io, b, b.build_root.handle, file_name, package_dir) catch {};
// Copy directories
for (package_dirs) |dir_name|
copyDirRecursive(io, b, b.build_root.handle, dir_name, package_dir) catch |err| {
return step.fail("Failed to copy directory '{s}': {}", .{ dir_name, err });
};
// Copy generated models to src/models/
copyGeneratedModels(b, models_path, models_dest_dir) catch |err| {
return step.fail("Failed to copy generated models: {}", .{err});
};
step.result_cached = false;
}
fn copyFile(io: std.Io, b: *std.Build, src_dir: std.Io.Dir, file_path: []const u8, dest_prefix: []const u8) !void {
const dest_path = b.pathJoin(&.{ dest_prefix, file_path });
// Ensure parent directory exists
if (std.fs.path.dirname(dest_path)) |parent|
std.Io.Dir.cwd().createDirPath(io, parent) catch {};
src_dir.copyFile(file_path, std.Io.Dir.cwd(), dest_path, io, .{}) catch return;
}
fn copyDirRecursive(io: std.Io, b: *std.Build, src_base: std.Io.Dir, dir_path: []const u8, dest_prefix: []const u8) !void {
var src_dir = src_base.openDir(io, dir_path, .{ .iterate = true }) catch return;
defer src_dir.close(io);
var walker = try src_dir.walk(b.allocator);
defer walker.deinit();
while (try walker.next(io)) |entry| {
// Skip zig build artifact directories
if (std.mem.indexOf(u8, entry.path, "zig-out") != null or
std.mem.indexOf(u8, entry.path, ".zig-cache") != null or
std.mem.indexOf(u8, entry.path, "zig-cache") != null)
continue;
const src_path = b.pathJoin(&.{ dir_path, entry.path });
const dest_path = b.pathJoin(&.{ dest_prefix, dir_path, entry.path });
switch (entry.kind) {
.directory => std.Io.Dir.cwd().createDirPath(io, dest_path) catch {},
.file => {
// Ensure parent directory exists
if (std.fs.path.dirname(dest_path)) |parent| {
std.Io.Dir.cwd().createDirPath(io, parent) catch {};
}
src_base.copyFile(src_path, std.Io.Dir.cwd(), dest_path, io, .{}) catch {};
},
.sym_link => {
var link_buf: [std.fs.max_path_bytes]u8 = undefined;
const link_target = entry.dir.readLink(io, entry.basename, &link_buf) catch continue;
// Ensure parent directory exists
if (std.fs.path.dirname(dest_path)) |parent| {
std.Io.Dir.cwd().createDirPath(io, parent) catch {};
}
std.Io.Dir.cwd().symLink(io, link_buf[0..link_target], dest_path, .{}) catch {};
},
else => {},
}
}
}
fn copyGeneratedModels(b: *std.Build, models_path: []const u8, models_dest_dir: []const u8) !void {
const io = b.graph.io;
var models_dir = std.Io.Dir.cwd().openDir(io, models_path, .{ .iterate = true }) catch
return error.ModelsNotFound;
defer models_dir.close(io);
var iter = models_dir.iterate();
while (try iter.next(io)) |entry| {
if (entry.kind != .file) continue;
const dest_path = b.pathJoin(&.{ models_dest_dir, entry.name });
models_dir.copyFile(entry.name, std.Io.Dir.cwd(), dest_path, io, .{}) catch continue;
}
}
};

View file

@ -11,7 +11,7 @@
"README.md",
"LICENSE",
},
.minimum_zig_version = "0.15.1",
.minimum_zig_version = "0.16.0",
.dependencies = .{
.smithy = .{
@ -29,12 +29,8 @@
.path = "lib/json",
},
.case = .{
.url = "git+https://github.com/travisstaloch/case.git#f8003fe5f93b65f673d10d41323e347225e8cb87",
.hash = "case-0.0.1-chGYqx_EAADaGJjmoln5M1iMBDTrMdd8to5wdEVpfXm4",
},
.zeit = .{
.url = "git+https://github.com/elerch/zeit#8190461dc1f892f6370fa9d5cd76690aac0e1c71",
.hash = "zeit-0.6.0-5I6bk99-AgDNMIDuw2Zcoe_9QYIpzwZJqeqMpU54egTd",
.url = "git+https://github.com/elerch/case?ref=zig-0.16.0#82017a92e179031f21896d02262ae1e216459e4f",
.hash = "case-0.0.1-chGYq1fEAAAN4h3YRmkh9OTDvuUyjmoXr6PhYAUzjU0D",
},
},
}

View file

@ -39,7 +39,7 @@ pub fn indent(self: @This()) GenerationState {
pub fn deindent(self: @This()) GenerationState {
var new_state = self.clone();
new_state.indent_level = @max(0, new_state.indent_level - 1);
new_state.indent_level = if (new_state.indent_level == 0) 0 else new_state.indent_level - 1;
return new_state;
}

View file

@ -8,7 +8,7 @@ pub const HashedFile = struct {
hash: [Hash.digest_length]u8,
failure: Error!void,
const Error = std.fs.File.OpenError || std.fs.File.ReadError || std.fs.File.StatError;
const Error = std.Io.File.OpenError || std.Io.File.ReadStreamingError || std.Io.File.StatError;
fn lessThan(context: void, lhs: *const HashedFile, rhs: *const HashedFile) bool {
_ = context;
@ -76,13 +76,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.Io.Dir.Walker.Entry) bool;
fn included(entry: std.fs.Dir.Walker.Entry) bool {
fn included(entry: std.Io.Dir.Walker.Entry) bool {
_ = entry;
return true;
}
fn excluded(entry: std.fs.Dir.Walker.Entry) bool {
fn excluded(entry: std.Io.Dir.Walker.Entry) bool {
_ = entry;
return false;
}
@ -94,33 +94,33 @@ pub const ComputeDirectoryOptions = struct {
};
pub fn computeDirectoryHash(
thread_pool: *std.Thread.Pool,
dir: std.fs.Dir,
allocator: std.mem.Allocator,
io: std.Io,
dir: std.Io.Dir,
options: *ComputeDirectoryOptions,
) ![Hash.digest_length]u8 {
const gpa = thread_pool.allocator;
// We'll use an arena allocator for the path name strings since they all
// need to be in memory for sorting.
var arena_instance = std.heap.ArenaAllocator.init(gpa);
var arena_instance = std.heap.ArenaAllocator.init(allocator);
defer arena_instance.deinit();
const arena = arena_instance.allocator();
// Collect all files, recursively, then sort.
// Normally we're looking at around 300 model files
var all_files = try std.ArrayList(*HashedFile).initCapacity(gpa, 300);
defer all_files.deinit(gpa);
var all_files = try std.ArrayList(*HashedFile).initCapacity(allocator, 300);
defer all_files.deinit(allocator);
var walker = try dir.walk(gpa);
var walker = try dir.walk(allocator);
defer walker.deinit();
{
// The final hash will be a hash of each file hashed independently. This
// allows hashing in parallel.
var wait_group: std.Thread.WaitGroup = .{};
defer wait_group.wait();
var g: std.Io.Group = .init;
errdefer g.cancel(io);
while (try walker.next()) |entry| {
while (try walker.next(io)) |entry| {
switch (entry.kind) {
.directory => continue,
.file => {},
@ -128,7 +128,7 @@ pub fn computeDirectoryHash(
}
if (options.isExcluded(entry) or !options.isIncluded(entry))
continue;
const alloc = if (options.needFileHashes) gpa else arena;
const alloc = if (options.needFileHashes) allocator else arena;
const hashed_file = try alloc.create(HashedFile);
const fs_path = try alloc.dupe(u8, entry.path);
hashed_file.* = .{
@ -137,11 +137,11 @@ pub fn computeDirectoryHash(
.hash = undefined, // to be populated by the worker
.failure = undefined, // to be populated by the worker
};
wait_group.start();
try thread_pool.spawn(workerHashFile, .{ dir, hashed_file, &wait_group });
g.async(io, workerHashFile, .{ io, dir, hashed_file, &g });
try all_files.append(gpa, hashed_file);
try all_files.append(allocator, hashed_file);
}
try g.await(io);
}
std.mem.sort(*HashedFile, all_files.items, {}, HashedFile.lessThan);
@ -156,23 +156,26 @@ pub fn computeDirectoryHash(
hasher.update(&hashed_file.hash);
}
if (any_failures) return error.DirectoryHashUnavailable;
if (options.needFileHashes) options.fileHashes = try all_files.toOwnedSlice(gpa);
if (options.needFileHashes) options.fileHashes = try all_files.toOwnedSlice(allocator);
return hasher.finalResult();
}
fn workerHashFile(dir: std.fs.Dir, hashed_file: *HashedFile, wg: *std.Thread.WaitGroup) void {
defer wg.finish();
hashed_file.failure = hashFileFallible(dir, hashed_file);
fn workerHashFile(io: std.Io, dir: std.Io.Dir, hashed_file: *HashedFile, wg: *std.Io.Group) void {
_ = wg; // assume here that 0.16.0 Io.Group no longer needs to be notified at the time of completion
hashed_file.failure = hashFileFallible(io, dir, hashed_file);
}
fn hashFileFallible(dir: std.fs.Dir, hashed_file: *HashedFile) HashedFile.Error!void {
fn hashFileFallible(io: std.Io, dir: std.Io.Dir, hashed_file: *HashedFile) HashedFile.Error!void {
var buf: [8000]u8 = undefined;
var file = try dir.openFile(hashed_file.fs_path, .{});
defer file.close();
var file = try dir.openFile(io, hashed_file.fs_path, .{});
defer file.close(io);
var hasher = Hash.init(.{});
hasher.update(hashed_file.normalized_path);
hasher.update(&.{ 0, @intFromBool(try isExecutable(file)) });
hasher.update(&.{ 0, @intFromBool(try isExecutable(io, file)) });
while (true) {
const bytes_read = try file.read(&buf);
const bytes_read = file.readStreaming(io, &.{&buf}) catch |err| switch (err) {
error.EndOfStream => break,
else => return err,
};
if (bytes_read == 0) break;
hasher.update(buf[0..bytes_read]);
}
@ -197,7 +200,7 @@ fn normalizePath(arena: std.mem.Allocator, fs_path: []const u8) ![]const u8 {
return normalized;
}
fn isExecutable(file: std.fs.File) !bool {
fn isExecutable(io: std.Io, file: std.Io.File) !bool {
if (builtin.os.tag == .windows) {
// TODO check the ACL on Windows.
// Until this is implemented, this could be a false negative on
@ -205,7 +208,7 @@ fn isExecutable(file: std.fs.File) !bool {
// when unpacking the tarball.
return false;
} else {
const stat = try file.stat();
return (stat.mode & std.posix.S.IXUSR) != 0;
const stat = try file.stat(io);
return stat.kind == .file and (stat.permissions.toMode() & std.posix.S.IXUSR != 0);
}
}

View file

@ -25,27 +25,21 @@ const next_version = std.SemanticVersion.parse(next_version_str) catch unreachab
const zig_version = @import("builtin").zig_version;
const is_next = zig_version.order(next_version) == .eq or zig_version.order(next_version) == .gt;
pub fn main() anyerror!void {
const root_progress_node = std.Progress.start(.{});
pub fn main(init: std.process.Init) anyerror!void {
const io = init.io;
const root_progress_node = std.Progress.start(io, .{});
defer root_progress_node.end();
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
const allocator = init.arena.allocator();
var threaded: std.Io.Threaded = .init(allocator);
defer threaded.deinit();
const io = threaded.io();
const args = try std.process.argsAlloc(allocator);
defer std.process.argsFree(allocator, args);
var stdout_writer = std.fs.File.stdout().writer(&.{});
const args = try init.minimal.args.toSlice(allocator);
var stdout_writer = std.Io.File.stdout().writer(io, &.{});
const stdout = &stdout_writer.interface;
var output_dir = std.fs.cwd();
defer if (output_dir.fd > 0) output_dir.close();
var models_dir: ?std.fs.Dir = null;
defer if (models_dir) |*m| m.close();
var output_dir = std.Io.Dir.cwd();
defer if (output_dir.handle > 0) output_dir.close(io);
var models_dir: ?std.Io.Dir = null;
defer if (models_dir) |*m| m.close(io);
for (args, 0..) |arg, i| {
if (std.mem.eql(u8, "--help", arg) or
std.mem.eql(u8, "-h", arg))
@ -55,15 +49,17 @@ pub fn main() anyerror!void {
try stdout.print(" --output specifies an output directory, otherwise the current working directory will be used\n", .{});
std.process.exit(0);
}
if (std.mem.eql(u8, "--output", arg))
output_dir = try output_dir.makeOpenPath(args[i + 1], .{});
if (std.mem.eql(u8, "--output", arg)) {
try output_dir.createDirPath(io, args[i + 1]);
output_dir = try std.Io.Dir.cwd().openDir(io, args[i + 1], .{ .iterate = true });
}
if (std.mem.eql(u8, "--models", arg))
models_dir = try std.fs.cwd().openDir(args[i + 1], .{ .iterate = true });
models_dir = try std.Io.Dir.cwd().openDir(io, args[i + 1], .{ .iterate = true });
}
var manifest_file = try output_dir.createFile("service_manifest.zig", .{});
defer manifest_file.close();
var manifest = manifest_file.writer(&manifest_buf).interface;
var manifest_file = try output_dir.createFile(io, "service_manifest.zig", .{});
defer manifest_file.close(io);
var manifest = manifest_file.writer(io, &manifest_buf).interface;
defer manifest.flush() catch @panic("Could not flush service manifest");
var files_processed: usize = 0;
var skip_next = true;
@ -92,20 +88,20 @@ pub fn main() anyerror!void {
// can be made
if (models_dir) |m| {
var cwd = try std.fs.cwd().openDir(".", .{});
defer cwd.close();
defer cwd.setAsCwd() catch unreachable;
var cwd = try std.Io.Dir.cwd().openDir(io, ".", .{});
defer cwd.close(io);
defer std.process.setCurrentDir(io, cwd) catch unreachable;
try m.setAsCwd();
try std.process.setCurrentDir(io, m);
try processDirectories(io, m, output_dir, &root_progress_node);
}
}
if (args.len == 0)
_ = try generateServices(allocator, io, ";", std.fs.File.stdin(), stdout);
_ = try generateServices(allocator, io, ";", std.Io.File.stdin(), stdout);
if (verbose) {
const output_path = try output_dir.realpathAlloc(allocator, ".");
const output_path = try output_dir.realPathFileAlloc(io, ".", allocator);
std.debug.print("Output path: {s}\n", .{output_path});
}
}
@ -114,25 +110,23 @@ const OutputManifest = struct {
model_dir_hash_digest: [Hasher.hex_multihash_len]u8,
output_dir_hash_digest: [Hasher.hex_multihash_len]u8,
};
fn processDirectories(io: std.Io, models_dir: std.fs.Dir, output_dir: std.fs.Dir, parent_progress: *const std.Progress.Node) !void {
fn processDirectories(io: std.Io, models_dir: std.Io.Dir, output_dir: std.Io.Dir, parent_progress: *const std.Progress.Node) !void {
// Let's get ready to hash!!
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
var thread_pool: std.Thread.Pool = undefined;
try thread_pool.init(.{ .allocator = allocator });
defer thread_pool.deinit();
const count, var calculated_manifest =
try calculateDigests(
allocator,
io,
models_dir,
output_dir,
&thread_pool,
);
const output_stored_manifest = if (is_next)
output_dir.readFileAlloc("output_manifest.json", allocator, .unlimited) catch null
output_dir.readFileAlloc(io, "output_manifest.json", allocator, .unlimited) catch null
else
output_dir.readFileAlloc(allocator, "output_manifest.json", std.math.maxInt(usize)) catch null;
output_dir.readFileAlloc(io, allocator, "output_manifest.json", std.math.maxInt(usize)) catch null;
if (output_stored_manifest) |o| {
// we have a stored manifest. Parse it and compare to our calculations
// we can leak as we're using an arena allocator
@ -147,16 +141,16 @@ fn processDirectories(io: std.Io, models_dir: std.fs.Dir, output_dir: std.fs.Dir
}
}
// Do this in a brain dead fashion from here, no optimization
const manifest_file = try output_dir.createFile("service_manifest.zig", .{});
defer manifest_file.close();
var manifest = manifest_file.writer(&manifest_buf);
const manifest_file = try output_dir.createFile(io, "service_manifest.zig", .{});
defer manifest_file.close(io);
var manifest = manifest_file.writer(io, &manifest_buf);
defer manifest.interface.flush() catch @panic("Error flushing service_manifest.zig");
var mi = models_dir.iterate();
const generating_models_progress = parent_progress.start("generating models", count);
defer generating_models_progress.end();
while (try mi.next()) |e| {
while (try mi.next(io)) |e| {
if ((e.kind == .file or e.kind == .sym_link) and std.mem.endsWith(u8, e.name, ".json")) {
try processFile(io, e.name, output_dir, &manifest.interface);
generating_models_progress.completeOne();
@ -164,26 +158,26 @@ fn processDirectories(io: std.Io, models_dir: std.fs.Dir, output_dir: std.fs.Dir
}
// re-calculate so we can store the manifest
model_digest = calculated_manifest.model_dir_hash_digest;
_, calculated_manifest = try calculateDigests(models_dir, output_dir, &thread_pool);
_, calculated_manifest = try calculateDigests(allocator, io, models_dir, output_dir);
const data = try std.fmt.allocPrint(allocator, "{f}", .{std.json.fmt(calculated_manifest, .{ .whitespace = .indent_2 })});
try output_dir.writeFile(.{ .sub_path = "output_manifest.json", .data = data });
try output_dir.writeFile(io, .{ .sub_path = "output_manifest.json", .data = data });
}
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) !struct { usize, OutputManifest } {
fn calculateDigests(allocator: std.mem.Allocator, io: std.Io, models_dir: std.Io.Dir, output_dir: std.Io.Dir) !struct { usize, OutputManifest } {
const Include = struct {
threadlocal var count: usize = 0;
pub fn include(entry: std.fs.Dir.Walker.Entry) bool {
pub fn include(entry: std.Io.Dir.Walker.Entry) bool {
const included = std.mem.endsWith(u8, entry.basename, ".json");
if (included) count += 1;
return included;
}
};
const model_hash = if (model_digest) |m| m[0..Hasher.digest_len].* else try Hasher.computeDirectoryHash(thread_pool, models_dir, @constCast(&Hasher.ComputeDirectoryOptions{
const model_hash = if (model_digest) |m| m[0..Hasher.digest_len].* else try Hasher.computeDirectoryHash(allocator, io, models_dir, @constCast(&Hasher.ComputeDirectoryOptions{
.isIncluded = Include.include,
.isExcluded = struct {
pub fn exclude(entry: std.fs.Dir.Walker.Entry) bool {
pub fn exclude(entry: std.Io.Dir.Walker.Entry) bool {
_ = entry;
return false;
}
@ -192,14 +186,18 @@ 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(allocator, io, try output_dir.openDir(
io,
".",
.{ .iterate = true },
), @constCast(&Hasher.ComputeDirectoryOptions{
.isIncluded = struct {
pub fn include(entry: std.fs.Dir.Walker.Entry) bool {
pub fn include(entry: std.Io.Dir.Walker.Entry) 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.Io.Dir.Walker.Entry) bool {
_ = entry;
return false;
}
@ -208,13 +206,14 @@ fn calculateDigests(models_dir: std.fs.Dir, output_dir: std.fs.Dir, thread_pool:
}));
if (verbose) std.log.info("Output directory hash: {s}", .{Hasher.hexDigest(output_hash)});
return .{
Include.count, .{
Include.count,
.{
.model_dir_hash_digest = model_digest orelse Hasher.hexDigest(model_hash),
.output_dir_hash_digest = Hasher.hexDigest(output_hash),
},
};
}
fn processFile(io: std.Io, file_name: []const u8, output_dir: std.fs.Dir, manifest: *std.Io.Writer) !void {
fn processFile(io: std.Io, file_name: []const u8, output_dir: std.Io.Dir, manifest: *std.Io.Writer) !void {
// It's probably best to create our own allocator here so we can deint at the end and
// toss all allocations related to the services in this file
// I can't guarantee we're not leaking something, and at the end of the
@ -232,7 +231,6 @@ fn processFile(io: std.Io, file_name: []const u8, output_dir: std.fs.Dir, manife
_ = try writer.write("const smithy = @import(\"smithy\");\n");
_ = try writer.write("const json = @import(\"json\");\n");
_ = try writer.write("const date = @import(\"date\");\n");
_ = try writer.write("const zeit = @import(\"zeit\");\n");
_ = try writer.write("\n");
_ = try writer.write("const serializeMap = json.serializeMap;\n");
_ = try writer.write("\n");
@ -272,9 +270,9 @@ fn processFile(io: std.Io, file_name: []const u8, output_dir: std.fs.Dir, manife
const formatted = try zigFmt(allocator, unformatted);
// Dump our buffer out to disk
var file = try output_dir.createFile(output_file_name, .{ .truncate = true });
defer file.close();
try file.writeAll(formatted);
var file = try output_dir.createFile(io, output_file_name, .{ .truncate = true });
defer file.close(io);
try file.writeStreamingAll(io, formatted);
for (service_names) |name| {
try manifest.print("pub const {s} = @import(\"{s}\");\n", .{ name, std.fs.path.basename(output_file_name) });
@ -298,8 +296,8 @@ fn generateServicesForFilePath(
path: []const u8,
writer: *std.Io.Writer,
) ![][]const u8 {
const file = try std.fs.cwd().openFile(path, .{});
defer file.close();
const file = try std.Io.Dir.cwd().openFile(io, path, .{});
defer file.close(io);
return try generateServices(allocator, io, terminator, file, writer);
}
@ -404,7 +402,7 @@ fn generateServices(
allocator: std.mem.Allocator,
io: std.Io,
comptime _: []const u8,
file: std.fs.File,
file: std.Io.File,
writer: *std.Io.Writer,
) ![][]const u8 {
var fbuf: [1024]u8 = undefined;
@ -429,14 +427,14 @@ fn generateServices(
// a reference count in case there are recursive data structures
var shape_references = std.StringHashMap(u64).init(allocator);
defer shape_references.deinit();
var stack: std.ArrayList([]const u8) = .{};
var stack: std.ArrayList([]const u8) = .empty;
defer stack.deinit(allocator);
for (services.items) |service|
try countReferences(allocator, service, shapes, &shape_references, &stack);
var constant_names = try std.ArrayList([]const u8).initCapacity(allocator, services.items.len);
defer constant_names.deinit(allocator);
var unresolved: std.ArrayList(smithy.ShapeInfo) = .{};
var unresolved: std.ArrayList(smithy.ShapeInfo) = .empty;
defer unresolved.deinit(allocator);
var generated = std.StringHashMap(void).init(allocator);
defer generated.deinit();
@ -526,7 +524,7 @@ fn generateAdditionalTypes(allocator: std.mem.Allocator, file_state: FileGenerat
while (file_state.additional_types_to_generate.pop()) |t| {
if (file_state.additional_types_generated.getEntry(t.name) != null) continue;
// std.log.info("\t\t{s}", .{t.name});
var type_stack: std.ArrayList(*const smithy.ShapeInfo) = .{};
var type_stack: std.ArrayList(*const smithy.ShapeInfo) = .empty;
defer type_stack.deinit(allocator);
const state = GenerationState{
.type_stack = &type_stack,
@ -577,7 +575,7 @@ fn generateOperation(allocator: std.mem.Allocator, operation: smithy.ShapeInfo,
const snake_case_name = try support.constantName(allocator, operation.name, .snake);
defer allocator.free(snake_case_name);
var type_stack: std.ArrayList(*const smithy.ShapeInfo) = .{};
var type_stack: std.ArrayList(*const smithy.ShapeInfo) = .empty;
defer type_stack.deinit(allocator);
const state = GenerationState{
.type_stack = &type_stack,
@ -585,8 +583,7 @@ fn generateOperation(allocator: std.mem.Allocator, operation: smithy.ShapeInfo,
.allocator = allocator,
.indent_level = 1,
};
var child_state = state;
child_state.indent_level += 1;
const child_state = state.indent();
// indent should start at 4 spaces here
const operation_name = avoidReserved(snake_case_name);
@ -652,7 +649,7 @@ fn generateOperation(allocator: std.mem.Allocator, operation: smithy.ShapeInfo,
try outputIndent(child_state, writer);
try writer.print(".uri = \"{s}\",\n", .{trait.http.uri});
try outputIndent(child_state, writer);
try writer.print(".success_code = {d},\n", .{trait.http.code});
try writer.print(".success_code = @as(u10, {d}),\n", .{trait.http.code});
try outputIndent(state, writer);
_ = try writer.write("};\n\n");
}
@ -687,17 +684,17 @@ fn generateMetadataFunction(operation_name: []const u8, state: GenerationState,
// }
// We want to add a short "get my parents" function into the response
var child_state = state;
child_state.indent_level += 1;
child_state = child_state.indent();
try outputIndent(child_state, writer);
_ = try writer.write("pub fn metaInfo() struct { ");
try writer.print("service_metadata: @TypeOf(service_metadata), action: @TypeOf({s})", .{operation_name});
_ = try writer.write(" } {\n");
child_state.indent_level += 1;
child_state = child_state.indent();
try outputIndent(child_state, writer);
_ = try writer.write("return .{ .service_metadata = service_metadata, ");
try writer.print(".action = {s}", .{operation_name});
_ = try writer.write(" };\n");
child_state.indent_level -= 1;
child_state = child_state.deindent();
try outputIndent(child_state, writer);
_ = try writer.write("}\n");
try outputIndent(state, writer);
@ -873,8 +870,7 @@ fn generateMapTypeFor(map: anytype, writer: *std.Io.Writer, state: GenerationSta
try writer.writeAll("pub const is_map_type = true;\n\n");
var child_state = state;
child_state.indent_level += 1;
const child_state = state.indent();
_ = try writer.write("key: ");
_ = try generateTypeFor(map.key, writer, child_state, options.endStructure(true));
@ -923,8 +919,7 @@ fn generateComplexTypeFor(shape_id: []const u8, members: []smithy.TypeMember, ty
// prolog. We'll rely on caller to get the spacing correct here
_ = try writer.write(type_type_name);
_ = try writer.write(" {\n");
var child_state = state;
child_state.indent_level += 1;
const child_state = state.indent();
var payload: ?[]const u8 = null;
for (members) |member| {
// This is our mapping
@ -1011,8 +1006,7 @@ fn generateComplexTypeFor(shape_id: []const u8, members: []smithy.TypeMember, ty
try writer.writeByte('\n');
try outputIndent(child_state, writer);
_ = try writer.write("pub fn fieldNameFor(_: @This(), comptime field_name: []const u8) []const u8 {\n");
var grandchild_state = child_state;
grandchild_state.indent_level += 1;
const grandchild_state = child_state.indent();
// We need to force output here becaseu we're referencing the field in the return statement below
try writeMappings(grandchild_state, "", "mappings", field_name_mappings, true, writer);
try outputIndent(grandchild_state, writer);
@ -1038,8 +1032,7 @@ fn writeMappings(
}
try writer.print("{s}const {s} = .", .{ @"pub", mapping_name });
_ = try writer.write("{\n");
var child_state = state;
child_state.indent_level += 1;
const child_state = state.indent();
for (mappings.items) |mapping| {
try outputIndent(child_state, writer);
try writer.print(".{s} = \"{s}\",\n", .{ avoidReserved(mapping.snake), mapping.original });

View file

@ -34,9 +34,9 @@ pub fn generateToJsonFunction(shape_id: []const u8, writer: *std.Io.Writer, stat
const member_value = try getMemberValueJson(allocator, "self", member);
defer allocator.free(member_value);
try writer.print("try jw.objectField(\"{s}\");\n", .{member.json_key});
try writeMemberJson(
.{
.object_field_name = member.json_key,
.shape_id = member.target,
.field_name = member.field_name,
.field_value = member_value,
@ -87,7 +87,7 @@ fn getJsonMembers(allocator: Allocator, shape: Shape, state: GenerationState) !?
return null;
}
var json_members = std.ArrayListUnmanaged(JsonMember){};
var json_members: std.ArrayListUnmanaged(JsonMember) = .empty;
var iter = hash_map.iterator();
while (iter.next()) |kvp| {
@ -146,6 +146,8 @@ fn writeMemberValue(
}
const WriteMemberJsonParams = struct {
object_field_name: []const u8,
quote_object_field_name: bool = true,
shape_id: []const u8,
field_name: []const u8,
field_value: []const u8,
@ -196,9 +198,9 @@ fn writeStructureJson(params: WriteMemberJsonParams, writer: *std.Io.Writer) !vo
const member_value = try getMemberValueJson(allocator, object_value, member);
defer allocator.free(member_value);
try writer.print("try jw.objectField(\"{s}\");\n", .{member.json_key});
try writeMemberJson(
.{
.object_field_name = member.json_key,
.shape_id = member.target,
.field_name = member.field_name,
.field_value = member_value,
@ -214,7 +216,7 @@ fn writeStructureJson(params: WriteMemberJsonParams, writer: *std.Io.Writer) !vo
if (is_optional) {
try writer.writeAll("} else {\n");
try writer.writeAll("try jw.write(null);\n");
try writer.writeAll("//try jw.write(null);\n");
try writer.writeAll("}\n");
}
}
@ -268,7 +270,7 @@ fn writeListJson(list: smithy_tools.ListShape, params: WriteMemberJsonParams, wr
if (list_is_optional) {
try writer.writeAll("} else {\n");
try writer.writeAll("try jw.write(null);\n");
try writer.writeAll("//try jw.write(null);\n");
try writer.writeAll("}\n");
}
}
@ -327,9 +329,10 @@ fn writeMapJson(map: smithy_tools.MapShape, params: WriteMemberJsonParams, write
// start loop
try writer.print("for ({s}) |{s}|", .{ map_value, map_value_capture });
try writer.writeAll("{\n");
try writer.print("try jw.objectField({s});\n", .{map_capture_key});
try writeMemberJson(.{
.object_field_name = map_capture_key,
.quote_object_field_name = false,
.shape_id = map.value,
.field_name = "value",
.field_value = map_capture_value,
@ -345,7 +348,7 @@ fn writeMapJson(map: smithy_tools.MapShape, params: WriteMemberJsonParams, write
if (map_is_optional) {
try writer.writeAll("} else {\n");
try writer.writeAll("try jw.write(null);\n");
try writer.writeAll("//try jw.write(null);\n");
try writer.writeAll("}\n");
}
}
@ -361,7 +364,16 @@ fn writeMemberJson(params: WriteMemberJsonParams, writer: *std.Io.Writer) anyerr
const shape_info = try smithy_tools.getShapeInfo(shape_id, state.file_state.shapes);
const shape = shape_info.shape;
const quote = if (params.quote_object_field_name) "\"" else "";
const is_optional = smithy_tools.shapeIsOptional(params.member.traits);
if (is_optional) {
try writer.print("if ({s}) |_|\n", .{params.field_value});
try writer.writeAll("{\n");
}
try writer.print("try jw.objectField({s}{s}{s});\n", .{ quote, params.object_field_name, quote });
if (state.getTypeRecurrenceCount(shape_id) > 2) {
if (is_optional) try writer.writeAll("\n}\n");
return;
}
@ -389,4 +401,5 @@ fn writeMemberJson(params: WriteMemberJsonParams, writer: *std.Io.Writer) anyerr
.short => try writeScalarJson("short", params, writer),
.service, .resource, .operation, .member, .set => std.debug.panic("Shape type not supported: {}", .{shape}),
}
if (is_optional) try writer.writeAll("\n}\n");
}

View file

@ -6,8 +6,8 @@
.dependencies = .{
.aws = .{
.url = "https://git.lerch.org/api/packages/lobo/generic/aws-sdk-with-models/9b870aa969124de05de2a71e0afb9050a2998b14/9b870aa969124de05de2a71e0afb9050a2998b14nightly-zig-with-models.tar.gz",
.hash = "aws-0.0.1-SbsFcFsaCgBDSmjnC9Lue34UN_csGkkAEBJ4EkUl9r6w",
.url = "https://git.lerch.org/api/packages/lobo/generic/aws-sdk-with-models/e41f98b389539c8bc6b1a231d25e2980318e5ef4/e41f98b389539c8bc6b1a231d25e2980318e5ef4-with-models.tar.gz",
.hash = "aws-0.0.1-SbsFcI0RCgBdf1nak95gi1kAtI6sv3Ntb7BPETH30fpS",
},
},
}

View file

@ -5,8 +5,8 @@
.minimum_zig_version = "0.14.0",
.dependencies = .{
.zeit = .{
.url = "git+https://github.com/elerch/zeit#8190461dc1f892f6370fa9d5cd76690aac0e1c71",
.hash = "zeit-0.6.0-5I6bk99-AgDNMIDuw2Zcoe_9QYIpzwZJqeqMpU54egTd",
.url = "git+https://github.com/rockorager/zeit#2a79678e05e4e82cd4efd4fd6b754dcf029c3a64",
.hash = "zeit-0.6.0-5I6bk7q6AgBdMJxze3D4l9ylQhkviQ_BX9FigDt13MFn",
},
.json = .{
.path = "../json",

View file

@ -1,6 +1,7 @@
const std = @import("std");
const log = std.log.scoped(.date);
const zeit = @import("zeit");
const instantWithoutIo = @import("timestamp.zig").instantWithoutIo;
pub const DateTime = struct {
day: u8,
@ -37,12 +38,13 @@ pub const DateTime = struct {
}
pub fn instant(self: DateTime) !zeit.Instant {
return try zeit.instant(.{ .source = .{ .time = self.time() } });
return try instantWithoutIo(.{ .source = .{ .time = self.time() } });
}
};
pub fn timestampToDateTime(timestamp: zeit.Seconds) DateTime {
const ins = zeit.instant(.{ .source = .{ .unix_timestamp = timestamp } }) catch @panic("Failed to create instant from timestamp");
pub fn timestampToDateTime(timestamp: i64) DateTime {
// zeit.Seconds is i64, so this should be identical
const ins = instantWithoutIo(.{ .source = .{ .unix_timestamp = timestamp } }) catch @panic("Failed to create instant from timestamp");
return DateTime.fromInstant(ins);
}
@ -53,7 +55,7 @@ pub fn parseEnglishToTimestamp(data: []const u8) !i64 {
/// Converts a string to a timestamp value. May not handle dates before the
/// epoch. Dates should look like "Fri, 03 Jun 2022 18:12:36 GMT"
pub fn parseEnglishToDateTime(data: []const u8) !DateTime {
const ins = try zeit.instant(.{ .source = .{ .rfc1123 = data } });
const ins = try instantWithoutIo(.{ .source = .{ .rfc1123 = data } });
return DateTime.fromInstant(ins);
}
@ -64,7 +66,7 @@ pub fn parseIso8601ToTimestamp(data: []const u8) !i64 {
/// Converts a string to a timestamp value. May not handle dates before the
/// epoch
pub fn parseIso8601ToDateTime(data: []const u8) !DateTime {
const ins = try zeit.instant(.{ .source = .{ .iso8601 = data } });
const ins = try instantWithoutIo(.{ .source = .{ .iso8601 = data } });
return DateTime.fromInstant(ins);
}
@ -84,7 +86,7 @@ fn printDateTime(dt: DateTime) void {
}
pub fn printNowUtc(io: std.Io) void {
const now = std.Io.Clock.Timestamp.now(io, .awake) catch return;
const now = std.Io.Clock.Timestamp.now(io, .awake);
const timestamp = @as(i64, @intCast(@divFloor(now.raw.nanoseconds, std.time.ns_per_s)));
printDateTime(timestampToDateTime(timestamp));
}

View file

@ -10,7 +10,7 @@ pub const Timestamp = enum(zeit.Nanoseconds) {
_,
pub fn jsonStringify(value: Timestamp, jw: anytype) !void {
const instant = zeit.instant(.{
const instant = instantWithoutIo(.{
.source = .{
.unix_nano = @intFromEnum(value),
},
@ -34,7 +34,7 @@ pub const Timestamp = enum(zeit.Nanoseconds) {
}
};
const ins = try zeit.instant(.{
const ins = try instantWithoutIo(.{
.source = switch (date_format) {
DateFormat.iso8601 => .{
.iso8601 = val,
@ -49,6 +49,36 @@ pub const Timestamp = enum(zeit.Nanoseconds) {
}
};
/// create a new Instant
pub fn instantWithoutIo(cfg: zeit.Instant.Config) !zeit.Instant {
const ts: zeit.Nanoseconds = switch (cfg.source) {
.now => return error.UseZeitInstantWithIoForNowInstants,
.unix_timestamp => |unix| @as(i128, unix) * std.time.ns_per_s,
.unix_nano => |nano| nano,
.time => |time| time.instant().timestamp,
.iso8601,
.rfc3339,
=> |iso| blk: {
const t = try zeit.Time.fromISO8601(iso);
break :blk t.instant().timestamp;
},
.rfc2822,
.rfc5322,
=> |eml| blk: {
const t = try zeit.Time.fromRFC5322(eml);
break :blk t.instant().timestamp;
},
.rfc1123 => |http_date| blk: {
const t = try zeit.Time.fromRFC1123(http_date);
break :blk t.instant().timestamp;
},
};
return .{
.timestamp = ts,
.timezone = cfg.timezone,
};
}
test Timestamp {
const in_date = "Wed, 23 Apr 2025 11:23:45 GMT";

View file

@ -1346,7 +1346,7 @@ test "json.validate" {
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const ArrayList = std.ArrayList;
const StringArrayHashMap = std.StringArrayHashMap;
const StringArrayHashMap = std.array_hash_map.String;
pub const ValueTree = struct {
arena: ArenaAllocator,
@ -1580,11 +1580,11 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options:
if (!numberToken.is_integer) {
// probably is in scientific notation
const n = try std.fmt.parseFloat(f128, numberToken.slice(tokens.slice, tokens.i - 1));
return try std.meta.intToEnum(T, @as(i128, @intFromFloat(n)));
return std.enums.fromInt(T, @as(i128, @intFromFloat(n))) orelse error.InvalidEnumTag;
}
const n = try std.fmt.parseInt(enumInfo.tag_type, numberToken.slice(tokens.slice, tokens.i - 1), 10);
return try std.meta.intToEnum(T, n);
return std.enums.fromInt(T, n) orelse error.InvalidEnumTag;
},
.String => |stringToken| {
const source_slice = stringToken.slice(tokens.slice, tokens.i - 1);
@ -1772,7 +1772,7 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options:
.slice => {
switch (token) {
.ArrayBegin => {
var arraylist = std.ArrayList(ptrInfo.child){};
var arraylist = std.ArrayList(ptrInfo.child).empty;
errdefer {
while (arraylist.pop()) |v| {
parseFree(ptrInfo.child, v, options);
@ -1817,7 +1817,7 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options:
if (key_type == null) return error.UnexpectedToken;
const value_type = typeForField(ptrInfo.child, "value");
if (value_type == null) return error.UnexpectedToken;
var arraylist = std.ArrayList(ptrInfo.child){};
var arraylist = std.ArrayList(ptrInfo.child).empty;
errdefer {
while (arraylist.pop()) |v| {
parseFree(ptrInfo.child, v, options);

View file

@ -4,8 +4,8 @@ const std = @import("std");
const case = @import("case");
const date = @import("date");
const json = @import("json");
const zeit = @import("zeit");
const credentials = @import("aws_credentials.zig");
const awshttp = @import("aws_http.zig");
const url = @import("url.zig");
const servicemodel = @import("servicemodel.zig");
@ -19,7 +19,6 @@ const scoped_log = std.log.scoped(.aws);
/// controls are insufficient (e.g. use in build script)
pub fn globalLogControl(aws_level: std.log.Level, http_level: std.log.Level, signing_level: std.log.Level, off: bool) void {
const signing = @import("aws_signing.zig");
const credentials = @import("aws_credentials.zig");
logs_off = off;
signing.logs_off = off;
credentials.logs_off = off;
@ -82,8 +81,9 @@ const log = struct {
pub const Options = struct {
region: []const u8 = "aws-global",
dualstack: bool = false,
success_http_code: i64 = 200,
success_http_status: std.http.Status = .ok,
client: Client,
credential_options: credentials.Options = .{},
diagnostics: ?*Diagnostics = null,
@ -91,7 +91,7 @@ pub const Options = struct {
};
pub const Diagnostics = struct {
http_code: i64,
response_status: std.http.Status,
response_body: []const u8,
allocator: std.mem.Allocator,
@ -114,6 +114,7 @@ pub const Services = servicemodel.Services;
pub const ClientOptions = struct {
proxy: ?std.http.Client.Proxy = null,
io: std.Io,
map: *const std.process.Environ.Map,
};
pub const Client = struct {
allocator: std.mem.Allocator,
@ -124,7 +125,7 @@ pub const Client = struct {
pub fn init(allocator: std.mem.Allocator, options: ClientOptions) Self {
return Self{
.allocator = allocator,
.aws_http = awshttp.AwsHttp.init(allocator, options.io, options.proxy),
.aws_http = awshttp.AwsHttp.init(allocator, options.io, options.map, options.proxy),
};
}
pub fn deinit(self: *Client) void {
@ -195,7 +196,7 @@ pub fn Request(comptime request_action: anytype) type {
log.debug("Rest method: '{s}'", .{aws_request.method});
log.debug("Rest success code: '{d}'", .{Action.http_config.success_code});
log.debug("Rest raw uri: '{s}'", .{Action.http_config.uri});
var al = std.ArrayList([]const u8){};
var al = std.ArrayList([]const u8).empty;
defer al.deinit(options.client.allocator);
aws_request.path = try buildPath(
options.client.allocator,
@ -232,8 +233,18 @@ pub fn Request(comptime request_action: anytype) type {
var buffer = std.Io.Writer.Allocating.init(options.client.allocator);
defer buffer.deinit();
if (Self.service_meta.aws_protocol == .rest_json_1) {
if (std.mem.eql(u8, "PUT", aws_request.method) or std.mem.eql(u8, "POST", aws_request.method))
try buffer.writer.print("{f}", .{std.json.fmt(request, .{ .whitespace = .indent_4 })});
if (std.mem.eql(u8, "PUT", aws_request.method) or std.mem.eql(u8, "POST", aws_request.method)) {
// Buried in the tests are our answer here:
// https://github.com/smithy-lang/smithy/blob/main/smithy-aws-protocol-tests/model/restJson1/json-structs.smithy#L71C24-L71C78
// documentation: "Rest Json should not serialize null structure values",
try buffer.writer.print(
"{f}",
.{std.json.fmt(request, .{
.whitespace = .indent_4,
.emit_null_optional_fields = false,
})},
);
}
}
aws_request.body = buffer.written();
var rest_xml_body: ?[]const u8 = null;
@ -294,14 +305,9 @@ pub fn Request(comptime request_action: anytype) type {
}
}
return try Self.callAws(aws_request, .{
.success_http_code = Action.http_config.success_code,
.region = options.region,
.dualstack = options.dualstack,
.client = options.client,
.diagnostics = options.diagnostics,
.mock = options.mock,
});
var rest_options = options;
rest_options.success_http_status = @enumFromInt(Action.http_config.success_code);
return try Self.callAws(aws_request, rest_options);
}
/// Calls using one of the json protocols (json_1_0, json_1_1)
@ -321,11 +327,20 @@ pub fn Request(comptime request_action: anytype) type {
// smithy spec, "A null value MAY be provided or omitted
// for a boxed member with no observable difference." But we're
// seeing a lot of differences here between spec and reality
//
// This is deliciously unclear:
// https://github.com/smithy-lang/smithy/blob/main/smithy-aws-protocol-tests/model/awsJson1_1/null.smithy#L36
//
// It looks like struct nulls are meant to be dropped, but sparse
// lists/maps included. We'll err here on the side of eliminating them
const body = try std.fmt.allocPrint(
options.client.allocator,
"{f}",
.{std.json.fmt(request, .{ .whitespace = .indent_4 })},
.{std.json.fmt(request, .{
.whitespace = .indent_4,
.emit_null_optional_fields = false,
})},
);
defer options.client.allocator.free(body);
@ -397,16 +412,27 @@ pub fn Request(comptime request_action: anytype) type {
.dualstack = options.dualstack,
.sigv4_service_name = Self.service_meta.sigv4_name,
.mock = options.mock,
.credential_options = options.credential_options,
},
);
defer response.deinit();
if (response.response_code != options.success_http_code and response.response_code != 404) {
try reportTraffic(options.client.allocator, "Call Failed", aws_request, response, log.err);
if (response.response_code != options.success_http_status) {
// If the consumer prrovided diagnostics, they are likely handling
// this error themselves. We'll not spam them with log.err
// output. Note that we may need to add additional information
// in diagnostics, as reportTraffic provides more information
// than what exists in the diagnostics data
if (options.diagnostics) |d| {
d.http_code = response.response_code;
d.response_status = response.response_code;
d.response_body = try d.allocator.dupe(u8, response.body);
}
} else try reportTraffic(
options.client.allocator,
"Call Failed",
aws_request,
response,
log.err,
);
return error.HttpFailure;
}
@ -455,9 +481,7 @@ pub fn Request(comptime request_action: anytype) type {
) catch |e| {
log.err("Could not set header value: Response header {s}. Field {s}. Value {s}", .{ header.name, f.?.name, header.value });
log.err("Error: {}", .{e});
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace);
}
std.debug.dumpCurrentStackTrace(.{});
};
break;
@ -1186,7 +1210,10 @@ fn buildPath(
"{f}",
.{std.json.fmt(
@field(request, field.name),
.{ .whitespace = .indent_4 },
.{
.whitespace = .indent_4,
.emit_null_optional_fields = false,
},
)},
);
const trimmed_replacement_val = std.mem.trim(u8, replacement_buffer.written(), "\"");
@ -1445,7 +1472,7 @@ test "REST Json v1 serializes lists in queries" {
}
test "REST Json v1 buildpath substitutes" {
const allocator = std.testing.allocator;
var al = std.ArrayList([]const u8){};
var al = std.ArrayList([]const u8).empty;
defer al.deinit(allocator);
const svs = Services(.{.lambda}){};
const request = svs.lambda.list_functions.Request{
@ -1458,7 +1485,7 @@ test "REST Json v1 buildpath substitutes" {
}
test "REST Json v1 buildpath handles restricted characters" {
const allocator = std.testing.allocator;
var al = std.ArrayList([]const u8){};
var al = std.ArrayList([]const u8).empty;
defer al.deinit(allocator);
const svs = Services(.{.lambda}){};
const request = svs.lambda.list_functions.Request{

View file

@ -69,6 +69,11 @@ pub const Profile = struct {
config_file: ?[]const u8 = null,
/// Config file. Defaults to AWS_PROFILE or default
profile_name: ?[]const u8 = null,
/// Profile name specified via command line should change precedence of operation,
/// moves credential file checking to the top. The sdk does not have a
/// way to know if this is coming from a command line, so this field
/// serves as a way to accomplish that task
prefer_profile_from_file: bool = false,
};
pub const Options = struct {
@ -77,9 +82,18 @@ pub const Options = struct {
pub var static_credentials: ?auth.Credentials = null;
pub fn getCredentials(allocator: std.mem.Allocator, io: std.Io, options: Options) !auth.Credentials {
pub fn getCredentials(allocator: std.mem.Allocator, map: *const std.process.Environ.Map, io: std.Io, options: Options) !auth.Credentials {
if (static_credentials) |c| return c;
if (try getEnvironmentCredentials(allocator)) |cred| {
if (options.profile.prefer_profile_from_file) {
log.debug(
"Command line profile specified. Checking credentials file first. Profile name {s}",
.{options.profile.profile_name orelse "default"},
);
if (try getProfileCredentials(allocator, io, map, options.profile)) |cred| return cred;
// Profile not found. We'll mirror the cli here and bail early
return error.CredentialsNotFound;
}
if (try getEnvironmentCredentials(allocator, map)) |cred| {
log.debug("Found credentials in environment. Access key: {s}", .{cred.access_key});
return cred;
}
@ -87,32 +101,31 @@ pub fn getCredentials(allocator: std.mem.Allocator, io: std.Io, options: Options
// GetWebIdentity is not currently implemented. The rest are tested and gtg
// Note: Lambda just sets environment variables
if (try getWebIdentityToken(allocator)) |cred| return cred;
if (try getProfileCredentials(allocator, io, options.profile)) |cred| return cred;
if (try getProfileCredentials(allocator, io, map, options.profile)) |cred| return cred;
if (try getContainerCredentials(allocator, io)) |cred| return cred;
if (try getContainerCredentials(allocator, io, map)) |cred| return cred;
// I don't think we need v1 at all?
if (try getImdsv2Credentials(allocator, io)) |cred| return cred;
return error.CredentialsNotFound;
}
fn getEnvironmentCredentials(allocator: std.mem.Allocator) !?auth.Credentials {
const secret_key = (try getEnvironmentVariable(allocator, "AWS_SECRET_ACCESS_KEY")) orelse return null;
defer allocator.free(secret_key); //yes, we're not zeroing. But then, the secret key is in an environment var anyway
fn getEnvironmentCredentials(allocator: std.mem.Allocator, map: *const std.process.Environ.Map) !?auth.Credentials {
const secret_key = getEnvironmentVariable(map, "AWS_SECRET_ACCESS_KEY") orelse return null;
const access_key = getEnvironmentVariable(map, "AWS_ACCESS_KEY_ID") orelse return null;
const token = getEnvironmentVariable(map, "AWS_SESSION_TOKEN") orelse
getEnvironmentVariable(map, "AWS_SECURITY_TOKEN"); // Security token is backward compat only
// Use cross-platform API (requires allocation)
return auth.Credentials.init(
allocator,
(try getEnvironmentVariable(allocator, "AWS_ACCESS_KEY_ID")) orelse return null,
try allocator.dupe(u8, access_key),
try allocator.dupe(u8, secret_key),
(try getEnvironmentVariable(allocator, "AWS_SESSION_TOKEN")) orelse
try getEnvironmentVariable(allocator, "AWS_SECURITY_TOKEN"), // Security token is backward compat only
if (token) |t| try allocator.dupe(u8, t) else null,
);
}
fn getEnvironmentVariable(allocator: std.mem.Allocator, key: []const u8) !?[]const u8 {
return std.process.getEnvVarOwned(allocator, key) catch |e| switch (e) {
std.process.GetEnvVarOwnedError.EnvironmentVariableNotFound => return null,
else => return e,
};
fn getEnvironmentVariable(map: *const std.process.Environ.Map, key: []const u8) ?[]const u8 {
if (!map.contains(key)) return null;
return map.get(key);
}
fn getWebIdentityToken(allocator: std.mem.Allocator) !?auth.Credentials {
@ -125,7 +138,7 @@ fn getWebIdentityToken(allocator: std.mem.Allocator) !?auth.Credentials {
// TODO: implement
return null;
}
fn getContainerCredentials(allocator: std.mem.Allocator, io: std.Io) !?auth.Credentials {
fn getContainerCredentials(allocator: std.mem.Allocator, io: std.Io, map: *const std.process.Environ.Map) !?auth.Credentials {
// A note on testing: The best way I have found to test this process is
// the following. Setup an ECS Fargate cluster and create a task definition
// with the command ["/bin/bash","-c","while true; do sleep 10; done"].
@ -166,8 +179,7 @@ fn getContainerCredentials(allocator: std.mem.Allocator, io: std.Io) !?auth.Cred
//
// Compile code, copy to S3, install AWS CLI within the session, download
// from s3 and run
const container_relative_uri = (try getEnvironmentVariable(allocator, "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI")) orelse return null;
defer allocator.free(container_relative_uri);
const container_relative_uri = getEnvironmentVariable(map, "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") orelse return null;
const container_uri = try std.fmt.allocPrint(allocator, "http://169.254.170.2{s}", .{container_relative_uri});
defer allocator.free(container_uri);
@ -200,9 +212,7 @@ fn getContainerCredentials(allocator: std.mem.Allocator, io: std.Io) !?auth.Cred
const res = std.json.parseFromSlice(CredsResponse, allocator, aw.written(), .{}) catch |e| {
log.err("Unexpected Json response from container credentials endpoint: {s}", .{aw.written()});
log.err("Error parsing json: {}", .{e});
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace);
}
std.debug.dumpCurrentStackTrace(.{});
return null;
};
@ -298,9 +308,7 @@ fn getImdsRoleName(allocator: std.mem.Allocator, client: *std.http.Client, imds_
const imds_response = std.json.parseFromSlice(ImdsResponse, allocator, aw.written(), .{}) catch |e| {
log.err("Unexpected Json response from IMDS endpoint: {s}", .{aw.written()});
log.err("Error parsing json: {}", .{e});
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace);
}
std.debug.dumpCurrentStackTrace(.{});
return null;
};
defer imds_response.deinit();
@ -353,10 +361,7 @@ fn getImdsCredentials(allocator: std.mem.Allocator, client: *std.http.Client, ro
const imds_response = std.json.parseFromSlice(ImdsResponse, allocator, aw.written(), .{}) catch |e| {
log.err("Unexpected Json response from IMDS endpoint: {s}", .{aw.written()});
log.err("Error parsing json: {}", .{e});
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace);
}
std.debug.dumpCurrentStackTrace(.{});
return null;
};
defer imds_response.deinit();
@ -383,12 +388,13 @@ fn getImdsCredentials(allocator: std.mem.Allocator, client: *std.http.Client, ro
}
fn getProfileCredentials(allocator: std.mem.Allocator, io: std.Io, options: Profile) !?auth.Credentials {
fn getProfileCredentials(allocator: std.mem.Allocator, io: std.Io, map: *const std.process.Environ.Map, options: Profile) !?auth.Credentials {
var default_path: ?[]const u8 = null;
defer if (default_path) |p| allocator.free(p);
const creds_file_path = try filePath(
allocator,
map,
options.credential_file,
"AWS_SHARED_CREDENTIALS_FILE",
default_path,
@ -398,8 +404,9 @@ fn getProfileCredentials(allocator: std.mem.Allocator, io: std.Io, options: Prof
default_path = default_path orelse creds_file_path.home;
const config_file_path = try filePath(
allocator,
options.credential_file,
"AWS_SHARED_CREDENTIALS_FILE",
map,
options.config_file,
"AWS_CONFIG_FILE",
default_path,
"config",
);
@ -407,21 +414,21 @@ fn getProfileCredentials(allocator: std.mem.Allocator, io: std.Io, options: Prof
default_path = default_path orelse config_file_path.home;
// Get active profile
const profile = (try getEnvironmentVariable(allocator, "AWS_PROFILE")) orelse
try allocator.dupe(u8, "default");
const profile = try allocator.dupe(u8, getEnvironmentVariable(map, "AWS_PROFILE") orelse
options.profile_name orelse "default");
defer allocator.free(profile);
log.debug("Looking for file credentials using profile '{s}'", .{profile});
log.debug("Checking credentials file: {s}", .{creds_file_path.evaluated_path});
const credentials_file = std.fs.openFileAbsolute(creds_file_path.evaluated_path, .{}) catch null;
defer if (credentials_file) |f| f.close();
const credentials_file = std.Io.Dir.openFileAbsolute(io, creds_file_path.evaluated_path, .{}) catch null;
defer if (credentials_file) |f| f.close(io);
// It's much more likely that we'll find credentials in the credentials file
// so we'll try that first
const creds_file_creds = try credsForFile(allocator, io, credentials_file, profile);
var conf_file_creds = PartialCredentials{};
if (creds_file_creds.access_key == null or creds_file_creds.secret_key == null) {
log.debug("Checking config file: {s}", .{config_file_path.evaluated_path});
const config_file = std.fs.openFileAbsolute(creds_file_path.evaluated_path, .{}) catch null;
defer if (config_file) |f| f.close();
const config_file = std.Io.Dir.openFileAbsolute(io, creds_file_path.evaluated_path, .{}) catch null;
defer if (config_file) |f| f.close(io);
conf_file_creds = try credsForFile(allocator, io, config_file, profile);
}
const access_key = keyFrom(allocator, creds_file_creds.access_key, conf_file_creds.access_key);
@ -461,7 +468,7 @@ const PartialCredentials = struct {
access_key: ?[]const u8 = null,
secret_key: ?[]const u8 = null,
};
fn credsForFile(allocator: std.mem.Allocator, io: std.Io, file: ?std.fs.File, profile: []const u8) !PartialCredentials {
fn credsForFile(allocator: std.mem.Allocator, io: std.Io, file: ?std.Io.File, profile: []const u8) !PartialCredentials {
if (file == null) return PartialCredentials{};
var fbuf: [1024]u8 = undefined;
var freader = file.?.reader(io, &fbuf);
@ -596,6 +603,7 @@ fn trimmed(text: []const u8, start: ?usize, end: ?usize) []const u8 {
fn filePath(
allocator: std.mem.Allocator,
map: *const std.process.Environ.Map,
specified_path: ?[]const u8,
env_var_name: []const u8,
config_dir: ?[]const u8,
@ -603,39 +611,28 @@ fn filePath(
) !EvaluatedPath {
if (specified_path) |p| return EvaluatedPath{ .evaluated_path = try allocator.dupe(u8, p) };
// Not specified. Check environment variable, otherwise, hard coded default
if (try getEnvironmentVariable(allocator, env_var_name)) |v| return EvaluatedPath{ .evaluated_path = v };
if (getEnvironmentVariable(map, env_var_name)) |v| return EvaluatedPath{ .evaluated_path = try allocator.dupe(u8, v) };
// Not in environment variable either. Go fish
return try getDefaultPath(allocator, config_dir, ".aws", config_file_name);
return try getDefaultPath(allocator, map, config_dir, ".aws", config_file_name);
}
const EvaluatedPath = struct {
home: ?[]const u8 = null,
evaluated_path: []const u8,
};
fn getDefaultPath(allocator: std.mem.Allocator, home_dir: ?[]const u8, dir: []const u8, file: []const u8) !EvaluatedPath {
const home = home_dir orelse try getHomeDir(allocator);
fn getDefaultPath(allocator: std.mem.Allocator, map: *const std.process.Environ.Map, home_dir: ?[]const u8, dir: []const u8, file: []const u8) !EvaluatedPath {
const home = home_dir orelse try getHomeDir(allocator, map);
log.debug("Home directory: {s}", .{home});
const rc = try std.fs.path.join(allocator, &[_][]const u8{ home, dir, file });
log.debug("Path evaluated as: {s}", .{rc});
return EvaluatedPath{ .home = home, .evaluated_path = rc };
}
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,
};
},
.macos, .linux, .freebsd, .netbsd, .dragonfly, .openbsd, .illumos => {
const home_dir = std.posix.getenv("HOME") orelse {
// TODO look in /etc/passwd
return error.HomeDirUnavailable;
};
return allocator.dupe(u8, home_dir);
},
fn getHomeDir(allocator: std.mem.Allocator, map: *const std.process.Environ.Map) ![]const u8 {
const env_key = switch (builtin.os.tag) {
.windows => "USERPROFILE",
.macos, .linux, .freebsd, .netbsd, .dragonfly, .openbsd, .illumos => "HOME",
// Code from https://github.com/ziglang/zig/blob/9f9f215305389c08a21730859982b68bf2681932/lib/std/fs/get_app_data_dir.zig
// be_user_settings magic number is probably different for home directory
// .haiku => {
@ -651,17 +648,26 @@ fn getHomeDir(allocator: std.mem.Allocator) ![]const u8 {
// }
// },
else => @compileError("Unsupported OS"),
}
};
return try allocator.dupe(u8, getEnvironmentVariable(map, env_key) orelse return error.HomeDirUnavailable);
}
test "filePath" {
const allocator = std.testing.allocator;
var map = std.process.Environ.Map.init(allocator);
defer map.deinit();
try map.put("USERPROFILE", "c:\\users\\myuser");
try map.put("HOME", "/home/user");
// std.testing.log_level = .debug;
// log.debug("\n", .{});
const path = try filePath(allocator, null, "NOTHING", null, "hello");
const path = try filePath(allocator, &map, null, "NOTHING", null, "hello");
defer allocator.free(path.evaluated_path);
defer allocator.free(path.home.?);
try std.testing.expect(path.evaluated_path.len > 10);
// try std.testing.expect(path.evaluated_path.len > 10);
if (builtin.os.tag == .windows)
try std.testing.expectEqualStrings("c:\\users\\myuser\\.aws\\hello", path.evaluated_path)
else
try std.testing.expectEqualStrings("/home/user/.aws/hello", path.evaluated_path);
try std.testing.expectEqualStrings("hello", path.evaluated_path[path.evaluated_path.len - 5 ..]);
try std.testing.expect(path.home != null);
}

View file

@ -90,6 +90,8 @@ pub const Options = struct {
dualstack: bool = false,
sigv4_service_name: ?[]const u8 = null,
credential_options: credentials.Options = .{},
mock: ?Mock = null,
};
@ -101,6 +103,7 @@ pub const Mock = struct {
context: usize = 0,
request_fn: *const fn (
usize,
std.Io,
std.http.Method,
std.Uri,
std.http.Client.RequestOptions,
@ -109,8 +112,8 @@ pub const Mock = struct {
receive_head: *const fn (usize) std.http.Client.Request.ReceiveHeadError!std.http.Client.Response,
reader_decompressing: *const fn (usize) *std.Io.Reader,
fn request(m: Mock, method: std.http.Method, uri: std.Uri, options: std.http.Client.RequestOptions) std.http.Client.RequestError!std.http.Client.Request {
return m.request_fn(m.context, method, uri, options);
fn request(m: Mock, io: std.Io, method: std.http.Method, uri: std.Uri, options: std.http.Client.RequestOptions) std.http.Client.RequestError!std.http.Client.Request {
return m.request_fn(m.context, io, method, uri, options);
}
fn sendBodyComplete(m: Mock, body: []u8) std.Io.Writer.Error!void {
return m.send_body_complete(m.context, body);
@ -145,14 +148,16 @@ pub const AwsHttp = struct {
allocator: std.mem.Allocator,
proxy: ?std.http.Client.Proxy,
io: std.Io,
map: *const std.process.Environ.Map,
const Self = @This();
pub fn init(allocator: std.mem.Allocator, io: std.Io, proxy: ?std.http.Client.Proxy) Self {
pub fn init(allocator: std.mem.Allocator, io: std.Io, map: *const std.process.Environ.Map, proxy: ?std.http.Client.Proxy) Self {
return Self{
.allocator = allocator,
.proxy = proxy,
.io = io,
.map = map,
// .credentialsProvider = // creds provider could be useful
};
}
@ -184,11 +189,11 @@ pub const AwsHttp = struct {
// S3 control uses <account-id>.s3-control.<region>.amazonaws.com
//
// So this regionSubDomain call needs to handle generic customization
const endpoint = try endpointForRequest(self.allocator, service, request, options);
const endpoint = try endpointForRequest(self.allocator, service, request, self.map, options);
defer endpoint.deinit();
log.debug("Calling endpoint {s}", .{endpoint.uri});
// TODO: Should we allow customization here?
const creds = try credentials.getCredentials(self.allocator, self.io, .{});
const creds = try credentials.getCredentials(self.allocator, self.map, self.io, options.credential_options);
defer creds.deinit();
const signing_config: signing.Config = .{
.region = getRegion(service, options.region),
@ -236,7 +241,7 @@ pub const AwsHttp = struct {
// We will use endpoint instead
request_cp.path = endpoint.path;
var request_headers = std.ArrayList(std.http.Header){};
var request_headers: std.ArrayList(std.http.Header) = .empty;
defer request_headers.deinit(self.allocator);
const len = try addHeaders(self.allocator, &request_headers, endpoint.host, request_cp.body, request_cp.content_type, request_cp.headers);
@ -250,7 +255,7 @@ pub const AwsHttp = struct {
}
}
var headers = std.ArrayList(std.http.Header){};
var headers: std.ArrayList(std.http.Header) = .empty;
defer headers.deinit(self.allocator);
for (request_cp.headers) |header|
try headers.append(self.allocator, .{ .name = header.name, .value = header.value });
@ -285,7 +290,7 @@ pub const AwsHttp = struct {
};
var req = if (options.mock) |m|
try m.request(method, uri, req_options) // This will call the test harness
try m.request(self.io, method, uri, req_options) // This will call the test harness
else
try cl.request(method, uri, req_options);
defer req.deinit();
@ -326,7 +331,7 @@ pub const AwsHttp = struct {
.{ @intFromEnum(response.head.status), response.head.status.phrase() },
);
log.debug("Response headers:", .{});
var resp_headers = std.ArrayList(Header){};
var resp_headers: std.ArrayList(Header) = .empty;
defer resp_headers.deinit(self.allocator);
var it = response.head.iterateHeaders();
while (it.next()) |h| { // even though we don't expect to fill the buffer,
@ -363,10 +368,11 @@ pub const AwsHttp = struct {
log.debug("raw response body:\n{s}", .{aw.written()});
const rc = HttpResult{
.response_code = @intFromEnum(response.head.status),
.response_code = response.head.status,
.body = try aw.toOwnedSlice(),
.headers = try resp_headers.toOwnedSlice(self.allocator),
.allocator = self.allocator,
.io = self.io,
};
return rc;
}
@ -410,25 +416,22 @@ fn addHeaders(
return null;
}
fn getEnvironmentVariable(allocator: std.mem.Allocator, key: []const u8) !?[]const u8 {
return std.process.getEnvVarOwned(allocator, key) catch |e| switch (e) {
std.process.GetEnvVarOwnedError.EnvironmentVariableNotFound => return null,
else => return e,
};
fn getEnvironmentVariable(map: *const std.process.Environ.Map, key: []const u8) ?[]const u8 {
if (!map.contains(key)) return null;
return map.get(key);
}
/// override endpoint url. Intended for use in testing. Normally, you should
/// rely on AWS_ENDPOINT_URL environment variable for this
pub var endpoint_override: ?[]const u8 = null;
fn endpointForRequest(allocator: std.mem.Allocator, service: []const u8, request: HttpRequest, options: Options) !EndPoint {
fn endpointForRequest(allocator: std.mem.Allocator, service: []const u8, request: HttpRequest, map: *const std.process.Environ.Map, options: Options) !EndPoint {
if (endpoint_override) |override| {
const uri = try allocator.dupe(u8, override);
return endPointFromUri(allocator, uri, request.path);
}
const environment_override = try getEnvironmentVariable(allocator, "AWS_ENDPOINT_URL");
const environment_override = getEnvironmentVariable(map, "AWS_ENDPOINT_URL");
if (environment_override) |override| {
defer allocator.free(override);
const uri = try allocator.dupe(u8, override);
return endPointFromUri(allocator, uri, request.path);
}
@ -575,7 +578,8 @@ test "endpointForRequest standard operation" {
const allocator = std.testing.allocator;
const service = "dynamodb";
const endpoint = try endpointForRequest(allocator, service, request, options);
const map = std.process.Environ.Map.init(allocator);
const endpoint = try endpointForRequest(allocator, service, request, &map, options);
defer endpoint.deinit();
try std.testing.expectEqualStrings("https://dynamodb.us-west-2.amazonaws.com", endpoint.uri);
}
@ -590,7 +594,8 @@ test "endpointForRequest for cloudfront" {
const allocator = std.testing.allocator;
const service = "cloudfront";
const endpoint = try endpointForRequest(allocator, service, request, options);
const map = std.process.Environ.Map.init(allocator);
const endpoint = try endpointForRequest(allocator, service, request, &map, options);
defer endpoint.deinit();
try std.testing.expectEqualStrings("https://cloudfront.amazonaws.com", endpoint.uri);
}
@ -605,7 +610,8 @@ test "endpointForRequest for s3" {
const allocator = std.testing.allocator;
const service = "s3";
const endpoint = try endpointForRequest(allocator, service, request, options);
const map = std.process.Environ.Map.init(allocator);
const endpoint = try endpointForRequest(allocator, service, request, &map, options);
defer endpoint.deinit();
try std.testing.expectEqualStrings("https://s3.us-east-2.amazonaws.com", endpoint.uri);
}
@ -621,7 +627,8 @@ test "endpointForRequest for s3 - specific bucket" {
const allocator = std.testing.allocator;
const service = "s3";
const endpoint = try endpointForRequest(allocator, service, request, options);
const map = std.process.Environ.Map.init(allocator);
const endpoint = try endpointForRequest(allocator, service, request, &map, options);
defer endpoint.deinit();
try std.testing.expectEqualStrings("https://bucket.s3.us-east-2.amazonaws.com", endpoint.uri);
try std.testing.expectEqualStrings("/key", endpoint.path);

View file

@ -9,10 +9,12 @@ pub const Request = struct {
headers: []const std.http.Header = &.{},
};
pub const Result = struct {
response_code: u16, // actually 3 digits can fit in u10
response_code: std.http.Status,
body: []const u8,
headers: []const std.http.Header,
allocator: std.mem.Allocator,
/// The io that was used for the request
io: std.Io,
pub fn deinit(self: Result) void {
self.allocator.free(self.body);

View file

@ -157,7 +157,7 @@ pub const SigningError = error{
XAmzExpiresHeaderInRequest,
/// Used if the request headers already includes x-amz-region-set
XAmzRegionSetHeaderInRequest,
} || error{OutOfMemory} || std.Io.Clock.Error;
} || error{OutOfMemory}; // || std.Io.Clock.Error;
const forbidden_headers = .{
.{ .name = "x-amz-content-sha256", .err = SigningError.XAmzContentSha256HeaderInRequest },
@ -196,7 +196,7 @@ pub fn signRequest(allocator: std.mem.Allocator, io: std.Io, request: base.Reque
var rc = request;
const signing_time = config.signing_time orelse blk: {
const now = try std.Io.Clock.Timestamp.now(io, .awake);
const now = std.Io.Clock.Timestamp.now(io, .real);
break :blk @as(i64, @intCast(@divFloor(now.raw.nanoseconds, std.time.ns_per_s)));
};
@ -336,9 +336,7 @@ pub fn signRequest(allocator: std.mem.Allocator, io: std.Io, request: base.Reque
pub fn freeSignedRequest(allocator: std.mem.Allocator, request: *base.Request, config: Config) void {
validateConfig(config) catch |e| {
log.err("Signing validation failed during signature free: {}", .{e});
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace);
}
std.debug.dumpCurrentStackTrace(.{});
return;
};
@ -369,7 +367,7 @@ pub const UnverifiedRequest = struct {
raw: *std.http.Server.Request,
pub fn init(allocator: std.mem.Allocator, request: *std.http.Server.Request) !UnverifiedRequest {
var al = std.ArrayList(std.http.Header){};
var al = std.ArrayList(std.http.Header).empty;
defer al.deinit(allocator);
var it = request.iterateHeaders();
while (it.next()) |h| try al.append(allocator, h);
@ -811,7 +809,7 @@ fn canonicalQueryString(allocator: std.mem.Allocator, path: []const u8) ![]const
// Split this by component
var portions = std.mem.splitScalar(u8, query, '&');
var sort_me = std.ArrayList([]const u8){};
var sort_me = std.ArrayList([]const u8).empty;
defer sort_me.deinit(allocator);
while (portions.next()) |item|
try sort_me.append(allocator, item);
@ -1105,9 +1103,7 @@ test "can sign" {
// [debug] (awshttp): Content-Length: 43
const allocator = std.testing.allocator;
var threaded: std.Io.Threaded = .init(allocator);
defer threaded.deinit();
const io = threaded.io();
const io = std.testing.io;
var headers = try std.ArrayList(std.http.Header).initCapacity(allocator, 5);
defer headers.deinit(allocator);
try headers.append(allocator, .{ .name = "Content-Type", .value = "application/x-www-form-urlencoded; charset=utf-8" });
@ -1159,9 +1155,7 @@ test "can sign" {
var test_credential: ?Credentials = null;
test "can verify server request" {
const allocator = std.testing.allocator;
var threaded: std.Io.Threaded = .init(allocator);
defer threaded.deinit();
const io = threaded.io();
const io = std.testing.io;
const access_key = try allocator.dupe(u8, "ACCESS");
const secret_key = try allocator.dupe(u8, "SECRET");
@ -1214,9 +1208,7 @@ test "can verify server request" {
}
test "can verify server request without x-amz-content-sha256" {
const allocator = std.testing.allocator;
var threaded: std.Io.Threaded = .init(allocator);
defer threaded.deinit();
const io = threaded.io();
const io = std.testing.io;
const access_key = try allocator.dupe(u8, "ACCESS");
const secret_key = try allocator.dupe(u8, "SECRET");

View file

@ -129,7 +129,7 @@ test "proper serialization for kms" {
const parsed_body = try std.json.parseFromSlice(struct {
KeyId: []const u8,
Plaintext: []const u8,
EncryptionContext: ?struct {},
EncryptionContext: ?struct {} = null,
GrantTokens: [][]const u8,
EncryptionAlgorithm: []const u8,
DryRun: bool,
@ -166,7 +166,6 @@ test "basic json request serialization" {
try buffer.writer.print("{f}", .{std.json.fmt(request, .{ .whitespace = .indent_4 })});
try std.testing.expectEqualStrings(
\\{
\\ "ExclusiveStartTableName": null,
\\ "Limit": 1
\\}
, buffer.written());
@ -254,8 +253,8 @@ const TestOptions = struct {
};
const TestSetup = struct {
allocator: std.mem.Allocator,
threaded: std.Io.Threaded,
io: std.Io,
map: *const std.process.Environ.Map,
options: TestOptions,
creds: aws_auth.Credentials,
client: aws.Client,
@ -266,7 +265,6 @@ const TestSetup = struct {
pub const RequestActuals = struct {
request: *std.http.Client.Request,
trace: []const u8,
// Looks like uri might be getting trounced before deinit
request_uri: []const u8,
@ -305,7 +303,6 @@ const TestSetup = struct {
}
allocator.free(self.extra_headers);
allocator.free(self.trace);
allocator.free(self.request_uri);
allocator.destroy(self.request.reader.in);
allocator.destroy(self.request.client);
@ -326,24 +323,21 @@ const TestSetup = struct {
fn request(
self_ptr: usize,
io: std.Io,
method: std.http.Method,
uri: std.Uri,
options: std.http.Client.RequestOptions,
) std.http.Client.RequestError!std.http.Client.Request {
_ = io;
const self: *Self = @ptrFromInt(self_ptr);
if (self.request_actuals) |r| {
std.debug.print("request has been called twice. Previous stack trace:\n", .{});
var stderr = std.fs.File.stderr().writer(&.{});
stderr.interface.writeAll(r.trace) catch @panic("could not write to stderr");
if (self.request_actuals) |_| {
std.debug.print("request has been called twice:\n", .{});
std.debug.print("Current stack trace:\n", .{});
std.debug.dumpCurrentStackTrace(.{});
return error.ConnectionRefused; // we should not be called twice
}
const acts = try self.allocator.create(RequestActuals);
errdefer self.allocator.destroy(acts);
var aw = std.Io.Writer.Allocating.init(self.allocator);
defer aw.deinit();
std.debug.writeCurrentStackTrace(.{}, &aw.writer, .no_color) catch return error.OutOfMemory;
const req = try self.allocator.create(std.http.Client.Request);
errdefer self.allocator.destroy(req);
const reader = try self.allocator.create(std.Io.Reader);
@ -387,7 +381,6 @@ const TestSetup = struct {
});
acts.* = .{
.trace = try self.allocator.dupe(u8, aw.written()),
.request = req,
.request_uri = try std.fmt.allocPrint(self.allocator, "{f}", .{uri}),
.extra_headers = try al.toOwnedSlice(self.allocator),
@ -446,9 +439,10 @@ const TestSetup = struct {
return self.request_actuals.?.request.reader.in;
}
fn init(options: TestOptions) !*Self {
var threaded: std.Io.Threaded = .init(options.allocator);
const io = threaded.io();
const client = aws.Client.init(options.allocator, .{ .io = io });
const io = std.testing.io;
const map = try options.allocator.create(std.process.Environ.Map);
map.* = std.process.Environ.Map.init(options.allocator);
const client = aws.Client.init(options.allocator, .{ .io = io, .map = map });
const call_options = try options.allocator.create(aws.Options);
const self = try options.allocator.create(Self);
call_options.* = .{
@ -468,8 +462,8 @@ const TestSetup = struct {
self.* = .{
.options = options,
.allocator = options.allocator,
.threaded = threaded,
.io = io,
.map = map,
.creds = aws_auth.Credentials.init(
options.allocator,
try options.allocator.dupe(u8, "ACCESS"),
@ -483,6 +477,7 @@ const TestSetup = struct {
return self;
}
fn deinit(self: *Self) void {
self.options.allocator.destroy(self.map);
if (self.response_actuals) |r| {
self.allocator.free(r.body);
self.allocator.destroy(r);
@ -493,7 +488,6 @@ const TestSetup = struct {
}
self.allocator.destroy(self.call_options);
self.call_options = undefined;
self.threaded.deinit();
self.allocator.destroy(self);
aws_creds.static_credentials = null;
}
@ -650,7 +644,7 @@ test "json_1_0_query_with_input: dynamodb listTables runtime" {
try req_actuals.expectHeader("X-Amz-Target", "DynamoDB_20120810.ListTables");
const parsed_body = try std.json.parseFromSlice(struct {
ExclusiveStartTableName: ?[]const u8,
ExclusiveStartTableName: ?[]const u8 = null,
Limit: u8,
}, std.testing.allocator, req_actuals.body.?, .{});
defer parsed_body.deinit();
@ -719,7 +713,7 @@ test "json_1_1_query_with_input: ecs listClusters runtime" {
try req_actuals.expectHeader("X-Amz-Target", "AmazonEC2ContainerServiceV20141113.ListClusters");
const parsed_body = try std.json.parseFromSlice(struct {
nextToken: ?[]const u8,
nextToken: ?[]const u8 = null,
maxResults: u8,
}, std.testing.allocator, req_actuals.body.?, .{});
defer parsed_body.deinit();
@ -759,8 +753,8 @@ test "json_1_1_query_no_input: ecs listClusters runtime" {
try req_actuals.expectHeader("X-Amz-Target", "AmazonEC2ContainerServiceV20141113.ListClusters");
const parsed_body = try std.json.parseFromSlice(struct {
nextToken: ?[]const u8,
maxResults: ?u8,
nextToken: ?[]const u8 = null,
maxResults: ?u8 = null,
}, std.testing.allocator, req_actuals.body.?, .{});
defer parsed_body.deinit();
@ -1183,13 +1177,9 @@ test "json_1_1: ECR timestamps" {
try std.testing.expectEqualStrings("https://146325435496.dkr.ecr.us-west-2.amazonaws.com", call.response.authorization_data.?[0].proxy_endpoint.?);
// try std.testing.expectEqual(@as(i64, 1.73859841557E9), call.response.authorization_data.?[0].expires_at.?);
const zeit = @import("zeit");
const expected_ins = try zeit.instant(.{
.source = .{ .iso8601 = "2022-05-17T06:56:13.652000+00:00" },
});
const expected_ts: date.Timestamp = @enumFromInt(expected_ins.timestamp);
try std.testing.expectEqual(expected_ts, call.response.authorization_data.?[0].expires_at.?);
const expected_ts = try date.Timestamp.parse("2022-05-17T06:56:13.652000+00:00");
const actual = call.response.authorization_data.?[0].expires_at.?;
try std.testing.expectEqual(expected_ts, actual);
}
test "jsonStringify: structure + enums" {
@ -1268,6 +1258,20 @@ test "jsonStringify" {
try std.testing.expectEqualStrings("1234", json_parsed.value.arn);
try std.testing.expectEqualStrings("bar", json_parsed.value.tags.foo);
}
test "jsonStringify does not emit null values on serialization" {
{
const lambda = (Services(.{.lambda}){}).lambda;
const request = lambda.CreateFunctionRequest{
.function_name = "foo",
.role = "bar",
.code = .{},
};
const request_json = try std.fmt.allocPrint(std.testing.allocator, "{f}", .{std.json.fmt(request, .{})});
defer std.testing.allocator.free(request_json);
try std.testing.expect(std.mem.indexOf(u8, request_json, "null") == null);
}
}
test "jsonStringify nullable object" {
// structure is not null
@ -1290,7 +1294,7 @@ test "jsonStringify nullable object" {
FunctionVersion: []const u8,
Name: []const u8,
RoutingConfig: struct {
AdditionalVersionWeights: ?struct {},
AdditionalVersionWeights: ?struct {} = null,
},
}, std.testing.allocator, request_json, .{ .ignore_unknown_fields = true });
defer json_parsed.deinit();
@ -1327,7 +1331,7 @@ test "works against a live server" {
const Server = struct {
allocator: std.mem.Allocator,
io: std.Io,
ready: std.Thread.Semaphore = .{},
ready: std.Io.Semaphore = .{},
requests_received: usize = 0,
thread: ?std.Thread = null,
listening_uri: []const u8 = undefined,
@ -1349,7 +1353,7 @@ test "works against a live server" {
threadMain,
.{self},
);
try self.ready.timedWait(1000 * std.time.ns_per_ms);
try self.ready.wait(self.io); // This could hang the test...
awshttp.endpoint_override = self.listening_uri;
if (awshttp.endpoint_override == null) return error.TestSetupStartFailure;
std.log.debug("endpoint override set to {?s}", .{awshttp.endpoint_override});
@ -1378,7 +1382,7 @@ test "works against a live server" {
self.allocator.free(self.listening_uri);
self.listening_uri = undefined;
}
self.ready.post();
self.ready.post(self.io);
while (true) {
var connection = try server.accept(self.io);
defer connection.close(self.io);
@ -1411,9 +1415,8 @@ test "works against a live server" {
}
};
const allocator = std.testing.allocator;
var threaded: std.Io.Threaded = .init(allocator);
defer threaded.deinit();
const io = threaded.io();
const io = std.testing.io;
const map = std.process.Environ.Map.init(allocator);
var server = Server{ .allocator = allocator, .io = io };
try server.start();
var stopped = false;
@ -1434,7 +1437,7 @@ test "works against a live server" {
// }
const sts = (Services(.{.sts}){}).sts;
const client = aws.Client.init(std.testing.allocator, .{ .io = io });
const client = aws.Client.init(std.testing.allocator, .{ .io = io, .map = &map });
const creds = aws_auth.Credentials.init(
allocator,
try allocator.dupe(u8, "ACCESS"),

View file

@ -32,10 +32,9 @@ pub fn log(
const prefix = "[" ++ @tagName(level) ++ "] " ++ scope_prefix;
// Print the message to stderr, silently ignoring any errors
std.debug.lockStdErr();
defer std.debug.unlockStdErr();
var stderr_writer = std.fs.File.stderr().writer(&.{});
const stderr = &stderr_writer.interface;
const locked = std.debug.lockStderr(&.{});
defer std.debug.unlockStderr();
const stderr = &locked.file_writer.interface;
nosuspend stderr.print(prefix ++ format ++ "\n", args) catch return;
}
@ -59,16 +58,15 @@ const Tests = enum {
rest_xml_work_with_s3,
};
pub fn main() anyerror!void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
pub fn main(init: std.process.Init) anyerror!void {
const allocator = init.gpa;
const io = init.io;
const map = init.environ_map;
var tests = try std.ArrayList(Tests).initCapacity(allocator, @typeInfo(Tests).@"enum".fields.len);
defer tests.deinit(allocator);
var args = try std.process.argsWithAllocator(allocator);
defer args.deinit();
var args = try init.minimal.args.iterateAllocator(init.arena.allocator());
var stdout_buf: [4096]u8 = undefined;
const stdout_raw = std.fs.File.stdout().writer(&stdout_buf);
const stdout_raw = std.Io.File.stdout().writer(io, &stdout_buf);
var stdout = stdout_raw.interface;
defer stdout.flush() catch @panic("could not flush stdout");
var arg0: ?[]const u8 = null;
@ -111,10 +109,7 @@ pub fn main() anyerror!void {
}
std.log.info("Start\n", .{});
var threaded: std.Io.Threaded = .init(allocator);
defer threaded.deinit();
const io = threaded.io();
const client_options = aws.ClientOptions{ .proxy = proxy, .io = io };
const client_options = aws.ClientOptions{ .proxy = proxy, .io = io, .map = map };
var client = aws.Client.init(allocator, client_options);
const options = aws.Options{
.region = "us-west-2",
@ -376,7 +371,7 @@ fn proxyFromString(string: []const u8) !std.http.Client.Proxy {
rc.protocol = .tls;
} else return error.InvalidScheme;
var split_iterator = std.mem.splitScalar(u8, remaining, ':');
const host_str = std.mem.trimRight(u8, split_iterator.first(), "/");
const host_str = std.mem.trimEnd(u8, split_iterator.first(), "/");
rc.host = try std.Io.net.HostName.init(host_str);
if (split_iterator.next()) |port|
rc.port = try std.fmt.parseInt(u16, port, 10);

View file

@ -31,8 +31,8 @@ pub const Element = struct {
fn init(tag: []const u8, alloc: Allocator) Element {
return .{
.tag = tag,
.attributes = AttributeList{},
.children = ContentList{},
.attributes = .empty,
.children = .empty,
.allocator = alloc,
};
}

View file

@ -192,9 +192,7 @@ fn parseInternal(comptime T: type, element: *xml.Element, options: ParseOptions)
e,
},
);
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace);
}
std.debug.dumpCurrentStackTrace(.{});
}
return e;
};
@ -381,7 +379,7 @@ fn parseInternal(comptime T: type, element: *xml.Element, options: ParseOptions)
log.debug("type = {s}, style = {s}, ptr_info.child == {s}, element = {s}", .{ @typeName(T), @tagName(array_style), @typeName(ptr_info.child), element.tag });
var children = std.ArrayList(ptr_info.child){};
var children = std.ArrayList(ptr_info.child).empty;
defer children.deinit(allocator);
switch (array_style) {