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

View file

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

View file

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

View file

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

1
.gitignore vendored
View file

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

View file

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

View file

@ -2,7 +2,7 @@
# See https://pre-commit.com/hooks.html for more hooks # See https://pre-commit.com/hooks.html for more hooks
repos: repos:
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0 rev: v6.0.0
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
- id: end-of-file-fixer - 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 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{ const test_targets = [_]std.Target.Query{
.{}, // native .{}, // native
.{ .cpu_arch = .x86_64, .os_tag = .linux }, .{ .cpu_arch = .x86_64, .os_tag = .linux },
@ -49,7 +47,7 @@ pub fn build(b: *Builder) !void {
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
}); });
configure(mod_exe, dep_mods, true); configure(mod_exe, dep_mods);
const exe = b.addExecutable(.{ const exe = b.addExecutable(.{
.name = "demo", .name = "demo",
@ -74,7 +72,7 @@ pub fn build(b: *Builder) !void {
.target = b.graph.host, .target = b.graph.host,
.optimize = if (b.verbose) .Debug else .ReleaseSafe, .optimize = if (b.verbose) .Debug else .ReleaseSafe,
}); });
configure(cg_mod, dep_mods, false); configure(cg_mod, dep_mods);
const cg_exe = b.addExecutable(.{ const cg_exe = b.addExecutable(.{
.name = "codegen", .name = "codegen",
@ -114,16 +112,45 @@ pub fn build(b: *Builder) !void {
cg.dependOn(&cg_cmd.step); 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(.{ 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, .target = target,
.optimize = optimize, .optimize = optimize,
}); });
configure(service_manifest_module, dep_mods, true); configure(service_manifest_module, dep_mods);
mod_exe.addImport("service_manifest", service_manifest_module); mod_exe.addImport("service_manifest", service_manifest_module);
@ -134,13 +161,13 @@ pub fn build(b: *Builder) !void {
.optimize = optimize, .optimize = optimize,
}); });
mod_aws.addImport("service_manifest", service_manifest_module); mod_aws.addImport("service_manifest", service_manifest_module);
configure(mod_aws, dep_mods, true); configure(mod_aws, dep_mods);
// Expose module to others // Expose module to others
const mod_aws_signing = b.addModule("aws-signing", .{ const mod_aws_signing = b.addModule("aws-signing", .{
.root_source_file = b.path("src/aws_signing.zig"), .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 // 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 // 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, .optimize = optimize,
}); });
mod_unit_tests.addImport("service_manifest", service_manifest_module); 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 // Creates a step for unit testing. This only builds the test executable
// but does not run it. // but does not run it.
@ -179,7 +206,8 @@ pub fn build(b: *Builder) !void {
.filters = test_filters, .filters = test_filters,
}); });
unit_tests.step.dependOn(cg); if (!has_pre_generated)
unit_tests.step.dependOn(cg);
unit_tests.use_llvm = !no_llvm; unit_tests.use_llvm = !no_llvm;
const run_unit_tests = b.addRunArtifact(unit_tests); const run_unit_tests = b.addRunArtifact(unit_tests);
@ -202,7 +230,8 @@ pub fn build(b: *Builder) !void {
.filters = test_filters, .filters = test_filters,
}); });
smoke_test.use_llvm = !no_llvm; 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); const run_smoke_test = b.addRunArtifact(smoke_test);
@ -212,14 +241,20 @@ pub fn build(b: *Builder) !void {
} else { } else {
b.installArtifact(exe); 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("smithy", modules.get("smithy").?);
compile.addImport("date", modules.get("date").?); compile.addImport("date", modules.get("date").?);
compile.addImport("json", modules.get("json").?); compile.addImport("json", modules.get("json").?);
compile.addImport("case", modules.get("case").?); 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) { 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"); const mod_smithy = dep_smithy.module("smithy");
try result.putNoClobber("smithy", mod_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 dep_case = b.dependency("case", args);
const mod_case = dep_case.module("case"); const mod_case = dep_case.module("case");
try result.putNoClobber("case", mod_case); try result.putNoClobber("case", mod_case);
@ -251,3 +282,140 @@ fn getDependencyModules(b: *std.Build, args: anytype) !std.StringHashMap(*std.Bu
return result; 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", "README.md",
"LICENSE", "LICENSE",
}, },
.minimum_zig_version = "0.15.1", .minimum_zig_version = "0.16.0",
.dependencies = .{ .dependencies = .{
.smithy = .{ .smithy = .{
@ -29,12 +29,8 @@
.path = "lib/json", .path = "lib/json",
}, },
.case = .{ .case = .{
.url = "git+https://github.com/travisstaloch/case.git#f8003fe5f93b65f673d10d41323e347225e8cb87", .url = "git+https://github.com/elerch/case?ref=zig-0.16.0#82017a92e179031f21896d02262ae1e216459e4f",
.hash = "case-0.0.1-chGYqx_EAADaGJjmoln5M1iMBDTrMdd8to5wdEVpfXm4", .hash = "case-0.0.1-chGYq1fEAAAN4h3YRmkh9OTDvuUyjmoXr6PhYAUzjU0D",
},
.zeit = .{
.url = "git+https://github.com/elerch/zeit#8190461dc1f892f6370fa9d5cd76690aac0e1c71",
.hash = "zeit-0.6.0-5I6bk99-AgDNMIDuw2Zcoe_9QYIpzwZJqeqMpU54egTd",
}, },
}, },
} }

View file

@ -39,7 +39,7 @@ pub fn indent(self: @This()) GenerationState {
pub fn deindent(self: @This()) GenerationState { pub fn deindent(self: @This()) GenerationState {
var new_state = self.clone(); 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; return new_state;
} }

View file

@ -8,7 +8,7 @@ pub const HashedFile = struct {
hash: [Hash.digest_length]u8, hash: [Hash.digest_length]u8,
failure: Error!void, 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 { fn lessThan(context: void, lhs: *const HashedFile, rhs: *const HashedFile) bool {
_ = context; _ = context;
@ -76,13 +76,13 @@ pub fn hex64(x: u64) [16]u8 {
return result; 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; _ = entry;
return true; return true;
} }
fn excluded(entry: std.fs.Dir.Walker.Entry) bool { fn excluded(entry: std.Io.Dir.Walker.Entry) bool {
_ = entry; _ = entry;
return false; return false;
} }
@ -94,33 +94,33 @@ pub const ComputeDirectoryOptions = struct {
}; };
pub fn computeDirectoryHash( pub fn computeDirectoryHash(
thread_pool: *std.Thread.Pool, allocator: std.mem.Allocator,
dir: std.fs.Dir, io: std.Io,
dir: std.Io.Dir,
options: *ComputeDirectoryOptions, options: *ComputeDirectoryOptions,
) ![Hash.digest_length]u8 { ) ![Hash.digest_length]u8 {
const gpa = thread_pool.allocator;
// We'll use an arena allocator for the path name strings since they all // We'll use an arena allocator for the path name strings since they all
// need to be in memory for sorting. // 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(); defer arena_instance.deinit();
const arena = arena_instance.allocator(); const arena = arena_instance.allocator();
// Collect all files, recursively, then sort. // Collect all files, recursively, then sort.
// Normally we're looking at around 300 model files // Normally we're looking at around 300 model files
var all_files = try std.ArrayList(*HashedFile).initCapacity(gpa, 300); var all_files = try std.ArrayList(*HashedFile).initCapacity(allocator, 300);
defer all_files.deinit(gpa); defer all_files.deinit(allocator);
var walker = try dir.walk(gpa); var walker = try dir.walk(allocator);
defer walker.deinit(); defer walker.deinit();
{ {
// The final hash will be a hash of each file hashed independently. This // The final hash will be a hash of each file hashed independently. This
// allows hashing in parallel. // allows hashing in parallel.
var wait_group: std.Thread.WaitGroup = .{}; var g: std.Io.Group = .init;
defer wait_group.wait(); errdefer g.cancel(io);
while (try walker.next()) |entry| { while (try walker.next(io)) |entry| {
switch (entry.kind) { switch (entry.kind) {
.directory => continue, .directory => continue,
.file => {}, .file => {},
@ -128,7 +128,7 @@ pub fn computeDirectoryHash(
} }
if (options.isExcluded(entry) or !options.isIncluded(entry)) if (options.isExcluded(entry) or !options.isIncluded(entry))
continue; 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 hashed_file = try alloc.create(HashedFile);
const fs_path = try alloc.dupe(u8, entry.path); const fs_path = try alloc.dupe(u8, entry.path);
hashed_file.* = .{ hashed_file.* = .{
@ -137,11 +137,11 @@ pub fn computeDirectoryHash(
.hash = undefined, // to be populated by the worker .hash = undefined, // to be populated by the worker
.failure = undefined, // to be populated by the worker .failure = undefined, // to be populated by the worker
}; };
wait_group.start(); g.async(io, workerHashFile, .{ io, dir, hashed_file, &g });
try thread_pool.spawn(workerHashFile, .{ dir, hashed_file, &wait_group });
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); std.mem.sort(*HashedFile, all_files.items, {}, HashedFile.lessThan);
@ -156,23 +156,26 @@ pub fn computeDirectoryHash(
hasher.update(&hashed_file.hash); hasher.update(&hashed_file.hash);
} }
if (any_failures) return error.DirectoryHashUnavailable; 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(); return hasher.finalResult();
} }
fn workerHashFile(dir: std.fs.Dir, hashed_file: *HashedFile, wg: *std.Thread.WaitGroup) void { fn workerHashFile(io: std.Io, dir: std.Io.Dir, hashed_file: *HashedFile, wg: *std.Io.Group) void {
defer wg.finish(); _ = wg; // assume here that 0.16.0 Io.Group no longer needs to be notified at the time of completion
hashed_file.failure = hashFileFallible(dir, hashed_file); 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 buf: [8000]u8 = undefined;
var file = try dir.openFile(hashed_file.fs_path, .{}); var file = try dir.openFile(io, hashed_file.fs_path, .{});
defer file.close(); defer file.close(io);
var hasher = Hash.init(.{}); var hasher = Hash.init(.{});
hasher.update(hashed_file.normalized_path); hasher.update(hashed_file.normalized_path);
hasher.update(&.{ 0, @intFromBool(try isExecutable(file)) }); hasher.update(&.{ 0, @intFromBool(try isExecutable(io, file)) });
while (true) { 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; if (bytes_read == 0) break;
hasher.update(buf[0..bytes_read]); hasher.update(buf[0..bytes_read]);
} }
@ -197,7 +200,7 @@ fn normalizePath(arena: std.mem.Allocator, fs_path: []const u8) ![]const u8 {
return normalized; return normalized;
} }
fn isExecutable(file: std.fs.File) !bool { fn isExecutable(io: std.Io, file: std.Io.File) !bool {
if (builtin.os.tag == .windows) { if (builtin.os.tag == .windows) {
// TODO check the ACL on Windows. // TODO check the ACL on Windows.
// Until this is implemented, this could be a false negative on // 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. // when unpacking the tarball.
return false; return false;
} else { } else {
const stat = try file.stat(); const stat = try file.stat(io);
return (stat.mode & std.posix.S.IXUSR) != 0; 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 zig_version = @import("builtin").zig_version;
const is_next = zig_version.order(next_version) == .eq or zig_version.order(next_version) == .gt; const is_next = zig_version.order(next_version) == .eq or zig_version.order(next_version) == .gt;
pub fn main() anyerror!void { pub fn main(init: std.process.Init) anyerror!void {
const root_progress_node = std.Progress.start(.{}); const io = init.io;
const root_progress_node = std.Progress.start(io, .{});
defer root_progress_node.end(); defer root_progress_node.end();
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); const allocator = init.arena.allocator();
defer arena.deinit();
const allocator = arena.allocator();
var threaded: std.Io.Threaded = .init(allocator); const args = try init.minimal.args.toSlice(allocator);
defer threaded.deinit(); var stdout_writer = std.Io.File.stdout().writer(io, &.{});
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 stdout = &stdout_writer.interface; const stdout = &stdout_writer.interface;
var output_dir = std.fs.cwd(); var output_dir = std.Io.Dir.cwd();
defer if (output_dir.fd > 0) output_dir.close(); defer if (output_dir.handle > 0) output_dir.close(io);
var models_dir: ?std.fs.Dir = null; var models_dir: ?std.Io.Dir = null;
defer if (models_dir) |*m| m.close(); defer if (models_dir) |*m| m.close(io);
for (args, 0..) |arg, i| { for (args, 0..) |arg, i| {
if (std.mem.eql(u8, "--help", arg) or if (std.mem.eql(u8, "--help", arg) or
std.mem.eql(u8, "-h", arg)) 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", .{}); try stdout.print(" --output specifies an output directory, otherwise the current working directory will be used\n", .{});
std.process.exit(0); std.process.exit(0);
} }
if (std.mem.eql(u8, "--output", arg)) if (std.mem.eql(u8, "--output", arg)) {
output_dir = try output_dir.makeOpenPath(args[i + 1], .{}); 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)) 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", .{}); var manifest_file = try output_dir.createFile(io, "service_manifest.zig", .{});
defer manifest_file.close(); defer manifest_file.close(io);
var manifest = manifest_file.writer(&manifest_buf).interface; var manifest = manifest_file.writer(io, &manifest_buf).interface;
defer manifest.flush() catch @panic("Could not flush service manifest"); defer manifest.flush() catch @panic("Could not flush service manifest");
var files_processed: usize = 0; var files_processed: usize = 0;
var skip_next = true; var skip_next = true;
@ -92,20 +88,20 @@ pub fn main() anyerror!void {
// can be made // can be made
if (models_dir) |m| { if (models_dir) |m| {
var cwd = try std.fs.cwd().openDir(".", .{}); var cwd = try std.Io.Dir.cwd().openDir(io, ".", .{});
defer cwd.close(); defer cwd.close(io);
defer cwd.setAsCwd() catch unreachable; 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); try processDirectories(io, m, output_dir, &root_progress_node);
} }
} }
if (args.len == 0) if (args.len == 0)
_ = try generateServices(allocator, io, ";", std.fs.File.stdin(), stdout); _ = try generateServices(allocator, io, ";", std.Io.File.stdin(), stdout);
if (verbose) { 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}); 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, model_dir_hash_digest: [Hasher.hex_multihash_len]u8,
output_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!! // Let's get ready to hash!!
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit(); defer arena.deinit();
const allocator = arena.allocator(); 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 = const count, var calculated_manifest =
try calculateDigests( try calculateDigests(
allocator,
io,
models_dir, models_dir,
output_dir, output_dir,
&thread_pool,
); );
const output_stored_manifest = if (is_next) 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 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| { if (output_stored_manifest) |o| {
// we have a stored manifest. Parse it and compare to our calculations // we have a stored manifest. Parse it and compare to our calculations
// we can leak as we're using an arena allocator // 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 // Do this in a brain dead fashion from here, no optimization
const manifest_file = try output_dir.createFile("service_manifest.zig", .{}); const manifest_file = try output_dir.createFile(io, "service_manifest.zig", .{});
defer manifest_file.close(); defer manifest_file.close(io);
var manifest = manifest_file.writer(&manifest_buf); var manifest = manifest_file.writer(io, &manifest_buf);
defer manifest.interface.flush() catch @panic("Error flushing service_manifest.zig"); defer manifest.interface.flush() catch @panic("Error flushing service_manifest.zig");
var mi = models_dir.iterate(); var mi = models_dir.iterate();
const generating_models_progress = parent_progress.start("generating models", count); const generating_models_progress = parent_progress.start("generating models", count);
defer generating_models_progress.end(); 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")) { 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); try processFile(io, e.name, output_dir, &manifest.interface);
generating_models_progress.completeOne(); 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 // re-calculate so we can store the manifest
model_digest = calculated_manifest.model_dir_hash_digest; 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 })}); 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; 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 { const Include = struct {
threadlocal var count: usize = 0; 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"); const included = std.mem.endsWith(u8, entry.basename, ".json");
if (included) count += 1; if (included) count += 1;
return included; 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, .isIncluded = Include.include,
.isExcluded = struct { .isExcluded = struct {
pub fn exclude(entry: std.fs.Dir.Walker.Entry) bool { pub fn exclude(entry: std.Io.Dir.Walker.Entry) bool {
_ = entry; _ = entry;
return false; 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)}); 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 { .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"); return std.mem.endsWith(u8, entry.basename, ".zig");
} }
}.include, }.include,
.isExcluded = struct { .isExcluded = struct {
pub fn exclude(entry: std.fs.Dir.Walker.Entry) bool { pub fn exclude(entry: std.Io.Dir.Walker.Entry) bool {
_ = entry; _ = entry;
return false; 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)}); if (verbose) std.log.info("Output directory hash: {s}", .{Hasher.hexDigest(output_hash)});
return .{ return .{
Include.count, .{ Include.count,
.{
.model_dir_hash_digest = model_digest orelse Hasher.hexDigest(model_hash), .model_dir_hash_digest = model_digest orelse Hasher.hexDigest(model_hash),
.output_dir_hash_digest = Hasher.hexDigest(output_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 // 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 // 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 // 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 smithy = @import(\"smithy\");\n");
_ = try writer.write("const json = @import(\"json\");\n"); _ = try writer.write("const json = @import(\"json\");\n");
_ = try writer.write("const date = @import(\"date\");\n"); _ = try writer.write("const date = @import(\"date\");\n");
_ = try writer.write("const zeit = @import(\"zeit\");\n");
_ = try writer.write("\n"); _ = try writer.write("\n");
_ = try writer.write("const serializeMap = json.serializeMap;\n"); _ = try writer.write("const serializeMap = json.serializeMap;\n");
_ = try writer.write("\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); const formatted = try zigFmt(allocator, unformatted);
// Dump our buffer out to disk // Dump our buffer out to disk
var file = try output_dir.createFile(output_file_name, .{ .truncate = true }); var file = try output_dir.createFile(io, output_file_name, .{ .truncate = true });
defer file.close(); defer file.close(io);
try file.writeAll(formatted); try file.writeStreamingAll(io, formatted);
for (service_names) |name| { for (service_names) |name| {
try manifest.print("pub const {s} = @import(\"{s}\");\n", .{ name, std.fs.path.basename(output_file_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, path: []const u8,
writer: *std.Io.Writer, writer: *std.Io.Writer,
) ![][]const u8 { ) ![][]const u8 {
const file = try std.fs.cwd().openFile(path, .{}); const file = try std.Io.Dir.cwd().openFile(io, path, .{});
defer file.close(); defer file.close(io);
return try generateServices(allocator, io, terminator, file, writer); return try generateServices(allocator, io, terminator, file, writer);
} }
@ -404,7 +402,7 @@ fn generateServices(
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
io: std.Io, io: std.Io,
comptime _: []const u8, comptime _: []const u8,
file: std.fs.File, file: std.Io.File,
writer: *std.Io.Writer, writer: *std.Io.Writer,
) ![][]const u8 { ) ![][]const u8 {
var fbuf: [1024]u8 = undefined; var fbuf: [1024]u8 = undefined;
@ -429,14 +427,14 @@ fn generateServices(
// a reference count in case there are recursive data structures // a reference count in case there are recursive data structures
var shape_references = std.StringHashMap(u64).init(allocator); var shape_references = std.StringHashMap(u64).init(allocator);
defer shape_references.deinit(); defer shape_references.deinit();
var stack: std.ArrayList([]const u8) = .{}; var stack: std.ArrayList([]const u8) = .empty;
defer stack.deinit(allocator); defer stack.deinit(allocator);
for (services.items) |service| for (services.items) |service|
try countReferences(allocator, service, shapes, &shape_references, &stack); try countReferences(allocator, service, shapes, &shape_references, &stack);
var constant_names = try std.ArrayList([]const u8).initCapacity(allocator, services.items.len); var constant_names = try std.ArrayList([]const u8).initCapacity(allocator, services.items.len);
defer constant_names.deinit(allocator); defer constant_names.deinit(allocator);
var unresolved: std.ArrayList(smithy.ShapeInfo) = .{}; var unresolved: std.ArrayList(smithy.ShapeInfo) = .empty;
defer unresolved.deinit(allocator); defer unresolved.deinit(allocator);
var generated = std.StringHashMap(void).init(allocator); var generated = std.StringHashMap(void).init(allocator);
defer generated.deinit(); 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| { while (file_state.additional_types_to_generate.pop()) |t| {
if (file_state.additional_types_generated.getEntry(t.name) != null) continue; if (file_state.additional_types_generated.getEntry(t.name) != null) continue;
// std.log.info("\t\t{s}", .{t.name}); // 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); defer type_stack.deinit(allocator);
const state = GenerationState{ const state = GenerationState{
.type_stack = &type_stack, .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); const snake_case_name = try support.constantName(allocator, operation.name, .snake);
defer allocator.free(snake_case_name); 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); defer type_stack.deinit(allocator);
const state = GenerationState{ const state = GenerationState{
.type_stack = &type_stack, .type_stack = &type_stack,
@ -585,8 +583,7 @@ fn generateOperation(allocator: std.mem.Allocator, operation: smithy.ShapeInfo,
.allocator = allocator, .allocator = allocator,
.indent_level = 1, .indent_level = 1,
}; };
var child_state = state; const child_state = state.indent();
child_state.indent_level += 1;
// indent should start at 4 spaces here // indent should start at 4 spaces here
const operation_name = avoidReserved(snake_case_name); 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 outputIndent(child_state, writer);
try writer.print(".uri = \"{s}\",\n", .{trait.http.uri}); try writer.print(".uri = \"{s}\",\n", .{trait.http.uri});
try outputIndent(child_state, writer); 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 outputIndent(state, writer);
_ = try writer.write("};\n\n"); _ = 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 // We want to add a short "get my parents" function into the response
var child_state = state; var child_state = state;
child_state.indent_level += 1; child_state = child_state.indent();
try outputIndent(child_state, writer); try outputIndent(child_state, writer);
_ = try writer.write("pub fn metaInfo() struct { "); _ = try writer.write("pub fn metaInfo() struct { ");
try writer.print("service_metadata: @TypeOf(service_metadata), action: @TypeOf({s})", .{operation_name}); try writer.print("service_metadata: @TypeOf(service_metadata), action: @TypeOf({s})", .{operation_name});
_ = try writer.write(" } {\n"); _ = try writer.write(" } {\n");
child_state.indent_level += 1; child_state = child_state.indent();
try outputIndent(child_state, writer); try outputIndent(child_state, writer);
_ = try writer.write("return .{ .service_metadata = service_metadata, "); _ = try writer.write("return .{ .service_metadata = service_metadata, ");
try writer.print(".action = {s}", .{operation_name}); try writer.print(".action = {s}", .{operation_name});
_ = try writer.write(" };\n"); _ = try writer.write(" };\n");
child_state.indent_level -= 1; child_state = child_state.deindent();
try outputIndent(child_state, writer); try outputIndent(child_state, writer);
_ = try writer.write("}\n"); _ = try writer.write("}\n");
try outputIndent(state, writer); 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"); try writer.writeAll("pub const is_map_type = true;\n\n");
var child_state = state; const child_state = state.indent();
child_state.indent_level += 1;
_ = try writer.write("key: "); _ = try writer.write("key: ");
_ = try generateTypeFor(map.key, writer, child_state, options.endStructure(true)); _ = 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 // prolog. We'll rely on caller to get the spacing correct here
_ = try writer.write(type_type_name); _ = try writer.write(type_type_name);
_ = try writer.write(" {\n"); _ = try writer.write(" {\n");
var child_state = state; const child_state = state.indent();
child_state.indent_level += 1;
var payload: ?[]const u8 = null; var payload: ?[]const u8 = null;
for (members) |member| { for (members) |member| {
// This is our mapping // This is our mapping
@ -1011,8 +1006,7 @@ fn generateComplexTypeFor(shape_id: []const u8, members: []smithy.TypeMember, ty
try writer.writeByte('\n'); try writer.writeByte('\n');
try outputIndent(child_state, writer); try outputIndent(child_state, writer);
_ = try writer.write("pub fn fieldNameFor(_: @This(), comptime field_name: []const u8) []const u8 {\n"); _ = try writer.write("pub fn fieldNameFor(_: @This(), comptime field_name: []const u8) []const u8 {\n");
var grandchild_state = child_state; const grandchild_state = child_state.indent();
grandchild_state.indent_level += 1;
// We need to force output here becaseu we're referencing the field in the return statement below // 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 writeMappings(grandchild_state, "", "mappings", field_name_mappings, true, writer);
try outputIndent(grandchild_state, writer); try outputIndent(grandchild_state, writer);
@ -1038,8 +1032,7 @@ fn writeMappings(
} }
try writer.print("{s}const {s} = .", .{ @"pub", mapping_name }); try writer.print("{s}const {s} = .", .{ @"pub", mapping_name });
_ = try writer.write("{\n"); _ = try writer.write("{\n");
var child_state = state; const child_state = state.indent();
child_state.indent_level += 1;
for (mappings.items) |mapping| { for (mappings.items) |mapping| {
try outputIndent(child_state, writer); try outputIndent(child_state, writer);
try writer.print(".{s} = \"{s}\",\n", .{ avoidReserved(mapping.snake), mapping.original }); 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); const member_value = try getMemberValueJson(allocator, "self", member);
defer allocator.free(member_value); defer allocator.free(member_value);
try writer.print("try jw.objectField(\"{s}\");\n", .{member.json_key});
try writeMemberJson( try writeMemberJson(
.{ .{
.object_field_name = member.json_key,
.shape_id = member.target, .shape_id = member.target,
.field_name = member.field_name, .field_name = member.field_name,
.field_value = member_value, .field_value = member_value,
@ -87,7 +87,7 @@ fn getJsonMembers(allocator: Allocator, shape: Shape, state: GenerationState) !?
return null; return null;
} }
var json_members = std.ArrayListUnmanaged(JsonMember){}; var json_members: std.ArrayListUnmanaged(JsonMember) = .empty;
var iter = hash_map.iterator(); var iter = hash_map.iterator();
while (iter.next()) |kvp| { while (iter.next()) |kvp| {
@ -146,6 +146,8 @@ fn writeMemberValue(
} }
const WriteMemberJsonParams = struct { const WriteMemberJsonParams = struct {
object_field_name: []const u8,
quote_object_field_name: bool = true,
shape_id: []const u8, shape_id: []const u8,
field_name: []const u8, field_name: []const u8,
field_value: []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); const member_value = try getMemberValueJson(allocator, object_value, member);
defer allocator.free(member_value); defer allocator.free(member_value);
try writer.print("try jw.objectField(\"{s}\");\n", .{member.json_key});
try writeMemberJson( try writeMemberJson(
.{ .{
.object_field_name = member.json_key,
.shape_id = member.target, .shape_id = member.target,
.field_name = member.field_name, .field_name = member.field_name,
.field_value = member_value, .field_value = member_value,
@ -214,7 +216,7 @@ fn writeStructureJson(params: WriteMemberJsonParams, writer: *std.Io.Writer) !vo
if (is_optional) { if (is_optional) {
try writer.writeAll("} else {\n"); 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"); try writer.writeAll("}\n");
} }
} }
@ -268,7 +270,7 @@ fn writeListJson(list: smithy_tools.ListShape, params: WriteMemberJsonParams, wr
if (list_is_optional) { if (list_is_optional) {
try writer.writeAll("} else {\n"); 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"); try writer.writeAll("}\n");
} }
} }
@ -327,9 +329,10 @@ fn writeMapJson(map: smithy_tools.MapShape, params: WriteMemberJsonParams, write
// start loop // start loop
try writer.print("for ({s}) |{s}|", .{ map_value, map_value_capture }); try writer.print("for ({s}) |{s}|", .{ map_value, map_value_capture });
try writer.writeAll("{\n"); try writer.writeAll("{\n");
try writer.print("try jw.objectField({s});\n", .{map_capture_key});
try writeMemberJson(.{ try writeMemberJson(.{
.object_field_name = map_capture_key,
.quote_object_field_name = false,
.shape_id = map.value, .shape_id = map.value,
.field_name = "value", .field_name = "value",
.field_value = map_capture_value, .field_value = map_capture_value,
@ -345,7 +348,7 @@ fn writeMapJson(map: smithy_tools.MapShape, params: WriteMemberJsonParams, write
if (map_is_optional) { if (map_is_optional) {
try writer.writeAll("} else {\n"); 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"); 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_info = try smithy_tools.getShapeInfo(shape_id, state.file_state.shapes);
const shape = shape_info.shape; 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 (state.getTypeRecurrenceCount(shape_id) > 2) {
if (is_optional) try writer.writeAll("\n}\n");
return; return;
} }
@ -389,4 +401,5 @@ fn writeMemberJson(params: WriteMemberJsonParams, writer: *std.Io.Writer) anyerr
.short => try writeScalarJson("short", params, writer), .short => try writeScalarJson("short", params, writer),
.service, .resource, .operation, .member, .set => std.debug.panic("Shape type not supported: {}", .{shape}), .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 = .{ .dependencies = .{
.aws = .{ .aws = .{
.url = "https://git.lerch.org/api/packages/lobo/generic/aws-sdk-with-models/9b870aa969124de05de2a71e0afb9050a2998b14/9b870aa969124de05de2a71e0afb9050a2998b14nightly-zig-with-models.tar.gz", .url = "https://git.lerch.org/api/packages/lobo/generic/aws-sdk-with-models/e41f98b389539c8bc6b1a231d25e2980318e5ef4/e41f98b389539c8bc6b1a231d25e2980318e5ef4-with-models.tar.gz",
.hash = "aws-0.0.1-SbsFcFsaCgBDSmjnC9Lue34UN_csGkkAEBJ4EkUl9r6w", .hash = "aws-0.0.1-SbsFcI0RCgBdf1nak95gi1kAtI6sv3Ntb7BPETH30fpS",
}, },
}, },
} }

View file

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

View file

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

View file

@ -10,7 +10,7 @@ pub const Timestamp = enum(zeit.Nanoseconds) {
_, _,
pub fn jsonStringify(value: Timestamp, jw: anytype) !void { pub fn jsonStringify(value: Timestamp, jw: anytype) !void {
const instant = zeit.instant(.{ const instant = instantWithoutIo(.{
.source = .{ .source = .{
.unix_nano = @intFromEnum(value), .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) { .source = switch (date_format) {
DateFormat.iso8601 => .{ DateFormat.iso8601 => .{
.iso8601 = val, .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 { test Timestamp {
const in_date = "Wed, 23 Apr 2025 11:23:45 GMT"; 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 Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator; const ArenaAllocator = std.heap.ArenaAllocator;
const ArrayList = std.ArrayList; const ArrayList = std.ArrayList;
const StringArrayHashMap = std.StringArrayHashMap; const StringArrayHashMap = std.array_hash_map.String;
pub const ValueTree = struct { pub const ValueTree = struct {
arena: ArenaAllocator, arena: ArenaAllocator,
@ -1580,11 +1580,11 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options:
if (!numberToken.is_integer) { if (!numberToken.is_integer) {
// probably is in scientific notation // probably is in scientific notation
const n = try std.fmt.parseFloat(f128, numberToken.slice(tokens.slice, tokens.i - 1)); 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); 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| { .String => |stringToken| {
const source_slice = stringToken.slice(tokens.slice, tokens.i - 1); 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 => { .slice => {
switch (token) { switch (token) {
.ArrayBegin => { .ArrayBegin => {
var arraylist = std.ArrayList(ptrInfo.child){}; var arraylist = std.ArrayList(ptrInfo.child).empty;
errdefer { errdefer {
while (arraylist.pop()) |v| { while (arraylist.pop()) |v| {
parseFree(ptrInfo.child, v, options); 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; if (key_type == null) return error.UnexpectedToken;
const value_type = typeForField(ptrInfo.child, "value"); const value_type = typeForField(ptrInfo.child, "value");
if (value_type == null) return error.UnexpectedToken; if (value_type == null) return error.UnexpectedToken;
var arraylist = std.ArrayList(ptrInfo.child){}; var arraylist = std.ArrayList(ptrInfo.child).empty;
errdefer { errdefer {
while (arraylist.pop()) |v| { while (arraylist.pop()) |v| {
parseFree(ptrInfo.child, v, options); parseFree(ptrInfo.child, v, options);

View file

@ -4,8 +4,8 @@ const std = @import("std");
const case = @import("case"); const case = @import("case");
const date = @import("date"); const date = @import("date");
const json = @import("json"); const json = @import("json");
const zeit = @import("zeit");
const credentials = @import("aws_credentials.zig");
const awshttp = @import("aws_http.zig"); const awshttp = @import("aws_http.zig");
const url = @import("url.zig"); const url = @import("url.zig");
const servicemodel = @import("servicemodel.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) /// 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 { 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 signing = @import("aws_signing.zig");
const credentials = @import("aws_credentials.zig");
logs_off = off; logs_off = off;
signing.logs_off = off; signing.logs_off = off;
credentials.logs_off = off; credentials.logs_off = off;
@ -82,8 +81,9 @@ const log = struct {
pub const Options = struct { pub const Options = struct {
region: []const u8 = "aws-global", region: []const u8 = "aws-global",
dualstack: bool = false, dualstack: bool = false,
success_http_code: i64 = 200, success_http_status: std.http.Status = .ok,
client: Client, client: Client,
credential_options: credentials.Options = .{},
diagnostics: ?*Diagnostics = null, diagnostics: ?*Diagnostics = null,
@ -91,7 +91,7 @@ pub const Options = struct {
}; };
pub const Diagnostics = struct { pub const Diagnostics = struct {
http_code: i64, response_status: std.http.Status,
response_body: []const u8, response_body: []const u8,
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
@ -114,6 +114,7 @@ pub const Services = servicemodel.Services;
pub const ClientOptions = struct { pub const ClientOptions = struct {
proxy: ?std.http.Client.Proxy = null, proxy: ?std.http.Client.Proxy = null,
io: std.Io, io: std.Io,
map: *const std.process.Environ.Map,
}; };
pub const Client = struct { pub const Client = struct {
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
@ -124,7 +125,7 @@ pub const Client = struct {
pub fn init(allocator: std.mem.Allocator, options: ClientOptions) Self { pub fn init(allocator: std.mem.Allocator, options: ClientOptions) Self {
return Self{ return Self{
.allocator = allocator, .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 { 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 method: '{s}'", .{aws_request.method});
log.debug("Rest success code: '{d}'", .{Action.http_config.success_code}); log.debug("Rest success code: '{d}'", .{Action.http_config.success_code});
log.debug("Rest raw uri: '{s}'", .{Action.http_config.uri}); 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); defer al.deinit(options.client.allocator);
aws_request.path = try buildPath( aws_request.path = try buildPath(
options.client.allocator, 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); var buffer = std.Io.Writer.Allocating.init(options.client.allocator);
defer buffer.deinit(); defer buffer.deinit();
if (Self.service_meta.aws_protocol == .rest_json_1) { 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)) 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 })}); // 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(); aws_request.body = buffer.written();
var rest_xml_body: ?[]const u8 = null; 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, .{ var rest_options = options;
.success_http_code = Action.http_config.success_code, rest_options.success_http_status = @enumFromInt(Action.http_config.success_code);
.region = options.region, return try Self.callAws(aws_request, rest_options);
.dualstack = options.dualstack,
.client = options.client,
.diagnostics = options.diagnostics,
.mock = options.mock,
});
} }
/// Calls using one of the json protocols (json_1_0, json_1_1) /// 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 // smithy spec, "A null value MAY be provided or omitted
// for a boxed member with no observable difference." But we're // for a boxed member with no observable difference." But we're
// seeing a lot of differences here between spec and reality // 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( const body = try std.fmt.allocPrint(
options.client.allocator, options.client.allocator,
"{f}", "{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); defer options.client.allocator.free(body);
@ -397,16 +412,27 @@ pub fn Request(comptime request_action: anytype) type {
.dualstack = options.dualstack, .dualstack = options.dualstack,
.sigv4_service_name = Self.service_meta.sigv4_name, .sigv4_service_name = Self.service_meta.sigv4_name,
.mock = options.mock, .mock = options.mock,
.credential_options = options.credential_options,
}, },
); );
defer response.deinit(); defer response.deinit();
if (response.response_code != options.success_http_code and response.response_code != 404) { if (response.response_code != options.success_http_status) {
try reportTraffic(options.client.allocator, "Call Failed", aws_request, response, log.err); // 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| { 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); 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; return error.HttpFailure;
} }
@ -455,9 +481,7 @@ pub fn Request(comptime request_action: anytype) type {
) catch |e| { ) catch |e| {
log.err("Could not set header value: Response header {s}. Field {s}. Value {s}", .{ header.name, f.?.name, header.value }); log.err("Could not set header value: Response header {s}. Field {s}. Value {s}", .{ header.name, f.?.name, header.value });
log.err("Error: {}", .{e}); log.err("Error: {}", .{e});
if (@errorReturnTrace()) |trace| { std.debug.dumpCurrentStackTrace(.{});
std.debug.dumpStackTrace(trace);
}
}; };
break; break;
@ -1186,7 +1210,10 @@ fn buildPath(
"{f}", "{f}",
.{std.json.fmt( .{std.json.fmt(
@field(request, field.name), @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(), "\""); 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" { test "REST Json v1 buildpath substitutes" {
const allocator = std.testing.allocator; const allocator = std.testing.allocator;
var al = std.ArrayList([]const u8){}; var al = std.ArrayList([]const u8).empty;
defer al.deinit(allocator); defer al.deinit(allocator);
const svs = Services(.{.lambda}){}; const svs = Services(.{.lambda}){};
const request = svs.lambda.list_functions.Request{ 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" { test "REST Json v1 buildpath handles restricted characters" {
const allocator = std.testing.allocator; const allocator = std.testing.allocator;
var al = std.ArrayList([]const u8){}; var al = std.ArrayList([]const u8).empty;
defer al.deinit(allocator); defer al.deinit(allocator);
const svs = Services(.{.lambda}){}; const svs = Services(.{.lambda}){};
const request = svs.lambda.list_functions.Request{ const request = svs.lambda.list_functions.Request{

View file

@ -69,6 +69,11 @@ pub const Profile = struct {
config_file: ?[]const u8 = null, config_file: ?[]const u8 = null,
/// Config file. Defaults to AWS_PROFILE or default /// Config file. Defaults to AWS_PROFILE or default
profile_name: ?[]const u8 = null, 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 { pub const Options = struct {
@ -77,9 +82,18 @@ pub const Options = struct {
pub var static_credentials: ?auth.Credentials = null; 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 (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}); log.debug("Found credentials in environment. Access key: {s}", .{cred.access_key});
return cred; 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 // GetWebIdentity is not currently implemented. The rest are tested and gtg
// Note: Lambda just sets environment variables // Note: Lambda just sets environment variables
if (try getWebIdentityToken(allocator)) |cred| return cred; 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? // I don't think we need v1 at all?
if (try getImdsv2Credentials(allocator, io)) |cred| return cred; if (try getImdsv2Credentials(allocator, io)) |cred| return cred;
return error.CredentialsNotFound; return error.CredentialsNotFound;
} }
fn getEnvironmentCredentials(allocator: std.mem.Allocator) !?auth.Credentials { fn getEnvironmentCredentials(allocator: std.mem.Allocator, map: *const std.process.Environ.Map) !?auth.Credentials {
const secret_key = (try getEnvironmentVariable(allocator, "AWS_SECRET_ACCESS_KEY")) orelse return null; const secret_key = getEnvironmentVariable(map, "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 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) // Use cross-platform API (requires allocation)
return auth.Credentials.init( return auth.Credentials.init(
allocator, allocator,
(try getEnvironmentVariable(allocator, "AWS_ACCESS_KEY_ID")) orelse return null, try allocator.dupe(u8, access_key),
try allocator.dupe(u8, secret_key), try allocator.dupe(u8, secret_key),
(try getEnvironmentVariable(allocator, "AWS_SESSION_TOKEN")) orelse if (token) |t| try allocator.dupe(u8, t) else null,
try getEnvironmentVariable(allocator, "AWS_SECURITY_TOKEN"), // Security token is backward compat only
); );
} }
fn getEnvironmentVariable(allocator: std.mem.Allocator, key: []const u8) !?[]const u8 { fn getEnvironmentVariable(map: *const std.process.Environ.Map, key: []const u8) ?[]const u8 {
return std.process.getEnvVarOwned(allocator, key) catch |e| switch (e) { if (!map.contains(key)) return null;
std.process.GetEnvVarOwnedError.EnvironmentVariableNotFound => return null, return map.get(key);
else => return e,
};
} }
fn getWebIdentityToken(allocator: std.mem.Allocator) !?auth.Credentials { fn getWebIdentityToken(allocator: std.mem.Allocator) !?auth.Credentials {
@ -125,7 +138,7 @@ fn getWebIdentityToken(allocator: std.mem.Allocator) !?auth.Credentials {
// TODO: implement // TODO: implement
return null; 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 // 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 // the following. Setup an ECS Fargate cluster and create a task definition
// with the command ["/bin/bash","-c","while true; do sleep 10; done"]. // 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 // Compile code, copy to S3, install AWS CLI within the session, download
// from s3 and run // from s3 and run
const container_relative_uri = (try getEnvironmentVariable(allocator, "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI")) orelse return null; const container_relative_uri = getEnvironmentVariable(map, "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") orelse return null;
defer allocator.free(container_relative_uri);
const container_uri = try std.fmt.allocPrint(allocator, "http://169.254.170.2{s}", .{container_relative_uri}); const container_uri = try std.fmt.allocPrint(allocator, "http://169.254.170.2{s}", .{container_relative_uri});
defer allocator.free(container_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| { 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("Unexpected Json response from container credentials endpoint: {s}", .{aw.written()});
log.err("Error parsing json: {}", .{e}); log.err("Error parsing json: {}", .{e});
if (@errorReturnTrace()) |trace| { std.debug.dumpCurrentStackTrace(.{});
std.debug.dumpStackTrace(trace);
}
return null; 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| { 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("Unexpected Json response from IMDS endpoint: {s}", .{aw.written()});
log.err("Error parsing json: {}", .{e}); log.err("Error parsing json: {}", .{e});
if (@errorReturnTrace()) |trace| { std.debug.dumpCurrentStackTrace(.{});
std.debug.dumpStackTrace(trace);
}
return null; return null;
}; };
defer imds_response.deinit(); 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| { 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("Unexpected Json response from IMDS endpoint: {s}", .{aw.written()});
log.err("Error parsing json: {}", .{e}); log.err("Error parsing json: {}", .{e});
if (@errorReturnTrace()) |trace| { std.debug.dumpCurrentStackTrace(.{});
std.debug.dumpStackTrace(trace);
}
return null; return null;
}; };
defer imds_response.deinit(); 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; var default_path: ?[]const u8 = null;
defer if (default_path) |p| allocator.free(p); defer if (default_path) |p| allocator.free(p);
const creds_file_path = try filePath( const creds_file_path = try filePath(
allocator, allocator,
map,
options.credential_file, options.credential_file,
"AWS_SHARED_CREDENTIALS_FILE", "AWS_SHARED_CREDENTIALS_FILE",
default_path, 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; default_path = default_path orelse creds_file_path.home;
const config_file_path = try filePath( const config_file_path = try filePath(
allocator, allocator,
options.credential_file, map,
"AWS_SHARED_CREDENTIALS_FILE", options.config_file,
"AWS_CONFIG_FILE",
default_path, default_path,
"config", "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; default_path = default_path orelse config_file_path.home;
// Get active profile // Get active profile
const profile = (try getEnvironmentVariable(allocator, "AWS_PROFILE")) orelse const profile = try allocator.dupe(u8, getEnvironmentVariable(map, "AWS_PROFILE") orelse
try allocator.dupe(u8, "default"); options.profile_name orelse "default");
defer allocator.free(profile); defer allocator.free(profile);
log.debug("Looking for file credentials using profile '{s}'", .{profile}); log.debug("Looking for file credentials using profile '{s}'", .{profile});
log.debug("Checking credentials file: {s}", .{creds_file_path.evaluated_path}); log.debug("Checking credentials file: {s}", .{creds_file_path.evaluated_path});
const credentials_file = std.fs.openFileAbsolute(creds_file_path.evaluated_path, .{}) catch null; const credentials_file = std.Io.Dir.openFileAbsolute(io, creds_file_path.evaluated_path, .{}) catch null;
defer if (credentials_file) |f| f.close(); defer if (credentials_file) |f| f.close(io);
// It's much more likely that we'll find credentials in the credentials file // It's much more likely that we'll find credentials in the credentials file
// so we'll try that first // so we'll try that first
const creds_file_creds = try credsForFile(allocator, io, credentials_file, profile); const creds_file_creds = try credsForFile(allocator, io, credentials_file, profile);
var conf_file_creds = PartialCredentials{}; var conf_file_creds = PartialCredentials{};
if (creds_file_creds.access_key == null or creds_file_creds.secret_key == null) { 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}); log.debug("Checking config file: {s}", .{config_file_path.evaluated_path});
const config_file = std.fs.openFileAbsolute(creds_file_path.evaluated_path, .{}) catch null; const config_file = std.Io.Dir.openFileAbsolute(io, creds_file_path.evaluated_path, .{}) catch null;
defer if (config_file) |f| f.close(); defer if (config_file) |f| f.close(io);
conf_file_creds = try credsForFile(allocator, io, config_file, profile); 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); 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, access_key: ?[]const u8 = null,
secret_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{}; if (file == null) return PartialCredentials{};
var fbuf: [1024]u8 = undefined; var fbuf: [1024]u8 = undefined;
var freader = file.?.reader(io, &fbuf); var freader = file.?.reader(io, &fbuf);
@ -596,6 +603,7 @@ fn trimmed(text: []const u8, start: ?usize, end: ?usize) []const u8 {
fn filePath( fn filePath(
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
map: *const std.process.Environ.Map,
specified_path: ?[]const u8, specified_path: ?[]const u8,
env_var_name: []const u8, env_var_name: []const u8,
config_dir: ?[]const u8, config_dir: ?[]const u8,
@ -603,39 +611,28 @@ fn filePath(
) !EvaluatedPath { ) !EvaluatedPath {
if (specified_path) |p| return EvaluatedPath{ .evaluated_path = try allocator.dupe(u8, p) }; if (specified_path) |p| return EvaluatedPath{ .evaluated_path = try allocator.dupe(u8, p) };
// Not specified. Check environment variable, otherwise, hard coded default // 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 // 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 { const EvaluatedPath = struct {
home: ?[]const u8 = null, home: ?[]const u8 = null,
evaluated_path: []const u8, evaluated_path: []const u8,
}; };
fn getDefaultPath(allocator: std.mem.Allocator, home_dir: ?[]const u8, dir: []const u8, file: []const u8) !EvaluatedPath { 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); const home = home_dir orelse try getHomeDir(allocator, map);
log.debug("Home directory: {s}", .{home}); log.debug("Home directory: {s}", .{home});
const rc = try std.fs.path.join(allocator, &[_][]const u8{ home, dir, file }); const rc = try std.fs.path.join(allocator, &[_][]const u8{ home, dir, file });
log.debug("Path evaluated as: {s}", .{rc}); log.debug("Path evaluated as: {s}", .{rc});
return EvaluatedPath{ .home = home, .evaluated_path = rc }; return EvaluatedPath{ .home = home, .evaluated_path = rc };
} }
fn getHomeDir(allocator: std.mem.Allocator) ![]const u8 { fn getHomeDir(allocator: std.mem.Allocator, map: *const std.process.Environ.Map) ![]const u8 {
switch (builtin.os.tag) { const env_key = switch (builtin.os.tag) {
.windows => { .windows => "USERPROFILE",
return std.process.getEnvVarOwned(allocator, "USERPROFILE") catch |err| switch (err) { .macos, .linux, .freebsd, .netbsd, .dragonfly, .openbsd, .illumos => "HOME",
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);
},
// Code from https://github.com/ziglang/zig/blob/9f9f215305389c08a21730859982b68bf2681932/lib/std/fs/get_app_data_dir.zig // 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 // be_user_settings magic number is probably different for home directory
// .haiku => { // .haiku => {
@ -651,17 +648,26 @@ fn getHomeDir(allocator: std.mem.Allocator) ![]const u8 {
// } // }
// }, // },
else => @compileError("Unsupported OS"), else => @compileError("Unsupported OS"),
} };
return try allocator.dupe(u8, getEnvironmentVariable(map, env_key) orelse return error.HomeDirUnavailable);
} }
test "filePath" { test "filePath" {
const allocator = std.testing.allocator; 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; // std.testing.log_level = .debug;
// log.debug("\n", .{}); // 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.evaluated_path);
defer allocator.free(path.home.?); 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.expectEqualStrings("hello", path.evaluated_path[path.evaluated_path.len - 5 ..]);
try std.testing.expect(path.home != null); try std.testing.expect(path.home != null);
} }

View file

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

View file

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

View file

@ -157,7 +157,7 @@ pub const SigningError = error{
XAmzExpiresHeaderInRequest, XAmzExpiresHeaderInRequest,
/// Used if the request headers already includes x-amz-region-set /// Used if the request headers already includes x-amz-region-set
XAmzRegionSetHeaderInRequest, XAmzRegionSetHeaderInRequest,
} || error{OutOfMemory} || std.Io.Clock.Error; } || error{OutOfMemory}; // || std.Io.Clock.Error;
const forbidden_headers = .{ const forbidden_headers = .{
.{ .name = "x-amz-content-sha256", .err = SigningError.XAmzContentSha256HeaderInRequest }, .{ .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; var rc = request;
const signing_time = config.signing_time orelse blk: { 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))); 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 { pub fn freeSignedRequest(allocator: std.mem.Allocator, request: *base.Request, config: Config) void {
validateConfig(config) catch |e| { validateConfig(config) catch |e| {
log.err("Signing validation failed during signature free: {}", .{e}); log.err("Signing validation failed during signature free: {}", .{e});
if (@errorReturnTrace()) |trace| { std.debug.dumpCurrentStackTrace(.{});
std.debug.dumpStackTrace(trace);
}
return; return;
}; };
@ -369,7 +367,7 @@ pub const UnverifiedRequest = struct {
raw: *std.http.Server.Request, raw: *std.http.Server.Request,
pub fn init(allocator: std.mem.Allocator, request: *std.http.Server.Request) !UnverifiedRequest { 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); defer al.deinit(allocator);
var it = request.iterateHeaders(); var it = request.iterateHeaders();
while (it.next()) |h| try al.append(allocator, h); 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 // Split this by component
var portions = std.mem.splitScalar(u8, query, '&'); 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); defer sort_me.deinit(allocator);
while (portions.next()) |item| while (portions.next()) |item|
try sort_me.append(allocator, item); try sort_me.append(allocator, item);
@ -1105,9 +1103,7 @@ test "can sign" {
// [debug] (awshttp): Content-Length: 43 // [debug] (awshttp): Content-Length: 43
const allocator = std.testing.allocator; const allocator = std.testing.allocator;
var threaded: std.Io.Threaded = .init(allocator); const io = std.testing.io;
defer threaded.deinit();
const io = threaded.io();
var headers = try std.ArrayList(std.http.Header).initCapacity(allocator, 5); var headers = try std.ArrayList(std.http.Header).initCapacity(allocator, 5);
defer headers.deinit(allocator); defer headers.deinit(allocator);
try headers.append(allocator, .{ .name = "Content-Type", .value = "application/x-www-form-urlencoded; charset=utf-8" }); 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; var test_credential: ?Credentials = null;
test "can verify server request" { test "can verify server request" {
const allocator = std.testing.allocator; const allocator = std.testing.allocator;
var threaded: std.Io.Threaded = .init(allocator); const io = std.testing.io;
defer threaded.deinit();
const io = threaded.io();
const access_key = try allocator.dupe(u8, "ACCESS"); const access_key = try allocator.dupe(u8, "ACCESS");
const secret_key = try allocator.dupe(u8, "SECRET"); 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" { test "can verify server request without x-amz-content-sha256" {
const allocator = std.testing.allocator; const allocator = std.testing.allocator;
var threaded: std.Io.Threaded = .init(allocator); const io = std.testing.io;
defer threaded.deinit();
const io = threaded.io();
const access_key = try allocator.dupe(u8, "ACCESS"); const access_key = try allocator.dupe(u8, "ACCESS");
const secret_key = try allocator.dupe(u8, "SECRET"); 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 { const parsed_body = try std.json.parseFromSlice(struct {
KeyId: []const u8, KeyId: []const u8,
Plaintext: []const u8, Plaintext: []const u8,
EncryptionContext: ?struct {}, EncryptionContext: ?struct {} = null,
GrantTokens: [][]const u8, GrantTokens: [][]const u8,
EncryptionAlgorithm: []const u8, EncryptionAlgorithm: []const u8,
DryRun: bool, DryRun: bool,
@ -166,7 +166,6 @@ test "basic json request serialization" {
try buffer.writer.print("{f}", .{std.json.fmt(request, .{ .whitespace = .indent_4 })}); try buffer.writer.print("{f}", .{std.json.fmt(request, .{ .whitespace = .indent_4 })});
try std.testing.expectEqualStrings( try std.testing.expectEqualStrings(
\\{ \\{
\\ "ExclusiveStartTableName": null,
\\ "Limit": 1 \\ "Limit": 1
\\} \\}
, buffer.written()); , buffer.written());
@ -254,8 +253,8 @@ const TestOptions = struct {
}; };
const TestSetup = struct { const TestSetup = struct {
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
threaded: std.Io.Threaded,
io: std.Io, io: std.Io,
map: *const std.process.Environ.Map,
options: TestOptions, options: TestOptions,
creds: aws_auth.Credentials, creds: aws_auth.Credentials,
client: aws.Client, client: aws.Client,
@ -266,7 +265,6 @@ const TestSetup = struct {
pub const RequestActuals = struct { pub const RequestActuals = struct {
request: *std.http.Client.Request, request: *std.http.Client.Request,
trace: []const u8,
// Looks like uri might be getting trounced before deinit // Looks like uri might be getting trounced before deinit
request_uri: []const u8, request_uri: []const u8,
@ -305,7 +303,6 @@ const TestSetup = struct {
} }
allocator.free(self.extra_headers); allocator.free(self.extra_headers);
allocator.free(self.trace);
allocator.free(self.request_uri); allocator.free(self.request_uri);
allocator.destroy(self.request.reader.in); allocator.destroy(self.request.reader.in);
allocator.destroy(self.request.client); allocator.destroy(self.request.client);
@ -326,24 +323,21 @@ const TestSetup = struct {
fn request( fn request(
self_ptr: usize, self_ptr: usize,
io: std.Io,
method: std.http.Method, method: std.http.Method,
uri: std.Uri, uri: std.Uri,
options: std.http.Client.RequestOptions, options: std.http.Client.RequestOptions,
) std.http.Client.RequestError!std.http.Client.Request { ) std.http.Client.RequestError!std.http.Client.Request {
_ = io;
const self: *Self = @ptrFromInt(self_ptr); const self: *Self = @ptrFromInt(self_ptr);
if (self.request_actuals) |r| { if (self.request_actuals) |_| {
std.debug.print("request has been called twice. Previous stack trace:\n", .{}); std.debug.print("request has been called twice:\n", .{});
var stderr = std.fs.File.stderr().writer(&.{});
stderr.interface.writeAll(r.trace) catch @panic("could not write to stderr");
std.debug.print("Current stack trace:\n", .{}); std.debug.print("Current stack trace:\n", .{});
std.debug.dumpCurrentStackTrace(.{}); std.debug.dumpCurrentStackTrace(.{});
return error.ConnectionRefused; // we should not be called twice return error.ConnectionRefused; // we should not be called twice
} }
const acts = try self.allocator.create(RequestActuals); const acts = try self.allocator.create(RequestActuals);
errdefer self.allocator.destroy(acts); 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); const req = try self.allocator.create(std.http.Client.Request);
errdefer self.allocator.destroy(req); errdefer self.allocator.destroy(req);
const reader = try self.allocator.create(std.Io.Reader); const reader = try self.allocator.create(std.Io.Reader);
@ -387,7 +381,6 @@ const TestSetup = struct {
}); });
acts.* = .{ acts.* = .{
.trace = try self.allocator.dupe(u8, aw.written()),
.request = req, .request = req,
.request_uri = try std.fmt.allocPrint(self.allocator, "{f}", .{uri}), .request_uri = try std.fmt.allocPrint(self.allocator, "{f}", .{uri}),
.extra_headers = try al.toOwnedSlice(self.allocator), .extra_headers = try al.toOwnedSlice(self.allocator),
@ -446,9 +439,10 @@ const TestSetup = struct {
return self.request_actuals.?.request.reader.in; return self.request_actuals.?.request.reader.in;
} }
fn init(options: TestOptions) !*Self { fn init(options: TestOptions) !*Self {
var threaded: std.Io.Threaded = .init(options.allocator); const io = std.testing.io;
const io = threaded.io(); const map = try options.allocator.create(std.process.Environ.Map);
const client = aws.Client.init(options.allocator, .{ .io = io }); 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 call_options = try options.allocator.create(aws.Options);
const self = try options.allocator.create(Self); const self = try options.allocator.create(Self);
call_options.* = .{ call_options.* = .{
@ -468,8 +462,8 @@ const TestSetup = struct {
self.* = .{ self.* = .{
.options = options, .options = options,
.allocator = options.allocator, .allocator = options.allocator,
.threaded = threaded,
.io = io, .io = io,
.map = map,
.creds = aws_auth.Credentials.init( .creds = aws_auth.Credentials.init(
options.allocator, options.allocator,
try options.allocator.dupe(u8, "ACCESS"), try options.allocator.dupe(u8, "ACCESS"),
@ -483,6 +477,7 @@ const TestSetup = struct {
return self; return self;
} }
fn deinit(self: *Self) void { fn deinit(self: *Self) void {
self.options.allocator.destroy(self.map);
if (self.response_actuals) |r| { if (self.response_actuals) |r| {
self.allocator.free(r.body); self.allocator.free(r.body);
self.allocator.destroy(r); self.allocator.destroy(r);
@ -493,7 +488,6 @@ const TestSetup = struct {
} }
self.allocator.destroy(self.call_options); self.allocator.destroy(self.call_options);
self.call_options = undefined; self.call_options = undefined;
self.threaded.deinit();
self.allocator.destroy(self); self.allocator.destroy(self);
aws_creds.static_credentials = null; 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"); try req_actuals.expectHeader("X-Amz-Target", "DynamoDB_20120810.ListTables");
const parsed_body = try std.json.parseFromSlice(struct { const parsed_body = try std.json.parseFromSlice(struct {
ExclusiveStartTableName: ?[]const u8, ExclusiveStartTableName: ?[]const u8 = null,
Limit: u8, Limit: u8,
}, std.testing.allocator, req_actuals.body.?, .{}); }, std.testing.allocator, req_actuals.body.?, .{});
defer parsed_body.deinit(); 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"); try req_actuals.expectHeader("X-Amz-Target", "AmazonEC2ContainerServiceV20141113.ListClusters");
const parsed_body = try std.json.parseFromSlice(struct { const parsed_body = try std.json.parseFromSlice(struct {
nextToken: ?[]const u8, nextToken: ?[]const u8 = null,
maxResults: u8, maxResults: u8,
}, std.testing.allocator, req_actuals.body.?, .{}); }, std.testing.allocator, req_actuals.body.?, .{});
defer parsed_body.deinit(); 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"); try req_actuals.expectHeader("X-Amz-Target", "AmazonEC2ContainerServiceV20141113.ListClusters");
const parsed_body = try std.json.parseFromSlice(struct { const parsed_body = try std.json.parseFromSlice(struct {
nextToken: ?[]const u8, nextToken: ?[]const u8 = null,
maxResults: ?u8, maxResults: ?u8 = null,
}, std.testing.allocator, req_actuals.body.?, .{}); }, std.testing.allocator, req_actuals.body.?, .{});
defer parsed_body.deinit(); 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.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.?); // try std.testing.expectEqual(@as(i64, 1.73859841557E9), call.response.authorization_data.?[0].expires_at.?);
const zeit = @import("zeit"); const expected_ts = try date.Timestamp.parse("2022-05-17T06:56:13.652000+00:00");
const expected_ins = try zeit.instant(.{ const actual = call.response.authorization_data.?[0].expires_at.?;
.source = .{ .iso8601 = "2022-05-17T06:56:13.652000+00:00" }, try std.testing.expectEqual(expected_ts, actual);
});
const expected_ts: date.Timestamp = @enumFromInt(expected_ins.timestamp);
try std.testing.expectEqual(expected_ts, call.response.authorization_data.?[0].expires_at.?);
} }
test "jsonStringify: structure + enums" { test "jsonStringify: structure + enums" {
@ -1268,6 +1258,20 @@ test "jsonStringify" {
try std.testing.expectEqualStrings("1234", json_parsed.value.arn); try std.testing.expectEqualStrings("1234", json_parsed.value.arn);
try std.testing.expectEqualStrings("bar", json_parsed.value.tags.foo); 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" { test "jsonStringify nullable object" {
// structure is not null // structure is not null
@ -1290,7 +1294,7 @@ test "jsonStringify nullable object" {
FunctionVersion: []const u8, FunctionVersion: []const u8,
Name: []const u8, Name: []const u8,
RoutingConfig: struct { RoutingConfig: struct {
AdditionalVersionWeights: ?struct {}, AdditionalVersionWeights: ?struct {} = null,
}, },
}, std.testing.allocator, request_json, .{ .ignore_unknown_fields = true }); }, std.testing.allocator, request_json, .{ .ignore_unknown_fields = true });
defer json_parsed.deinit(); defer json_parsed.deinit();
@ -1327,7 +1331,7 @@ test "works against a live server" {
const Server = struct { const Server = struct {
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
io: std.Io, io: std.Io,
ready: std.Thread.Semaphore = .{}, ready: std.Io.Semaphore = .{},
requests_received: usize = 0, requests_received: usize = 0,
thread: ?std.Thread = null, thread: ?std.Thread = null,
listening_uri: []const u8 = undefined, listening_uri: []const u8 = undefined,
@ -1349,7 +1353,7 @@ test "works against a live server" {
threadMain, threadMain,
.{self}, .{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; awshttp.endpoint_override = self.listening_uri;
if (awshttp.endpoint_override == null) return error.TestSetupStartFailure; if (awshttp.endpoint_override == null) return error.TestSetupStartFailure;
std.log.debug("endpoint override set to {?s}", .{awshttp.endpoint_override}); 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.allocator.free(self.listening_uri);
self.listening_uri = undefined; self.listening_uri = undefined;
} }
self.ready.post(); self.ready.post(self.io);
while (true) { while (true) {
var connection = try server.accept(self.io); var connection = try server.accept(self.io);
defer connection.close(self.io); defer connection.close(self.io);
@ -1411,9 +1415,8 @@ test "works against a live server" {
} }
}; };
const allocator = std.testing.allocator; const allocator = std.testing.allocator;
var threaded: std.Io.Threaded = .init(allocator); const io = std.testing.io;
defer threaded.deinit(); const map = std.process.Environ.Map.init(allocator);
const io = threaded.io();
var server = Server{ .allocator = allocator, .io = io }; var server = Server{ .allocator = allocator, .io = io };
try server.start(); try server.start();
var stopped = false; var stopped = false;
@ -1434,7 +1437,7 @@ test "works against a live server" {
// } // }
const sts = (Services(.{.sts}){}).sts; 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( const creds = aws_auth.Credentials.init(
allocator, allocator,
try allocator.dupe(u8, "ACCESS"), try allocator.dupe(u8, "ACCESS"),

View file

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

View file

@ -192,9 +192,7 @@ fn parseInternal(comptime T: type, element: *xml.Element, options: ParseOptions)
e, e,
}, },
); );
if (@errorReturnTrace()) |trace| { std.debug.dumpCurrentStackTrace(.{});
std.debug.dumpStackTrace(trace);
}
} }
return e; 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 }); 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); defer children.deinit(allocator);
switch (array_style) { switch (array_style) {