Skip to content

Add YAML verification to Smoke Tests#51

Merged
christianhelle merged 13 commits into
mainfrom
yaml-smoke-tests
May 1, 2026
Merged

Add YAML verification to Smoke Tests#51
christianhelle merged 13 commits into
mainfrom
yaml-smoke-tests

Conversation

@christianhelle

Copy link
Copy Markdown
Owner
  • Add YAML OpenAPI parsing
  • Preserve YAML scalar quoting
  • Differentiate unsupported OpenAPI versions
  • Remove stale YAML roadmap item
  • Fix vendored zig-yaml README snippets
  • Tighten vendored zig-yaml parser coverage
  • test: cover YAML smoke sweep
  • docs: align YAML smoke coverage
  • build: add YAML smoke artifacts
  • docs: clarify YAML smoke scope
  • chore: record YAML smoke decisions
  • chore: update squad session records
  • chore(squad): record yaml smoke closeout
  • chore: clean up squad state

Copilot AI review requested due to automatic review settings May 1, 2026 12:14
@coderabbitai

coderabbitai Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@christianhelle has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 16 minutes and 43 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8d706ea1-d547-44f3-a191-5694ded70e73

📥 Commits

Reviewing files that changed from the base of the PR and between a7b7677 and 2a05bfa.

⛔ Files ignored due to path filters (5)
  • generated/compile_generated.zig is excluded by !**/generated/**
  • generated/generated_v2_yaml.zig is excluded by !**/generated/**
  • generated/generated_v31_yaml.zig is excluded by !**/generated/**
  • generated/generated_v3_yaml.zig is excluded by !**/generated/**
  • generated/main.zig is excluded by !**/generated/**
📒 Files selected for processing (24)
  • .squad/agents/fenster/history.md
  • .squad/agents/juno/history.md
  • .squad/agents/lando/history.md
  • .squad/agents/scribe/history.md
  • .squad/agents/starkiller/history-archive.md
  • .squad/agents/starkiller/history.md
  • .squad/decisions.md
  • .squad/identity/now.md
  • .squad/log/2026-05-01T09-50-14Z-yaml-smoke-closeout.md
  • .squad/orchestration-log/2026-05-01T09-50-14Z-juno.md
  • .squad/orchestration-log/2026-05-01T09-50-14Z-lando.md
  • .squad/orchestration-log/2026-05-01T09-50-14Z-starkiller.md
  • .squad/skills/project-conventions/SKILL.md
  • .squad/skills/smoke-harness-scope/SKILL.md
  • README.md
  • build.zig
  • src/generator.zig
  • src/tests.zig
  • src/tests/yaml_loader_tests.zig
  • src/yaml_loader.zig
  • test/smoke-tests.ps1
  • vendor/zig-yaml/README.md
  • vendor/zig-yaml/src/Parser.zig
  • vendor/zig-yaml/test/test.zig
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch yaml-smoke-tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 16 minutes and 43 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds YAML input support and YAML verification to openapi2zig’s smoke testing pipeline by vendoring zig-yaml, introducing YAML→JSON normalization, and updating the curated generated harness + documentation to reflect the expanded format coverage.

Changes:

  • Vendor zig-yaml and wire it into the build so the CLI can accept .yaml/.yml by converting YAML→JSON before OpenAPI/Swagger version detection and generation.
  • Expand the broad smoke sweep (test/smoke-tests.ps1) to discover JSON+YAML fixtures and avoid JSON/YAML sibling output collisions via __<format>__ filenames.
  • Extend curated generation artifacts and harness imports to include YAML-generated clients (v2/v3/v3.1), plus update README + squad records to document scope/decisions.

Reviewed changes

Copilot reviewed 23 out of 45 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
vendor/zig-yaml/test/test.zig Adds vendor library tests for YAML parsing/stringify + typed decoding.
vendor/zig-yaml/test/spec.zig Adds build-step generator for YAML spec test suite runner.
vendor/zig-yaml/test/single_lib.tbd Adds test fixture for directive + mapping/list parsing.
vendor/zig-yaml/test/simple.yaml Adds basic YAML fixture for typed parsing.
vendor/zig-yaml/test/multi_lib.tbd Adds multi-document fixture coverage for TBD parsing.
vendor/zig-yaml/src/stringify.zig Adds top-level stringify helper that encodes Zig values to YAML AST then writes YAML.
vendor/zig-yaml/src/lib.zig Exposes vendor library modules and re-exports stringify.
vendor/zig-yaml/src/Yaml.zig Implements YAML document load/parse/stringify and YAML AST/value encoding/decoding.
vendor/zig-yaml/src/Tree.zig Defines the YAML parse tree storage format and helpers (raw strings, directives, extras).
vendor/zig-yaml/src/Tokenizer.zig Implements YAML tokenization with basic YAML primitives and tests.
vendor/zig-yaml/src/Parser.zig Implements YAML parsing into Tree with error bundling and node encoding.
vendor/zig-yaml/build.zig.zon Declares vendored package metadata (Zig min version, included paths).
vendor/zig-yaml/build.zig Adds vendor module + vendor test step.
vendor/zig-yaml/README.md Updates vendor README, usage examples, and spec-test instructions.
vendor/zig-yaml/LICENSE Adds vendor license file.
test/smoke-tests.ps1 Expands smoke discovery to include .json/.yaml/.yml, adds format counts, and collision-safe output naming.
src/yaml_loader.zig Adds YAML→JSON normalization and conversion logic used by the CLI/library.
src/tests/yaml_loader_tests.zig Adds unit tests for YAML→JSON conversion + version detection + key/scalar edge cases.
src/tests.zig Registers YAML loader tests and generator tests in the test aggregator.
src/lib.zig Exposes yamlToJson and YAML-based parsing/version-detection helpers as part of the library API.
src/generator.zig Enables YAML inputs in generation path and distinguishes unsupported OpenAPI versions with a dedicated error.
generated/main.zig Extends runtime harness to import/init YAML-generated clients.
generated/generated_v31_yaml.zig Adds a checked-in curated YAML-generated v3.1 client artifact.
generated/compile_generated.zig Extends compile-only harness to reference YAML-generated curated artifacts.
build.zig.zon Adds the vendored YAML dependency and includes openapi/ + vendor/zig-yaml/ in packaged paths.
build.zig Wires the YAML dependency into all root modules and adds curated YAML generation steps for v2/v3/v3.1.
README.md Updates docs to state JSON+YAML coverage, describes smoke-sweep behavior, and documents curated YAML artifacts.
.squad/skills/smoke-harness-scope/SKILL.md Records conventions for keeping broad sweep vs curated harness responsibilities separate.
.squad/skills/project-conventions/SKILL.md Adds documentation convention about describing the two smoke layers distinctly.
.squad/orchestration-log/2026-05-01T09-50-14Z-starkiller.md Records orchestration approval log entry.
.squad/orchestration-log/2026-05-01T09-50-14Z-lando.md Records orchestration approval log entry.
.squad/orchestration-log/2026-05-01T09-50-14Z-juno.md Records orchestration approval log entry.
.squad/log/2026-05-01T09-50-14Z-yaml-smoke-closeout.md Records closeout actions for YAML smoke coordination.
.squad/identity/now.md Updates focus area/status to YAML smoke coverage work.
.squad/decisions.md Records decisions around YAML smoke scope, acceptance, and documentation requirements.
.squad/agents/starkiller/history.md Updates agent history with YAML smoke notes and archival summary.
.squad/agents/starkiller/history-archive.md Adds archived historical record.
.squad/agents/scribe/history.md Records scribe closeout actions.
.squad/agents/lando/history.md Records scope review + verdict details for YAML smoke work.
.squad/agents/juno/history.md Records docs alignment guidance + closeout.
.squad/agents/fenster/history.md Records YAML smoke implementation notes + denylist scope.

Comment thread src/yaml_loader.zig
Comment on lines +313 to +339
fn appendMarkedLine(allocator: std.mem.Allocator, out: *std.ArrayList(u8), line: []const u8) !void {
const colon_index = std.mem.indexOfScalar(u8, line, ':') orelse {
try out.appendSlice(allocator, line);
return;
};

var value_start = colon_index + 1;
while (value_start < line.len and (line[value_start] == ' ' or line[value_start] == '\t')) : (value_start += 1) {}

if (value_start >= line.len) {
try out.appendSlice(allocator, line);
return;
}

const comment_index = findInlineCommentStart(line[value_start..]) orelse line[value_start..].len;
const scalar_end = value_start + comment_index;
const scalar = std.mem.trimEnd(u8, line[value_start..scalar_end], " \t\r");
if (!shouldMarkOriginallyUnquotedScalar(scalar)) {
try out.appendSlice(allocator, line);
return;
}

try out.appendSlice(allocator, line[0..value_start]);
try out.appendSlice(allocator, unquoted_scalar_placeholder);
try out.appendSlice(allocator, line[value_start..]);
}

Copilot AI May 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

markOriginallyUnquotedScalars only marks scalars that appear on key: value lines. Any scalars in sequences (e.g. - 10, enum: [1, 2], multi-line - blocks) will never be marked as originally-unquoted, so writeJsonScalar will stringify them as JSON strings rather than numbers/bools/null. This can change OpenAPI semantics (notably schema.enum values) when converting YAML→JSON. Consider extending the marking pass to also handle sequence items (lines starting with - after indentation) and flow-sequence elements, or move the “quoted vs unquoted” decision into token-level parsing rather than a colon-line heuristic.

Suggested change
fn appendMarkedLine(allocator: std.mem.Allocator, out: *std.ArrayList(u8), line: []const u8) !void {
const colon_index = std.mem.indexOfScalar(u8, line, ':') orelse {
try out.appendSlice(allocator, line);
return;
};
var value_start = colon_index + 1;
while (value_start < line.len and (line[value_start] == ' ' or line[value_start] == '\t')) : (value_start += 1) {}
if (value_start >= line.len) {
try out.appendSlice(allocator, line);
return;
}
const comment_index = findInlineCommentStart(line[value_start..]) orelse line[value_start..].len;
const scalar_end = value_start + comment_index;
const scalar = std.mem.trimEnd(u8, line[value_start..scalar_end], " \t\r");
if (!shouldMarkOriginallyUnquotedScalar(scalar)) {
try out.appendSlice(allocator, line);
return;
}
try out.appendSlice(allocator, line[0..value_start]);
try out.appendSlice(allocator, unquoted_scalar_placeholder);
try out.appendSlice(allocator, line[value_start..]);
}
fn findTopLevelColon(line: []const u8) ?usize {
var in_single_quote = false;
var in_double_quote = false;
var escape = false;
var bracket_depth: usize = 0;
var brace_depth: usize = 0;
for (line, 0..) |char, i| {
if (in_double_quote) {
if (escape) {
escape = false;
continue;
}
switch (char) {
'\\' => escape = true,
'"' => in_double_quote = false,
else => {},
}
continue;
}
if (in_single_quote) {
if (char == '\'') in_single_quote = false;
continue;
}
switch (char) {
'\'' => in_single_quote = true,
'"' => in_double_quote = true,
'[' => bracket_depth += 1,
']' => if (bracket_depth > 0) bracket_depth -= 1,
'{' => brace_depth += 1,
'}' => if (brace_depth > 0) brace_depth -= 1,
':' => if (bracket_depth == 0 and brace_depth == 0) return i,
else => {},
}
}
return null;
}
fn findFlowSequenceClose(region: []const u8, open_index: usize) ?usize {
var in_single_quote = false;
var in_double_quote = false;
var escape = false;
var bracket_depth: usize = 0;
for (region[open_index..], open_index..) |char, i| {
if (in_double_quote) {
if (escape) {
escape = false;
continue;
}
switch (char) {
'\\' => escape = true,
'"' => in_double_quote = false,
else => {},
}
continue;
}
if (in_single_quote) {
if (char == '\'') in_single_quote = false;
continue;
}
switch (char) {
'\'' => in_single_quote = true,
'"' => in_double_quote = true,
'[' => bracket_depth += 1,
']' => {
if (bracket_depth == 0) return null;
bracket_depth -= 1;
if (bracket_depth == 0) return i;
},
else => {},
}
}
return null;
}
fn appendMarkedFlowSequenceElement(allocator: std.mem.Allocator, out: *std.ArrayList(u8), element: []const u8) !void {
const trimmed = std.mem.trim(u8, element, " \t\r");
if (trimmed.len == 0) {
try out.appendSlice(allocator, element);
return;
}
const leading_len = std.mem.indexOf(u8, element, trimmed) orelse 0;
const trailing_start = leading_len + trimmed.len;
if (trimmed[0] == '\'' or trimmed[0] == '"' or trimmed[0] == '[' or trimmed[0] == '{' or findTopLevelColon(trimmed) != null or !shouldMarkOriginallyUnquotedScalar(trimmed)) {
try out.appendSlice(allocator, element);
return;
}
try out.appendSlice(allocator, element[0..leading_len]);
try out.appendSlice(allocator, unquoted_scalar_placeholder);
try out.appendSlice(allocator, element[leading_len..trailing_start]);
try out.appendSlice(allocator, element[trailing_start..]);
}
fn appendMarkedFlowSequence(allocator: std.mem.Allocator, out: *std.ArrayList(u8), region: []const u8) !void {
const open_index = std.mem.indexOfScalar(u8, region, '[') orelse {
try out.appendSlice(allocator, region);
return;
};
const close_index = findFlowSequenceClose(region, open_index) orelse {
try out.appendSlice(allocator, region);
return;
};
try out.appendSlice(allocator, region[0 .. open_index + 1]);
const inner = region[open_index + 1 .. close_index];
var in_single_quote = false;
var in_double_quote = false;
var escape = false;
var bracket_depth: usize = 0;
var brace_depth: usize = 0;
var element_start: usize = 0;
for (inner, 0..) |char, i| {
if (in_double_quote) {
if (escape) {
escape = false;
continue;
}
switch (char) {
'\\' => escape = true,
'"' => in_double_quote = false,
else => {},
}
continue;
}
if (in_single_quote) {
if (char == '\'') in_single_quote = false;
continue;
}
switch (char) {
'\'' => in_single_quote = true,
'"' => in_double_quote = true,
'[' => bracket_depth += 1,
']' => if (bracket_depth > 0) bracket_depth -= 1,
'{' => brace_depth += 1,
'}' => if (brace_depth > 0) brace_depth -= 1,
',' => if (bracket_depth == 0 and brace_depth == 0) {
try appendMarkedFlowSequenceElement(allocator, out, inner[element_start..i]);
try out.append(allocator, ',');
element_start = i + 1;
},
else => {},
}
}
try appendMarkedFlowSequenceElement(allocator, out, inner[element_start..]);
try out.append(allocator, ']');
try out.appendSlice(allocator, region[close_index + 1 ..]);
}
fn appendMarkedValueRegion(allocator: std.mem.Allocator, out: *std.ArrayList(u8), region: []const u8) !void {
const trimmed = std.mem.trim(u8, region, " \t\r");
if (trimmed.len == 0) {
try out.appendSlice(allocator, region);
return;
}
if (trimmed[0] == '[') {
try appendMarkedFlowSequence(allocator, out, region);
return;
}
if (trimmed[0] == '{' or trimmed[0] == '\'' or trimmed[0] == '"' or findTopLevelColon(trimmed) != null or !shouldMarkOriginallyUnquotedScalar(trimmed)) {
try out.appendSlice(allocator, region);
return;
}
const leading_len = std.mem.indexOf(u8, region, trimmed) orelse 0;
const trailing_start = leading_len + trimmed.len;
try out.appendSlice(allocator, region[0..leading_len]);
try out.appendSlice(allocator, unquoted_scalar_placeholder);
try out.appendSlice(allocator, region[leading_len..trailing_start]);
try out.appendSlice(allocator, region[trailing_start..]);
}
fn appendMarkedLine(allocator: std.mem.Allocator, out: *std.ArrayList(u8), line: []const u8) !void {
const comment_index = findInlineCommentStart(line) orelse line.len;
const content = line[0..comment_index];
const comment = line[comment_index..];
var index: usize = 0;
while (index < content.len and (content[index] == ' ' or content[index] == '\t')) : (index += 1) {}
if (index < content.len and content[index] == '-') {
const next_index = index + 1;
if (next_index == content.len or content[next_index] == ' ' or content[next_index] == '\t') {
var value_start = next_index;
while (value_start < content.len and (content[value_start] == ' ' or content[value_start] == '\t')) : (value_start += 1) {}
try out.appendSlice(allocator, content[0..value_start]);
try appendMarkedValueRegion(allocator, out, content[value_start..]);
try out.appendSlice(allocator, comment);
return;
}
}
const colon_index = findTopLevelColon(content) orelse {
try out.appendSlice(allocator, line);
return;
};
var value_start = colon_index + 1;
while (value_start < content.len and (content[value_start] == ' ' or content[value_start] == '\t')) : (value_start += 1) {}
try out.appendSlice(allocator, content[0..value_start]);
try appendMarkedValueRegion(allocator, out, content[value_start..]);
try out.appendSlice(allocator, comment);
}

Copilot uses AI. Check for mistakes.
christianhelle and others added 13 commits May 1, 2026 14:20
Keep quoted YAML scalars as strings by marking values that were originally unquoted before coercing them during YAML-to-JSON conversion. Also preserve folded block paragraph breaks, escape backslashes in synthetic double-quoted YAML, and add regression coverage for the affected cases.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a distinct UnsupportedOpenAPIVersion generator error so callers can tell version detection failures apart from invalid file extensions. Cover the behavior with a regression test wired into the main test module.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Drop the README planned-feature entry for YAML specification format support now that YAML input is implemented and documented elsewhere.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add language labels to the installation and build.zig examples and correct the raw-load snippet to operate on the loaded yaml value.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Accept escaped backslashes in vendored zig-yaml double-quoted parsing and strengthen the vendor test suite to compare booleans, UUIDs, symmetric optionals, and collection lengths.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Discover YAML specs in smoke-tests, keep output filenames extension-aware so JSON/YAML siblings do not collide, and denylist the currently known YAML parser gaps to keep the sweep deterministic.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Clarify that the broad smoke matrix covers JSON and YAML fixtures, including source-format-aware output names, while the checked-in sample generation section still documents the curated JSON-backed harness. Also record the team note and reusable doc pattern for future smoke-test updates.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Extend the quick generated-artifact smoke path to emit YAML-backed fixtures for the existing v2, v3, and v3.1 samples, and wire the generated harnesses to compile and initialize those modules alongside the JSON artifacts.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Align the README with the final smoke-test scope: broad smoke discovery covers JSON and YAML roots without requiring sibling fixtures, while the curated quick harness stays selective and keeps OpenAPI v3.2 JSON-only until a real YAML fixture exists.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Capture the YAML smoke-layer decisions, append the implementation learnings, and record the reusable smoke-harness pattern for format-aware outputs and mode-wide denylist entries.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- merge the YAML smoke decision inbox into decisions.md
- record orchestration and session logs for the 2026-05-01 closeout
- summarize Starkiller history and append cross-agent notes

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove processed squad inbox notes and refresh current squad identity.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@christianhelle christianhelle merged commit 37834ac into main May 1, 2026
21 checks passed
@christianhelle christianhelle deleted the yaml-smoke-tests branch May 1, 2026 12:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants