Compare commits

..

No commits in common. "abd422edb720aaac8240d3b25b8941edaf8d4a86" and "1cff425ff6812d105478be3abcb2f1cf6650d78b" have entirely different histories.

27 changed files with 356 additions and 589 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://codeberg.org/mlugg/setup-zig@v2.2.1 uses: https://github.com/mlugg/setup-zig@v2.0.5
# 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,8 +44,11 @@ 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: |
zig build package sed -i 's#// UNCOMMENT AFTER MODEL GEN TO USE IN BUILD SCRIPTS //##' build.zig
(cd zig-out/package && tar -czf ${{ runner.temp }}/${{ github.sha }}-with-models.tar.gz --format ustar *) tar -czf ${{ runner.temp }}/${{ github.sha }}-with-models.tar.gz \
--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://codeberg.org/mlugg/setup-zig@v2.2.1 uses: https://github.com/mlugg/setup-zig@v2.0.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://codeberg.org/mlugg/setup-zig@v2.2.1 uses: https://github.com/mlugg/setup-zig@v2.0.5
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://codeberg.org/mlugg/setup-zig@v2.2.1 uses: https://github.com/mlugg/setup-zig@v2.0.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,4 +12,3 @@ src/git_version.zig
zig-out zig-out
core core
.zig-cache .zig-cache
zig-pkg/

View file

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

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: v6.0.0 rev: v3.2.0
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
- id: end-of-file-fixer - id: end-of-file-fixer

202
build.zig
View file

@ -3,6 +3,8 @@ 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 },
@ -47,7 +49,7 @@ pub fn build(b: *Builder) !void {
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
}); });
configure(mod_exe, dep_mods); configure(mod_exe, dep_mods, true);
const exe = b.addExecutable(.{ const exe = b.addExecutable(.{
.name = "demo", .name = "demo",
@ -72,7 +74,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); configure(cg_mod, dep_mods, false);
const cg_exe = b.addExecutable(.{ const cg_exe = b.addExecutable(.{
.name = "codegen", .name = "codegen",
@ -112,45 +114,16 @@ pub fn build(b: *Builder) !void {
cg.dependOn(&cg_cmd.step); cg.dependOn(&cg_cmd.step);
// 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); exe.step.dependOn(cg);
// Use pre-generated models if available, otherwise use codegen output // This allows us to have each module depend on the
const service_manifest_source: std.Build.LazyPath = if (has_pre_generated) // generated service manifest.
b.path("src/models/service_manifest.zig")
else
cg_output_dir.path(b, "service_manifest.zig");
const service_manifest_module = b.createModule(.{ const service_manifest_module = b.createModule(.{
.root_source_file = service_manifest_source, .root_source_file = cg_output_dir.path(b, "service_manifest.zig"),
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
}); });
configure(service_manifest_module, dep_mods); configure(service_manifest_module, dep_mods, true);
mod_exe.addImport("service_manifest", service_manifest_module); mod_exe.addImport("service_manifest", service_manifest_module);
@ -161,13 +134,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); configure(mod_aws, dep_mods, true);
// 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); configure(mod_aws_signing, dep_mods, false);
// 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
@ -197,7 +170,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); configure(mod_unit_tests, dep_mods, true);
// 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.
@ -206,7 +179,6 @@ pub fn build(b: *Builder) !void {
.filters = test_filters, .filters = test_filters,
}); });
if (!has_pre_generated)
unit_tests.step.dependOn(cg); unit_tests.step.dependOn(cg);
unit_tests.use_llvm = !no_llvm; unit_tests.use_llvm = !no_llvm;
@ -230,7 +202,6 @@ pub fn build(b: *Builder) !void {
.filters = test_filters, .filters = test_filters,
}); });
smoke_test.use_llvm = !no_llvm; smoke_test.use_llvm = !no_llvm;
if (!has_pre_generated)
smoke_test.step.dependOn(cg); smoke_test.step.dependOn(cg);
const run_smoke_test = b.addRunArtifact(smoke_test); const run_smoke_test = b.addRunArtifact(smoke_test);
@ -241,20 +212,14 @@ 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)) void { fn configure(compile: *std.Build.Module, modules: std.StringHashMap(*std.Build.Module), include_time: bool) 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) {
@ -265,6 +230,10 @@ 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);
@ -282,140 +251,3 @@ 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.16.0", .minimum_zig_version = "0.15.1",
.dependencies = .{ .dependencies = .{
.smithy = .{ .smithy = .{
@ -29,8 +29,12 @@
.path = "lib/json", .path = "lib/json",
}, },
.case = .{ .case = .{
.url = "git+https://github.com/elerch/case?ref=zig-0.16.0#82017a92e179031f21896d02262ae1e216459e4f", .url = "git+https://github.com/travisstaloch/case.git#f8003fe5f93b65f673d10d41323e347225e8cb87",
.hash = "case-0.0.1-chGYq1fEAAAN4h3YRmkh9OTDvuUyjmoXr6PhYAUzjU0D", .hash = "case-0.0.1-chGYqx_EAADaGJjmoln5M1iMBDTrMdd8to5wdEVpfXm4",
},
.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 = if (new_state.indent_level == 0) 0 else new_state.indent_level - 1; new_state.indent_level = @max(0, 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.Io.File.OpenError || std.Io.File.ReadStreamingError || std.Io.File.StatError; const Error = std.fs.File.OpenError || std.fs.File.ReadError || std.fs.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.Io.Dir.Walker.Entry) bool; pub const walkerFn = *const fn (std.fs.Dir.Walker.Entry) bool;
fn included(entry: std.Io.Dir.Walker.Entry) bool { fn included(entry: std.fs.Dir.Walker.Entry) bool {
_ = entry; _ = entry;
return true; return true;
} }
fn excluded(entry: std.Io.Dir.Walker.Entry) bool { fn excluded(entry: std.fs.Dir.Walker.Entry) bool {
_ = entry; _ = entry;
return false; return false;
} }
@ -94,33 +94,33 @@ pub const ComputeDirectoryOptions = struct {
}; };
pub fn computeDirectoryHash( pub fn computeDirectoryHash(
allocator: std.mem.Allocator, thread_pool: *std.Thread.Pool,
io: std.Io, dir: std.fs.Dir,
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(allocator); var arena_instance = std.heap.ArenaAllocator.init(gpa);
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(allocator, 300); var all_files = try std.ArrayList(*HashedFile).initCapacity(gpa, 300);
defer all_files.deinit(allocator); defer all_files.deinit(gpa);
var walker = try dir.walk(allocator); var walker = try dir.walk(gpa);
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 g: std.Io.Group = .init; var wait_group: std.Thread.WaitGroup = .{};
errdefer g.cancel(io); defer wait_group.wait();
while (try walker.next(io)) |entry| { while (try walker.next()) |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) allocator else arena; const alloc = if (options.needFileHashes) gpa 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
}; };
g.async(io, workerHashFile, .{ io, dir, hashed_file, &g }); wait_group.start();
try thread_pool.spawn(workerHashFile, .{ dir, hashed_file, &wait_group });
try all_files.append(allocator, hashed_file); try all_files.append(gpa, 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,26 +156,23 @@ 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(allocator); if (options.needFileHashes) options.fileHashes = try all_files.toOwnedSlice(gpa);
return hasher.finalResult(); return hasher.finalResult();
} }
fn workerHashFile(io: std.Io, dir: std.Io.Dir, hashed_file: *HashedFile, wg: *std.Io.Group) void { fn workerHashFile(dir: std.fs.Dir, hashed_file: *HashedFile, wg: *std.Thread.WaitGroup) void {
_ = wg; // assume here that 0.16.0 Io.Group no longer needs to be notified at the time of completion defer wg.finish();
hashed_file.failure = hashFileFallible(io, dir, hashed_file); hashed_file.failure = hashFileFallible(dir, hashed_file);
} }
fn hashFileFallible(io: std.Io, dir: std.Io.Dir, hashed_file: *HashedFile) HashedFile.Error!void { fn hashFileFallible(dir: std.fs.Dir, hashed_file: *HashedFile) HashedFile.Error!void {
var buf: [8000]u8 = undefined; var buf: [8000]u8 = undefined;
var file = try dir.openFile(io, hashed_file.fs_path, .{}); var file = try dir.openFile(hashed_file.fs_path, .{});
defer file.close(io); defer file.close();
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(io, file)) }); hasher.update(&.{ 0, @intFromBool(try isExecutable(file)) });
while (true) { while (true) {
const bytes_read = file.readStreaming(io, &.{&buf}) catch |err| switch (err) { const bytes_read = try file.read(&buf);
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]);
} }
@ -200,7 +197,7 @@ fn normalizePath(arena: std.mem.Allocator, fs_path: []const u8) ![]const u8 {
return normalized; return normalized;
} }
fn isExecutable(io: std.Io, file: std.Io.File) !bool { fn isExecutable(file: std.fs.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
@ -208,7 +205,7 @@ fn isExecutable(io: std.Io, file: std.Io.File) !bool {
// when unpacking the tarball. // when unpacking the tarball.
return false; return false;
} else { } else {
const stat = try file.stat(io); const stat = try file.stat();
return stat.kind == .file and (stat.permissions.toMode() & std.posix.S.IXUSR != 0); return (stat.mode & std.posix.S.IXUSR) != 0;
} }
} }

View file

@ -25,21 +25,27 @@ 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(init: std.process.Init) anyerror!void { pub fn main() anyerror!void {
const io = init.io; const root_progress_node = std.Progress.start(.{});
const root_progress_node = std.Progress.start(io, .{});
defer root_progress_node.end(); defer root_progress_node.end();
const allocator = init.arena.allocator(); var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
const args = try init.minimal.args.toSlice(allocator); var threaded: std.Io.Threaded = .init(allocator);
var stdout_writer = std.Io.File.stdout().writer(io, &.{}); defer threaded.deinit();
const io = threaded.io();
const args = try std.process.argsAlloc(allocator);
defer std.process.argsFree(allocator, args);
var stdout_writer = std.fs.File.stdout().writer(&.{});
const stdout = &stdout_writer.interface; const stdout = &stdout_writer.interface;
var output_dir = std.Io.Dir.cwd(); var output_dir = std.fs.cwd();
defer if (output_dir.handle > 0) output_dir.close(io); defer if (output_dir.fd > 0) output_dir.close();
var models_dir: ?std.Io.Dir = null; var models_dir: ?std.fs.Dir = null;
defer if (models_dir) |*m| m.close(io); defer if (models_dir) |*m| m.close();
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))
@ -49,17 +55,15 @@ pub fn main(init: std.process.Init) 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))
try output_dir.createDirPath(io, args[i + 1]); output_dir = try output_dir.makeOpenPath(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.Io.Dir.cwd().openDir(io, args[i + 1], .{ .iterate = true }); models_dir = try std.fs.cwd().openDir(args[i + 1], .{ .iterate = true });
} }
var manifest_file = try output_dir.createFile(io, "service_manifest.zig", .{}); var manifest_file = try output_dir.createFile("service_manifest.zig", .{});
defer manifest_file.close(io); defer manifest_file.close();
var manifest = manifest_file.writer(io, &manifest_buf).interface; var manifest = manifest_file.writer(&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;
@ -88,20 +92,20 @@ pub fn main(init: std.process.Init) anyerror!void {
// can be made // can be made
if (models_dir) |m| { if (models_dir) |m| {
var cwd = try std.Io.Dir.cwd().openDir(io, ".", .{}); var cwd = try std.fs.cwd().openDir(".", .{});
defer cwd.close(io); defer cwd.close();
defer std.process.setCurrentDir(io, cwd) catch unreachable; defer cwd.setAsCwd() catch unreachable;
try std.process.setCurrentDir(io, m); try m.setAsCwd();
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.Io.File.stdin(), stdout); _ = try generateServices(allocator, io, ";", std.fs.File.stdin(), stdout);
if (verbose) { if (verbose) {
const output_path = try output_dir.realPathFileAlloc(io, ".", allocator); const output_path = try output_dir.realpathAlloc(allocator, ".");
std.debug.print("Output path: {s}\n", .{output_path}); std.debug.print("Output path: {s}\n", .{output_path});
} }
} }
@ -110,23 +114,25 @@ 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.Io.Dir, output_dir: std.Io.Dir, parent_progress: *const std.Progress.Node) !void { fn processDirectories(io: std.Io, models_dir: std.fs.Dir, output_dir: std.fs.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(io, "output_manifest.json", allocator, .unlimited) catch null output_dir.readFileAlloc("output_manifest.json", allocator, .unlimited) catch null
else else
output_dir.readFileAlloc(io, allocator, "output_manifest.json", std.math.maxInt(usize)) catch null; output_dir.readFileAlloc(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
@ -141,16 +147,16 @@ fn processDirectories(io: std.Io, models_dir: std.Io.Dir, output_dir: std.Io.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(io, "service_manifest.zig", .{}); const manifest_file = try output_dir.createFile("service_manifest.zig", .{});
defer manifest_file.close(io); defer manifest_file.close();
var manifest = manifest_file.writer(io, &manifest_buf); var manifest = manifest_file.writer(&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(io)) |e| { while (try mi.next()) |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();
@ -158,26 +164,26 @@ fn processDirectories(io: std.Io, models_dir: std.Io.Dir, output_dir: std.Io.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(allocator, io, models_dir, output_dir); _, calculated_manifest = try calculateDigests(models_dir, output_dir, &thread_pool);
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(io, .{ .sub_path = "output_manifest.json", .data = data }); try output_dir.writeFile(.{ .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(allocator: std.mem.Allocator, io: std.Io, models_dir: std.Io.Dir, output_dir: std.Io.Dir) !struct { usize, OutputManifest } { fn calculateDigests(models_dir: std.fs.Dir, output_dir: std.fs.Dir, thread_pool: *std.Thread.Pool) !struct { usize, OutputManifest } {
const Include = struct { const Include = struct {
threadlocal var count: usize = 0; threadlocal var count: usize = 0;
pub fn include(entry: std.Io.Dir.Walker.Entry) bool { pub fn include(entry: std.fs.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(allocator, io, models_dir, @constCast(&Hasher.ComputeDirectoryOptions{ const model_hash = if (model_digest) |m| m[0..Hasher.digest_len].* else try Hasher.computeDirectoryHash(thread_pool, models_dir, @constCast(&Hasher.ComputeDirectoryOptions{
.isIncluded = Include.include, .isIncluded = Include.include,
.isExcluded = struct { .isExcluded = struct {
pub fn exclude(entry: std.Io.Dir.Walker.Entry) bool { pub fn exclude(entry: std.fs.Dir.Walker.Entry) bool {
_ = entry; _ = entry;
return false; return false;
} }
@ -186,18 +192,14 @@ fn calculateDigests(allocator: std.mem.Allocator, io: std.Io, models_dir: std.Io
})); }));
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(allocator, io, try output_dir.openDir( const output_hash = try Hasher.computeDirectoryHash(thread_pool, try output_dir.openDir(".", .{ .iterate = true }), @constCast(&Hasher.ComputeDirectoryOptions{
io,
".",
.{ .iterate = true },
), @constCast(&Hasher.ComputeDirectoryOptions{
.isIncluded = struct { .isIncluded = struct {
pub fn include(entry: std.Io.Dir.Walker.Entry) bool { pub fn include(entry: std.fs.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.Io.Dir.Walker.Entry) bool { pub fn exclude(entry: std.fs.Dir.Walker.Entry) bool {
_ = entry; _ = entry;
return false; return false;
} }
@ -206,14 +208,13 @@ fn calculateDigests(allocator: std.mem.Allocator, io: std.Io, models_dir: std.Io
})); }));
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.Io.Dir, manifest: *std.Io.Writer) !void { fn processFile(io: std.Io, file_name: []const u8, output_dir: std.fs.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
@ -231,6 +232,7 @@ fn processFile(io: std.Io, file_name: []const u8, output_dir: std.Io.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");
@ -270,9 +272,9 @@ fn processFile(io: std.Io, file_name: []const u8, output_dir: std.Io.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(io, output_file_name, .{ .truncate = true }); var file = try output_dir.createFile(output_file_name, .{ .truncate = true });
defer file.close(io); defer file.close();
try file.writeStreamingAll(io, formatted); try file.writeAll(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) });
@ -296,8 +298,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.Io.Dir.cwd().openFile(io, path, .{}); const file = try std.fs.cwd().openFile(path, .{});
defer file.close(io); defer file.close();
return try generateServices(allocator, io, terminator, file, writer); return try generateServices(allocator, io, terminator, file, writer);
} }
@ -402,7 +404,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.Io.File, file: std.fs.File,
writer: *std.Io.Writer, writer: *std.Io.Writer,
) ![][]const u8 { ) ![][]const u8 {
var fbuf: [1024]u8 = undefined; var fbuf: [1024]u8 = undefined;
@ -427,14 +429,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) = .empty; var stack: std.ArrayList([]const u8) = .{};
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) = .empty; var unresolved: std.ArrayList(smithy.ShapeInfo) = .{};
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();
@ -524,7 +526,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) = .empty; var type_stack: std.ArrayList(*const smithy.ShapeInfo) = .{};
defer type_stack.deinit(allocator); defer type_stack.deinit(allocator);
const state = GenerationState{ const state = GenerationState{
.type_stack = &type_stack, .type_stack = &type_stack,
@ -575,7 +577,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) = .empty; var type_stack: std.ArrayList(*const smithy.ShapeInfo) = .{};
defer type_stack.deinit(allocator); defer type_stack.deinit(allocator);
const state = GenerationState{ const state = GenerationState{
.type_stack = &type_stack, .type_stack = &type_stack,
@ -583,7 +585,8 @@ fn generateOperation(allocator: std.mem.Allocator, operation: smithy.ShapeInfo,
.allocator = allocator, .allocator = allocator,
.indent_level = 1, .indent_level = 1,
}; };
const child_state = state.indent(); var child_state = state;
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);
@ -649,7 +652,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 = @as(u10, {d}),\n", .{trait.http.code}); try writer.print(".success_code = {d},\n", .{trait.http.code});
try outputIndent(state, writer); try outputIndent(state, writer);
_ = try writer.write("};\n\n"); _ = try writer.write("};\n\n");
} }
@ -684,17 +687,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 = child_state.indent(); child_state.indent_level += 1;
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 = child_state.indent(); child_state.indent_level += 1;
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 = child_state.deindent(); child_state.indent_level -= 1;
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);
@ -870,7 +873,8 @@ 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");
const child_state = state.indent(); var child_state = state;
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));
@ -919,7 +923,8 @@ 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");
const child_state = state.indent(); var child_state = state;
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
@ -1006,7 +1011,8 @@ 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");
const grandchild_state = child_state.indent(); var grandchild_state = child_state;
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);
@ -1032,7 +1038,8 @@ 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");
const child_state = state.indent(); var child_state = state;
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) = .empty; var json_members = std.ArrayListUnmanaged(JsonMember){};
var iter = hash_map.iterator(); var iter = hash_map.iterator();
while (iter.next()) |kvp| { while (iter.next()) |kvp| {
@ -146,8 +146,6 @@ 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,
@ -198,9 +196,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,
@ -216,7 +214,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");
} }
} }
@ -270,7 +268,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");
} }
} }
@ -329,10 +327,9 @@ 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,
@ -348,7 +345,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");
} }
} }
@ -364,16 +361,7 @@ 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;
} }
@ -401,5 +389,4 @@ 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/e41f98b389539c8bc6b1a231d25e2980318e5ef4/e41f98b389539c8bc6b1a231d25e2980318e5ef4-with-models.tar.gz", .url = "https://git.lerch.org/api/packages/lobo/generic/aws-sdk-with-models/9b870aa969124de05de2a71e0afb9050a2998b14/9b870aa969124de05de2a71e0afb9050a2998b14nightly-zig-with-models.tar.gz",
.hash = "aws-0.0.1-SbsFcI0RCgBdf1nak95gi1kAtI6sv3Ntb7BPETH30fpS", .hash = "aws-0.0.1-SbsFcFsaCgBDSmjnC9Lue34UN_csGkkAEBJ4EkUl9r6w",
}, },
}, },
} }

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/rockorager/zeit#2a79678e05e4e82cd4efd4fd6b754dcf029c3a64", .url = "git+https://github.com/elerch/zeit#8190461dc1f892f6370fa9d5cd76690aac0e1c71",
.hash = "zeit-0.6.0-5I6bk7q6AgBdMJxze3D4l9ylQhkviQ_BX9FigDt13MFn", .hash = "zeit-0.6.0-5I6bk99-AgDNMIDuw2Zcoe_9QYIpzwZJqeqMpU54egTd",
}, },
.json = .{ .json = .{
.path = "../json", .path = "../json",

View file

@ -1,7 +1,6 @@
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,
@ -38,13 +37,12 @@ pub const DateTime = struct {
} }
pub fn instant(self: DateTime) !zeit.Instant { pub fn instant(self: DateTime) !zeit.Instant {
return try instantWithoutIo(.{ .source = .{ .time = self.time() } }); return try zeit.instant(.{ .source = .{ .time = self.time() } });
} }
}; };
pub fn timestampToDateTime(timestamp: i64) DateTime { pub fn timestampToDateTime(timestamp: zeit.Seconds) DateTime {
// zeit.Seconds is i64, so this should be identical const ins = zeit.instant(.{ .source = .{ .unix_timestamp = timestamp } }) catch @panic("Failed to create instant from timestamp");
const ins = instantWithoutIo(.{ .source = .{ .unix_timestamp = timestamp } }) catch @panic("Failed to create instant from timestamp");
return DateTime.fromInstant(ins); return DateTime.fromInstant(ins);
} }
@ -55,7 +53,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 instantWithoutIo(.{ .source = .{ .rfc1123 = data } }); const ins = try zeit.instant(.{ .source = .{ .rfc1123 = data } });
return DateTime.fromInstant(ins); return DateTime.fromInstant(ins);
} }
@ -66,7 +64,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 instantWithoutIo(.{ .source = .{ .iso8601 = data } }); const ins = try zeit.instant(.{ .source = .{ .iso8601 = data } });
return DateTime.fromInstant(ins); return DateTime.fromInstant(ins);
} }
@ -86,7 +84,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); const now = std.Io.Clock.Timestamp.now(io, .awake) catch return;
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 = instantWithoutIo(.{ const instant = zeit.instant(.{
.source = .{ .source = .{
.unix_nano = @intFromEnum(value), .unix_nano = @intFromEnum(value),
}, },
@ -34,7 +34,7 @@ pub const Timestamp = enum(zeit.Nanoseconds) {
} }
}; };
const ins = try instantWithoutIo(.{ const ins = try zeit.instant(.{
.source = switch (date_format) { .source = switch (date_format) {
DateFormat.iso8601 => .{ DateFormat.iso8601 => .{
.iso8601 = val, .iso8601 = val,
@ -49,36 +49,6 @@ 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.array_hash_map.String; const StringArrayHashMap = std.StringArrayHashMap;
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 std.enums.fromInt(T, @as(i128, @intFromFloat(n))) orelse error.InvalidEnumTag; return try std.meta.intToEnum(T, @as(i128, @intFromFloat(n)));
} }
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 std.enums.fromInt(T, n) orelse error.InvalidEnumTag; return try std.meta.intToEnum(T, n);
}, },
.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).empty; var arraylist = std.ArrayList(ptrInfo.child){};
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).empty; var arraylist = std.ArrayList(ptrInfo.child){};
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,6 +19,7 @@ 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;
@ -81,9 +82,8 @@ 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_status: std.http.Status = .ok, success_http_code: i64 = 200,
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 {
response_status: std.http.Status, http_code: i64,
response_body: []const u8, response_body: []const u8,
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
@ -114,7 +114,6 @@ 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,
@ -125,7 +124,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.map, options.proxy), .aws_http = awshttp.AwsHttp.init(allocator, options.io, options.proxy),
}; };
} }
pub fn deinit(self: *Client) void { pub fn deinit(self: *Client) void {
@ -196,7 +195,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).empty; var al = std.ArrayList([]const u8){};
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,
@ -233,18 +232,8 @@ 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))
// Buried in the tests are our answer here: try buffer.writer.print("{f}", .{std.json.fmt(request, .{ .whitespace = .indent_4 })});
// 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;
@ -305,9 +294,14 @@ pub fn Request(comptime request_action: anytype) type {
} }
} }
var rest_options = options; return try Self.callAws(aws_request, .{
rest_options.success_http_status = @enumFromInt(Action.http_config.success_code); .success_http_code = Action.http_config.success_code,
return try Self.callAws(aws_request, rest_options); .region = options.region,
.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)
@ -327,20 +321,11 @@ 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, .{ .{std.json.fmt(request, .{ .whitespace = .indent_4 })},
.whitespace = .indent_4,
.emit_null_optional_fields = false,
})},
); );
defer options.client.allocator.free(body); defer options.client.allocator.free(body);
@ -412,27 +397,16 @@ 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_status) { if (response.response_code != options.success_http_code and response.response_code != 404) {
// If the consumer prrovided diagnostics, they are likely handling try reportTraffic(options.client.allocator, "Call Failed", aws_request, response, log.err);
// 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.response_status = response.response_code; d.http_code = 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;
} }
@ -481,7 +455,9 @@ 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});
std.debug.dumpCurrentStackTrace(.{}); if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace);
}
}; };
break; break;
@ -1210,10 +1186,7 @@ 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(), "\"");
@ -1472,7 +1445,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).empty; var al = std.ArrayList([]const u8){};
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{
@ -1485,7 +1458,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).empty; var al = std.ArrayList([]const u8){};
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,11 +69,6 @@ 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 {
@ -82,18 +77,9 @@ 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, map: *const std.process.Environ.Map, io: std.Io, options: Options) !auth.Credentials { pub fn getCredentials(allocator: std.mem.Allocator, io: std.Io, options: Options) !auth.Credentials {
if (static_credentials) |c| return c; if (static_credentials) |c| return c;
if (options.profile.prefer_profile_from_file) { if (try getEnvironmentCredentials(allocator)) |cred| {
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;
} }
@ -101,31 +87,32 @@ pub fn getCredentials(allocator: std.mem.Allocator, map: *const std.process.Envi
// 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, map, options.profile)) |cred| return cred; if (try getProfileCredentials(allocator, io, options.profile)) |cred| return cred;
if (try getContainerCredentials(allocator, io, map)) |cred| return cred; if (try getContainerCredentials(allocator, io)) |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, map: *const std.process.Environ.Map) !?auth.Credentials { fn getEnvironmentCredentials(allocator: std.mem.Allocator) !?auth.Credentials {
const secret_key = getEnvironmentVariable(map, "AWS_SECRET_ACCESS_KEY") orelse return null; const secret_key = (try getEnvironmentVariable(allocator, "AWS_SECRET_ACCESS_KEY")) orelse return null;
const access_key = getEnvironmentVariable(map, "AWS_ACCESS_KEY_ID") 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 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 allocator.dupe(u8, access_key), (try getEnvironmentVariable(allocator, "AWS_ACCESS_KEY_ID")) orelse return null,
try allocator.dupe(u8, secret_key), try allocator.dupe(u8, secret_key),
if (token) |t| try allocator.dupe(u8, t) else null, (try getEnvironmentVariable(allocator, "AWS_SESSION_TOKEN")) orelse
try getEnvironmentVariable(allocator, "AWS_SECURITY_TOKEN"), // Security token is backward compat only
); );
} }
fn getEnvironmentVariable(map: *const std.process.Environ.Map, key: []const u8) ?[]const u8 { fn getEnvironmentVariable(allocator: std.mem.Allocator, key: []const u8) !?[]const u8 {
if (!map.contains(key)) return null; return std.process.getEnvVarOwned(allocator, key) catch |e| switch (e) {
return map.get(key); std.process.GetEnvVarOwnedError.EnvironmentVariableNotFound => return null,
else => return e,
};
} }
fn getWebIdentityToken(allocator: std.mem.Allocator) !?auth.Credentials { fn getWebIdentityToken(allocator: std.mem.Allocator) !?auth.Credentials {
@ -138,7 +125,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, map: *const std.process.Environ.Map) !?auth.Credentials { fn getContainerCredentials(allocator: std.mem.Allocator, io: std.Io) !?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"].
@ -179,7 +166,8 @@ fn getContainerCredentials(allocator: std.mem.Allocator, io: std.Io, map: *const
// //
// 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 = getEnvironmentVariable(map, "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") orelse return null; const container_relative_uri = (try getEnvironmentVariable(allocator, "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);
@ -212,7 +200,9 @@ fn getContainerCredentials(allocator: std.mem.Allocator, io: std.Io, map: *const
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});
std.debug.dumpCurrentStackTrace(.{}); if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace);
}
return null; return null;
}; };
@ -308,7 +298,9 @@ 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});
std.debug.dumpCurrentStackTrace(.{}); if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace);
}
return null; return null;
}; };
defer imds_response.deinit(); defer imds_response.deinit();
@ -361,7 +353,10 @@ 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});
std.debug.dumpCurrentStackTrace(.{}); if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace);
}
return null; return null;
}; };
defer imds_response.deinit(); defer imds_response.deinit();
@ -388,13 +383,12 @@ fn getImdsCredentials(allocator: std.mem.Allocator, client: *std.http.Client, ro
} }
fn getProfileCredentials(allocator: std.mem.Allocator, io: std.Io, map: *const std.process.Environ.Map, options: Profile) !?auth.Credentials { fn getProfileCredentials(allocator: std.mem.Allocator, io: std.Io, 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,
@ -404,9 +398,8 @@ fn getProfileCredentials(allocator: std.mem.Allocator, io: std.Io, map: *const s
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,
map, options.credential_file,
options.config_file, "AWS_SHARED_CREDENTIALS_FILE",
"AWS_CONFIG_FILE",
default_path, default_path,
"config", "config",
); );
@ -414,21 +407,21 @@ fn getProfileCredentials(allocator: std.mem.Allocator, io: std.Io, map: *const s
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 allocator.dupe(u8, getEnvironmentVariable(map, "AWS_PROFILE") orelse const profile = (try getEnvironmentVariable(allocator, "AWS_PROFILE")) orelse
options.profile_name orelse "default"); try allocator.dupe(u8, "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.Io.Dir.openFileAbsolute(io, creds_file_path.evaluated_path, .{}) catch null; const credentials_file = std.fs.openFileAbsolute(creds_file_path.evaluated_path, .{}) catch null;
defer if (credentials_file) |f| f.close(io); defer if (credentials_file) |f| f.close();
// 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.Io.Dir.openFileAbsolute(io, creds_file_path.evaluated_path, .{}) catch null; const config_file = std.fs.openFileAbsolute(creds_file_path.evaluated_path, .{}) catch null;
defer if (config_file) |f| f.close(io); defer if (config_file) |f| f.close();
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);
@ -468,7 +461,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.Io.File, profile: []const u8) !PartialCredentials { fn credsForFile(allocator: std.mem.Allocator, io: std.Io, file: ?std.fs.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);
@ -603,7 +596,6 @@ 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,
@ -611,28 +603,39 @@ 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 (getEnvironmentVariable(map, env_var_name)) |v| return EvaluatedPath{ .evaluated_path = try allocator.dupe(u8, v) }; if (try getEnvironmentVariable(allocator, env_var_name)) |v| return EvaluatedPath{ .evaluated_path = v };
// Not in environment variable either. Go fish // Not in environment variable either. Go fish
return try getDefaultPath(allocator, map, config_dir, ".aws", config_file_name); return try getDefaultPath(allocator, 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, map: *const std.process.Environ.Map, home_dir: ?[]const u8, dir: []const u8, file: []const u8) !EvaluatedPath { fn getDefaultPath(allocator: std.mem.Allocator, home_dir: ?[]const u8, dir: []const u8, file: []const u8) !EvaluatedPath {
const home = home_dir orelse try getHomeDir(allocator, map); const home = home_dir orelse try getHomeDir(allocator);
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, map: *const std.process.Environ.Map) ![]const u8 { fn getHomeDir(allocator: std.mem.Allocator) ![]const u8 {
const env_key = switch (builtin.os.tag) { switch (builtin.os.tag) {
.windows => "USERPROFILE", .windows => {
.macos, .linux, .freebsd, .netbsd, .dragonfly, .openbsd, .illumos => "HOME", return std.process.getEnvVarOwned(allocator, "USERPROFILE") catch |err| switch (err) {
error.OutOfMemory => |e| return e,
else => return error.HomeDirUnavailable,
};
},
.macos, .linux, .freebsd, .netbsd, .dragonfly, .openbsd, .illumos => {
const home_dir = std.posix.getenv("HOME") orelse {
// TODO look in /etc/passwd
return error.HomeDirUnavailable;
};
return allocator.dupe(u8, home_dir);
},
// 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 => {
@ -648,26 +651,17 @@ fn getHomeDir(allocator: std.mem.Allocator, map: *const std.process.Environ.Map)
// } // }
// }, // },
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, &map, null, "NOTHING", null, "hello"); const path = try filePath(allocator, 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,8 +90,6 @@ 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,
}; };
@ -103,7 +101,6 @@ 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,
@ -112,8 +109,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, io: std.Io, method: std.http.Method, uri: std.Uri, options: std.http.Client.RequestOptions) std.http.Client.RequestError!std.http.Client.Request { fn request(m: Mock, method: std.http.Method, uri: std.Uri, options: std.http.Client.RequestOptions) std.http.Client.RequestError!std.http.Client.Request {
return m.request_fn(m.context, io, method, uri, options); return m.request_fn(m.context, 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);
@ -148,16 +145,14 @@ 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, map: *const std.process.Environ.Map, proxy: ?std.http.Client.Proxy) Self { pub fn init(allocator: std.mem.Allocator, io: std.Io, 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
}; };
} }
@ -189,11 +184,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, self.map, options); const endpoint = try endpointForRequest(self.allocator, service, request, 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.map, self.io, options.credential_options); const creds = try credentials.getCredentials(self.allocator, self.io, .{});
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),
@ -241,7 +236,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) = .empty; var request_headers = std.ArrayList(std.http.Header){};
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);
@ -255,7 +250,7 @@ pub const AwsHttp = struct {
} }
} }
var headers: std.ArrayList(std.http.Header) = .empty; var headers = std.ArrayList(std.http.Header){};
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 });
@ -290,7 +285,7 @@ pub const AwsHttp = struct {
}; };
var req = if (options.mock) |m| var req = if (options.mock) |m|
try m.request(self.io, method, uri, req_options) // This will call the test harness try m.request(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();
@ -331,7 +326,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) = .empty; var resp_headers = std.ArrayList(Header){};
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,
@ -368,11 +363,10 @@ 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 = response.head.status, .response_code = @intFromEnum(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;
} }
@ -416,22 +410,25 @@ fn addHeaders(
return null; return null;
} }
fn getEnvironmentVariable(map: *const std.process.Environ.Map, key: []const u8) ?[]const u8 { fn getEnvironmentVariable(allocator: std.mem.Allocator, key: []const u8) !?[]const u8 {
if (!map.contains(key)) return null; return std.process.getEnvVarOwned(allocator, key) catch |e| switch (e) {
return map.get(key); std.process.GetEnvVarOwnedError.EnvironmentVariableNotFound => return null,
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, map: *const std.process.Environ.Map, options: Options) !EndPoint { fn endpointForRequest(allocator: std.mem.Allocator, service: []const u8, request: HttpRequest, 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 = getEnvironmentVariable(map, "AWS_ENDPOINT_URL"); const environment_override = try getEnvironmentVariable(allocator, "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);
} }
@ -578,8 +575,7 @@ test "endpointForRequest standard operation" {
const allocator = std.testing.allocator; const allocator = std.testing.allocator;
const service = "dynamodb"; const service = "dynamodb";
const map = std.process.Environ.Map.init(allocator); const endpoint = try endpointForRequest(allocator, service, request, options);
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);
} }
@ -594,8 +590,7 @@ test "endpointForRequest for cloudfront" {
const allocator = std.testing.allocator; const allocator = std.testing.allocator;
const service = "cloudfront"; const service = "cloudfront";
const map = std.process.Environ.Map.init(allocator); const endpoint = try endpointForRequest(allocator, service, request, options);
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);
} }
@ -610,8 +605,7 @@ test "endpointForRequest for s3" {
const allocator = std.testing.allocator; const allocator = std.testing.allocator;
const service = "s3"; const service = "s3";
const map = std.process.Environ.Map.init(allocator); const endpoint = try endpointForRequest(allocator, service, request, options);
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);
} }
@ -627,8 +621,7 @@ test "endpointForRequest for s3 - specific bucket" {
const allocator = std.testing.allocator; const allocator = std.testing.allocator;
const service = "s3"; const service = "s3";
const map = std.process.Environ.Map.init(allocator); const endpoint = try endpointForRequest(allocator, service, request, options);
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,12 +9,10 @@ pub const Request = struct {
headers: []const std.http.Header = &.{}, headers: []const std.http.Header = &.{},
}; };
pub const Result = struct { pub const Result = struct {
response_code: std.http.Status, response_code: u16, // actually 3 digits can fit in u10
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 = std.Io.Clock.Timestamp.now(io, .real); const now = try std.Io.Clock.Timestamp.now(io, .awake);
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,7 +336,9 @@ 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});
std.debug.dumpCurrentStackTrace(.{}); if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace);
}
return; return;
}; };
@ -367,7 +369,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).empty; var al = std.ArrayList(std.http.Header){};
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);
@ -809,7 +811,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).empty; var sort_me = std.ArrayList([]const u8){};
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);
@ -1103,7 +1105,9 @@ test "can sign" {
// [debug] (awshttp): Content-Length: 43 // [debug] (awshttp): Content-Length: 43
const allocator = std.testing.allocator; const allocator = std.testing.allocator;
const io = std.testing.io; var threaded: std.Io.Threaded = .init(allocator);
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" });
@ -1155,7 +1159,9 @@ 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;
const io = std.testing.io; var threaded: std.Io.Threaded = .init(allocator);
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");
@ -1208,7 +1214,9 @@ 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;
const io = std.testing.io; var threaded: std.Io.Threaded = .init(allocator);
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 {} = null, EncryptionContext: ?struct {},
GrantTokens: [][]const u8, GrantTokens: [][]const u8,
EncryptionAlgorithm: []const u8, EncryptionAlgorithm: []const u8,
DryRun: bool, DryRun: bool,
@ -166,6 +166,7 @@ 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());
@ -253,8 +254,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,
@ -265,6 +266,7 @@ 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,
@ -303,6 +305,7 @@ 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);
@ -323,21 +326,24 @@ 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) |_| { if (self.request_actuals) |r| {
std.debug.print("request has been called twice:\n", .{}); std.debug.print("request has been called twice. Previous stack trace:\n", .{});
var stderr = std.fs.File.stderr().writer(&.{});
stderr.interface.writeAll(r.trace) catch @panic("could not write to stderr");
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);
@ -381,6 +387,7 @@ 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),
@ -439,10 +446,9 @@ 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 {
const io = std.testing.io; var threaded: std.Io.Threaded = .init(options.allocator);
const map = try options.allocator.create(std.process.Environ.Map); const io = threaded.io();
map.* = std.process.Environ.Map.init(options.allocator); const client = aws.Client.init(options.allocator, .{ .io = io });
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.* = .{
@ -462,8 +468,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"),
@ -477,7 +483,6 @@ 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);
@ -488,6 +493,7 @@ 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;
} }
@ -644,7 +650,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 = null, ExclusiveStartTableName: ?[]const u8,
Limit: u8, Limit: u8,
}, std.testing.allocator, req_actuals.body.?, .{}); }, std.testing.allocator, req_actuals.body.?, .{});
defer parsed_body.deinit(); defer parsed_body.deinit();
@ -713,7 +719,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 = null, nextToken: ?[]const u8,
maxResults: u8, maxResults: u8,
}, std.testing.allocator, req_actuals.body.?, .{}); }, std.testing.allocator, req_actuals.body.?, .{});
defer parsed_body.deinit(); defer parsed_body.deinit();
@ -753,8 +759,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 = null, nextToken: ?[]const u8,
maxResults: ?u8 = null, maxResults: ?u8,
}, std.testing.allocator, req_actuals.body.?, .{}); }, std.testing.allocator, req_actuals.body.?, .{});
defer parsed_body.deinit(); defer parsed_body.deinit();
@ -1177,9 +1183,13 @@ 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 expected_ts = try date.Timestamp.parse("2022-05-17T06:56:13.652000+00:00"); const zeit = @import("zeit");
const actual = call.response.authorization_data.?[0].expires_at.?; const expected_ins = try zeit.instant(.{
try std.testing.expectEqual(expected_ts, actual); .source = .{ .iso8601 = "2022-05-17T06:56:13.652000+00:00" },
});
const expected_ts: date.Timestamp = @enumFromInt(expected_ins.timestamp);
try std.testing.expectEqual(expected_ts, call.response.authorization_data.?[0].expires_at.?);
} }
test "jsonStringify: structure + enums" { test "jsonStringify: structure + enums" {
@ -1258,20 +1268,6 @@ 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
@ -1294,7 +1290,7 @@ test "jsonStringify nullable object" {
FunctionVersion: []const u8, FunctionVersion: []const u8,
Name: []const u8, Name: []const u8,
RoutingConfig: struct { RoutingConfig: struct {
AdditionalVersionWeights: ?struct {} = null, AdditionalVersionWeights: ?struct {},
}, },
}, 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();
@ -1331,7 +1327,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.Io.Semaphore = .{}, ready: std.Thread.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,
@ -1353,7 +1349,7 @@ test "works against a live server" {
threadMain, threadMain,
.{self}, .{self},
); );
try self.ready.wait(self.io); // This could hang the test... try self.ready.timedWait(1000 * std.time.ns_per_ms);
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});
@ -1382,7 +1378,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.io); self.ready.post();
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);
@ -1415,8 +1411,9 @@ test "works against a live server" {
} }
}; };
const allocator = std.testing.allocator; const allocator = std.testing.allocator;
const io = std.testing.io; var threaded: std.Io.Threaded = .init(allocator);
const map = std.process.Environ.Map.init(allocator); defer threaded.deinit();
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;
@ -1437,7 +1434,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, .map = &map }); const client = aws.Client.init(std.testing.allocator, .{ .io = io });
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,9 +32,10 @@ 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
const locked = std.debug.lockStderr(&.{}); std.debug.lockStdErr();
defer std.debug.unlockStderr(); defer std.debug.unlockStdErr();
const stderr = &locked.file_writer.interface; var stderr_writer = std.fs.File.stderr().writer(&.{});
const stderr = &stderr_writer.interface;
nosuspend stderr.print(prefix ++ format ++ "\n", args) catch return; nosuspend stderr.print(prefix ++ format ++ "\n", args) catch return;
} }
@ -58,15 +59,16 @@ const Tests = enum {
rest_xml_work_with_s3, rest_xml_work_with_s3,
}; };
pub fn main(init: std.process.Init) anyerror!void { pub fn main() anyerror!void {
const allocator = init.gpa; var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const io = init.io; defer _ = gpa.deinit();
const map = init.environ_map; const allocator = gpa.allocator();
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 init.minimal.args.iterateAllocator(init.arena.allocator()); var args = try std.process.argsWithAllocator(allocator);
defer args.deinit();
var stdout_buf: [4096]u8 = undefined; var stdout_buf: [4096]u8 = undefined;
const stdout_raw = std.Io.File.stdout().writer(io, &stdout_buf); const stdout_raw = std.fs.File.stdout().writer(&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;
@ -109,7 +111,10 @@ pub fn main(init: std.process.Init) anyerror!void {
} }
std.log.info("Start\n", .{}); std.log.info("Start\n", .{});
const client_options = aws.ClientOptions{ .proxy = proxy, .io = io, .map = map }; var threaded: std.Io.Threaded = .init(allocator);
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",
@ -371,7 +376,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.trimEnd(u8, split_iterator.first(), "/"); const host_str = std.mem.trimRight(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 = .empty, .attributes = AttributeList{},
.children = .empty, .children = ContentList{},
.allocator = alloc, .allocator = alloc,
}; };
} }

View file

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