const std = @import("std"); const model_types = @import("model.zig"); const diag = @import("diag.zig"); const project = @import("project.zig"); const text = @import("text.zig"); const findObligation = model_types.findObligation; const findRequirement = model_types.findRequirement; const findTestBinding = model_types.findTestBinding; const findTestSpec = model_types.findTestSpec; /// Answer a generic relationship query. /// /// Product-domain queries are dispatched by the profile consumer before this /// function is reached. pub fn runQuery( model: *const model_types.Graph, args: []const []const u8, writer: *std.Io.Writer, ) !void { if (args.len < 2 or args.len > 3) return error.InvalidQuery; const json = args.len == 3 and std.mem.eql(u8, args[2], "--json"); if (args.len == 3 and !json) return error.InvalidQuery; if (std.mem.eql(u8, args[0], "requirement")) { return queryRequirement(model, args[1], json, writer); } if (std.mem.eql(u8, args[0], "concept")) { return queryConcept(model, args[1], json, writer); } if (std.mem.eql(u8, args[0], "document")) { return queryDocument(model, args[1], json, writer); } if (std.mem.eql(u8, args[0], "owner")) { return queryOwner(model, args[1], json, writer); } if (std.mem.eql(u8, args[0], "test-spec")) { return queryTestSpec(model, args[1], json, writer); } if (std.mem.eql(u8, args[0], "obligation")) { return queryObligation(model, args[1], json, writer); } if (std.mem.eql(u8, args[0], "implementation")) { return queryImplementation(model, args[1], json, writer); } return error.UnknownQuery; } pub fn queryRequirement( model: *const model_types.Graph, id: []const u8, json: bool, writer: *std.Io.Writer, ) !void { for (model.groups) |group| { for (group.requirements) |requirement| { if (!std.mem.eql(u8, requirement.id, id)) continue; if (json) { try writer.print("{f}\n", .{std.json.fmt(requirement, .{})}); } else { try writer.print("{s}@{d}: {s}\n", .{ requirement.id, requirement.revision, requirement.label, }); try writer.print("owner: {s}\n", .{requirement.owner}); try writer.writeAll("suites: "); try project.renderSuites(requirement.suites, writer); try writer.print( "\nlimitation: {s}\n", .{@tagName(requirement.limitation_status)}, ); } return; } } return error.RequirementNotFound; } pub fn queryConcept( model: *const model_types.Graph, key: []const u8, json: bool, writer: *std.Io.Writer, ) !void { for (model.concepts) |concept| { const matches = std.mem.eql(u8, concept.id, key) or std.ascii.eqlIgnoreCase(concept.term, key); if (!matches) continue; if (json) { try writer.print("{f}\n", .{std.json.fmt(concept, .{})}); } else { try writer.print("{s}: {s}\nowner: {s}\ndefinition: ", .{ concept.id, concept.term, concept.owner, }); try text.writeCollapsedWhitespace(writer, concept.definition); try writer.writeAll("\ninvariants:\n"); for (concept.invariants) |invariant| { try writer.writeAll("- "); try text.writeCollapsedWhitespace(writer, invariant); try writer.writeByte('\n'); } } return; } return error.ConceptNotFound; } pub fn queryDocument( model: *const model_types.Graph, name: []const u8, json: bool, writer: *std.Io.Writer, ) !void { for (model.documents) |document| { if (!std.mem.eql(u8, document.name, name)) continue; if (json) { try writer.print("{f}\n", .{std.json.fmt(document, .{})}); } else { try writer.print("{s}: {s}\n{s}\n", .{ document.name, document.location, document.description, }); } return; } return error.DocumentNotFound; } pub fn queryOwner( model: *const model_types.Graph, owner: []const u8, json: bool, writer: *std.Io.Writer, ) !void { var match_count: u32 = 0; if (json) try writer.writeByte('['); for (model.groups) |group| { for (group.requirements) |requirement| { if (!std.mem.eql(u8, requirement.owner, owner)) continue; if (json) { if (match_count > 0) try writer.writeByte(','); try writer.print("{f}", .{std.json.fmt(requirement.id, .{})}); } else { try writer.print("{s}@{d}: {s}\n", .{ requirement.id, requirement.revision, requirement.label, }); } match_count += 1; } } if (json) try writer.writeAll("]\n"); if (match_count == 0) return error.OwnerNotFound; } pub fn queryTestSpec( model: *const model_types.Graph, id: []const u8, json: bool, writer: *std.Io.Writer, ) !void { const test_spec = findTestSpec(model.test_specs, id) orelse return error.TestSpecNotFound; if (json) { try writer.print("{f}\n", .{std.json.fmt(test_spec, .{})}); return; } try writer.print("{s}@{d}\nsuite: {s}\nfixture: {s}@{d}\npath: {s}\n", .{ test_spec.id, test_spec.revision, @tagName(test_spec.suite), test_spec.fixture.id, test_spec.fixture.revision, test_spec.fixture.path, }); if (findTestBinding(model.test_bindings, test_spec.id)) |binding| { try writer.print("state: bound\nsource: {s}\n", .{binding.source}); } else { try writer.writeAll("state: specified\n"); } try writer.writeAll("claims:\n"); for (test_spec.claims) |claim| { try writer.print("- {s} ({s})\n", .{ claim.oracle, @tagName(claim.kind) }); for (claim.obligations) |obligation| { try writer.print(" - {s}@{d}\n", .{ obligation.id, obligation.revision }); } } } pub fn queryObligation( model: *const model_types.Graph, id: []const u8, json: bool, writer: *std.Io.Writer, ) !void { const obligation = findObligation(model.verification, id) orelse return error.ObligationNotFound; if (json) return writer.print("{f}\n", .{std.json.fmt(obligation, .{})}); try writer.print("{s}@{d}: {s}\nkind: {s}\nrequirement: {s}@{d}\nsuites:", .{ obligation.id, obligation.revision, obligation.label, @tagName(obligation.kind), obligation.requirement.id, obligation.requirement.revision, }); for (obligation.suites) |suite| try writer.print(" {s}", .{@tagName(suite)}); try writer.writeByte('\n'); } pub fn queryImplementation( model: *const model_types.Graph, id: []const u8, json: bool, writer: *std.Io.Writer, ) !void { for (model.implementations) |unit| { if (!std.mem.eql(u8, unit.id, id)) continue; if (json) return writer.print("{f}\n", .{std.json.fmt(unit, .{})}); try writer.print("{s}@{d}\nsources:\n", .{ unit.id, unit.revision }); for (unit.sources) |source| try writer.print("- {s}\n", .{source.path}); try writer.writeAll("realizes:\n"); for (unit.realizes) |reference| { try writer.print("- {s}@{d}\n", .{ reference.id, reference.revision }); } return; } return error.ImplementationNotFound; } pub fn runFingerprint( allocator: std.mem.Allocator, io: std.Io, path: []const u8, writer: *std.Io.Writer, stderr: *std.Io.Writer, ) !void { try diag.validateRepositoryPath(io, path, stderr); const source = try std.Io.Dir.cwd().readFileAlloc( io, path, allocator, .limited(model_types.file_size_max), ); defer allocator.free(source); var digest: [std.crypto.hash.sha2.Sha256.digest_length]u8 = undefined; std.crypto.hash.sha2.Sha256.hash(source, &digest, .{}); try writer.print("{x} {s}\n", .{ &digest, path }); } pub fn runImpact( model: *const model_types.Graph, target: []const u8, json: bool, writer: *std.Io.Writer, ) !void { if (json) return runImpactJson(model, target, writer); var matched = false; for (model.groups) |group| { for (group.requirements) |requirement| { if (!std.mem.eql(u8, requirement.id, target)) continue; matched = true; try writer.print("requirement {s}@{d}\n owner {s}\n", .{ requirement.id, requirement.revision, requirement.owner, }); } } for (model.verification) |obligation| { if (std.mem.eql(u8, obligation.id, target) or std.mem.eql(u8, obligation.requirement.id, target)) { matched = true; try writer.print("obligation {s}@{d}\n requirement {s}@{d}\n", .{ obligation.id, obligation.revision, obligation.requirement.id, obligation.requirement.revision, }); } } for (model.test_specs) |test_spec| { if (testSpecMatchesImpact(model, &test_spec, target)) { matched = true; try writer.print("test {s}@{d}\n fixture {s}@{d}\n path {s}\n", .{ test_spec.id, test_spec.revision, test_spec.fixture.id, test_spec.fixture.revision, test_spec.fixture.path, }); } } for (model.implementations) |unit| { if (implementationMatchesImpact(model, &unit, target)) { matched = true; try writer.print("implementation {s}@{d}\n", .{ unit.id, unit.revision }); for (unit.sources) |source| try writer.print(" source {s}\n", .{source.path}); } } if (!matched) return error.ImpactTargetNotFound; } fn runImpactJson( model: *const model_types.Graph, target: []const u8, writer: *std.Io.Writer, ) !void { var matched = false; try writer.print("{{\"target\":{f},\"requirements\":[", .{std.json.fmt(target, .{})}); var count: u32 = 0; for (model.groups) |group| { for (group.requirements) |requirement| { if (!std.mem.eql(u8, requirement.id, target)) continue; if (count > 0) try writer.writeByte(','); try writer.print("{f}", .{std.json.fmt(requirement, .{})}); count += 1; matched = true; } } try writer.writeAll("],\"obligations\":["); count = 0; for (model.verification) |obligation| { if (!std.mem.eql(u8, obligation.id, target) and !std.mem.eql(u8, obligation.requirement.id, target)) continue; if (count > 0) try writer.writeByte(','); try writer.print("{f}", .{std.json.fmt(obligation, .{})}); count += 1; matched = true; } try writer.writeAll("],\"tests\":["); count = 0; for (model.test_specs) |test_spec| { if (!testSpecMatchesImpact(model, &test_spec, target)) continue; if (count > 0) try writer.writeByte(','); try writer.print("{f}", .{std.json.fmt(test_spec, .{})}); count += 1; matched = true; } try writer.writeAll("],\"implementations\":["); count = 0; for (model.implementations) |unit| { if (!implementationMatchesImpact(model, &unit, target)) continue; if (count > 0) try writer.writeByte(','); try writer.print("{f}", .{std.json.fmt(unit, .{})}); count += 1; matched = true; } try writer.writeAll("]}\n"); if (!matched) return error.ImpactTargetNotFound; } pub fn testSpecMatchesImpact( model: *const model_types.Graph, test_spec: *const model_types.TestSpec, target: []const u8, ) bool { if (std.mem.eql(u8, test_spec.id, target) or std.mem.eql(u8, test_spec.fixture.id, target) or std.mem.eql(u8, test_spec.fixture.path, target)) return true; for (model.test_bindings) |binding| { if (std.mem.eql(u8, binding.test_spec.id, test_spec.id) and std.mem.eql(u8, binding.source, target)) return true; } for (test_spec.claims) |claim| { if (std.mem.eql(u8, claim.oracle, target)) return true; for (claim.obligations) |reference| { if (std.mem.eql(u8, reference.id, target)) return true; const obligation = findObligation(model.verification, reference.id) orelse continue; if (std.mem.eql(u8, obligation.requirement.id, target)) return true; } } return false; } pub fn implementationMatchesImpact( model: *const model_types.Graph, unit: *const model_types.ImplementationUnit, target: []const u8, ) bool { if (std.mem.eql(u8, unit.id, target)) return true; for (unit.sources) |source| { if (std.mem.eql(u8, source.path, target)) return true; } for (unit.realizes) |reference| { if (std.mem.eql(u8, reference.id, target)) return true; const obligation = findObligation(model.verification, reference.id) orelse continue; if (std.mem.eql(u8, obligation.requirement.id, target)) return true; } return false; } test "missing queries fail explicitly" { const model: model_types.Graph = .{ .documents = &.{}, .concepts = &.{}, .groups = &.{}, .test_specs = &.{}, .test_bindings = &.{}, .verification = &.{}, .implementations = &.{}, }; var output = std.Io.Writer.Allocating.init(std.testing.allocator); defer output.deinit(); try std.testing.expectError( error.RequirementNotFound, queryRequirement(&model, "REQ-MISSING-01", false, &output.writer), ); try std.testing.expectError( error.ConceptNotFound, queryConcept(&model, "missing", false, &output.writer), ); try std.testing.expectError( error.DocumentNotFound, queryDocument(&model, "missing", false, &output.writer), ); try std.testing.expectError( error.TestSpecNotFound, queryTestSpec(&model, "missing", false, &output.writer), ); }