Skip to content

Improve summarize() to show nested validation errors #1337

@nktkas

Description

@nktkas

Problem:
v.summarize() only shows the top-level error message without revealing the actual validation failures inside.

Example:

import * as v from "valibot";

const schema = v.union([
  v.object({ type: v.literal("admin"), role: v.string() }),
  v.object({ type: v.literal("user"), age: v.number() }),
]);
const data = { type: "unknown", role: 123, age: "invalid" };

const result = v.safeParse(schema, data);
if (!result.success) {
  console.log(v.summarize(result.issues));
}

Current output:

× Invalid type: Expected Object but received Object

Suggested fix:

import * as v from "valibot";

/**
 * Summarize the error messages of issues in a pretty-printable multi-line string.
 *
 * @param issues The list of issues.
 *
 * @returns A summary of the issues.
 *
 * @beta
 */
// @__NO_SIDE_EFFECTS__
export function summarize(
  issues: [v.BaseIssue<unknown>, ...v.BaseIssue<unknown>[]],
): string {
  // Create variable to store summary
  let summary = "";

  /** Recursively process issues with indentation */
  function processIssues(
    issueList: v.BaseIssue<unknown>[],
    indent: string = "",
  ): void {
    for (const issue of issueList) {
      // Add newline if summary is not empty
      if (summary) {
        summary += "\n";
      }

      // Add message to summary with current indentation
      summary += `${indent}x ${issue.message}`;

      // Get dot path from issue
      const dotPath = v.getDotPath(issue);

      // If dot path is available, add it to summary
      if (dotPath) {
        summary += `\n${indent}  → at ${dotPath}`;
      }

      // Process nested issues recursively with increased indentation
      if (issue.issues && issue.issues.length > 0) {
        processIssues(issue.issues, indent + "  ");
      }
    }
  }

  // Process all issues
  processIssues(issues);

  // Return summary
  return summary;
}

New output:

x Invalid type: Expected Object but received Object
  x Invalid type: Expected "admin" but received "unknown"
    → at type
  x Invalid type: Expected string but received 123
    → at role
  x Invalid type: Expected "user" but received "unknown"
    → at type
  x Invalid type: Expected number but received "invalid"
    → at age

P.S.: suggested fix is just an example, most likely there are edge cases that it doesn't handle well

Metadata

Metadata

Assignees

Labels

enhancementNew feature or requestquestionFurther information is requested

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions