Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"lint": "oxlint",
"test": "vitest run",
"type-check": "tsc --noEmit",
"docs": "typedoc --excludeExternals"
"docs": "typedoc --excludeExternals src/index.d.ts"
},
"dependencies": {
"@fluent/bundle": "^0.19.1",
Expand Down
3 changes: 1 addition & 2 deletions src/error-handlers/contains.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import * as Schema from "@hyperjump/browser";
import * as Instance from "@hyperjump/json-schema/instance/experimental";

/**
* @import { ContainsRange } from "../localization.js"
* @import { ErrorHandler, ErrorObject } from "../index.d.ts"
* @import { ContainsRange, ErrorHandler, ErrorObject } from "../index.d.ts"
*/

/** @type ErrorHandler */
Expand Down
94 changes: 88 additions & 6 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,40 @@ import { AST, EvaluationPlugin } from "@hyperjump/json-schema/experimental";
import { JsonNode } from "@hyperjump/json-schema/instance/experimental";
import { Localization } from "./localization.js";

/**
* Converts standard JSON Schema validation output into human-oriented, localized
* messages. Schemas need to be registered with `@hyperjump/json-schema`'s
* `registerSchema` function. The default locale is `en-US`.
*
* @param errorOutput - The validation output in standard JSON Schema output format.
* @param schemaUri - The URI of the JSON Schema the data was validated against
* @param instance - The JSON data that was validated
* @param options - Options to configure the error handler (default locale is "en-US")
*/
export const jsonSchemaErrors: (
errorOutput: OutputFormat,
schemaUri: string,
instance: Json,
options?: Options
options?: JsonSchemaErrorsOptions
) => Promise<JsonSchemaErrors>;

export const setNormalizationHandler: (uri: string, handler: NormalizationHandler) => void;
/**
* Sets a normalization handler for a specific keyword URI. Normalization handlers
* process keyword values during schema validation to produce normalized output.
*/
export const setNormalizationHandler: (keywordUri: string, handler: NormalizationHandler) => void;

/**
* The standard JSON Schema output format. Supports the "basic", "detailed", and
* "verbose" formats.
*/
export type OutputFormat = OutputUnit & {
valid: boolean;
};

/**
* A single node of the JSON Schema output format.
*/
export type OutputUnit = {
valid?: boolean;
absoluteKeywordLocation?: string;
Expand All @@ -28,21 +49,51 @@ export type JsonObject = {
[property: string]: Json;
};

export type Options = {
language?: string;
export type JsonSchemaErrorsOptions = {
/**
* A locale identifier in the form of "{language}-{region}".
*
* @example "en-US"
*/
locale?: string;
};

/**
* An array of error objects representing validation failures.
*/
export type JsonSchemaErrors = ErrorObject[];

/**
* Represents a single validation error with message and schema location
* information.
*/
export type ErrorObject = {
message: string;
alternatives?: ErrorObject[][];
instanceLocation: string;
schemaLocations: string[];
};

/**
* Used to convert a specific keyword to the normalized format used by the error
* handlers.
*/
export type NormalizationHandler<KeywordValue = unknown, Context extends EvaluationContext = EvaluationContext> = {
/**
* For non-applicator keywords, this doesn't need to do anything. Just return void.
*
* For applicator keywords, it should call `evaluateSchema` on each subschema and
* return an array with each result.
*/
evaluate(value: KeywordValue, instance: JsonNode, context: Context): NormalizedOutput[] | void;

/**
* Simple applicators just apply subschemas and don't have any validation behavior
* of their own. For example, `allOf` and `properties` are simple applicators. They
* never fail. Only their subschema can fail. `anyOf` and `oneOf` are not simple
* applicators because they can fail independently of the validation result of
* their subschemas.
*/
simpleApplicator?: true;
};

Expand All @@ -58,20 +109,51 @@ export type ErrorIndex = {
};
};

/**
* The normalized keyword result keyed by keyword URI and keyword location. If the
* keyword is an applicator the values can be `false` or `NormalizedOutput[]`. If
* the value is not an applicator, the value is just a boolean.
*/
export type InstanceOutput = {
[keywordUri: string]: {
[keywordLocation: string]: boolean | NormalizedOutput[];
};
};

/**
* A map of an instance location to the normalized keyword result for that location.
*/
export type NormalizedOutput = {
[instanceLocation: string]: InstanceOutput;
};

/**
* Builds the normalized output format for a schema. It's used in normalization
* handlers to evaluate an applicator's subschemas.
*
* @param schemaLocation - A URI with a JSON Pointer fragment
* @param instance
* @param context
*/
export const evaluateSchema: (schemaLocation: string, instance: JsonNode, context: EvaluationContext) => NormalizedOutput;

export const addErrorHandler: (handler: ErrorHandler) => void;

/**
* A function that transforms normalized errors for one or more keywords into human
* readable messages.
*/
export type ErrorHandler = (normalizedErrors: InstanceOutput, instance: JsonNode, localization: Localization) => Promise<ErrorObject[]>;

export const evaluateSchema: (schemaLocation: string, instance: JsonNode, context: EvaluationContext) => NormalizedOutput;
/**
* Converts the normalized error format to human readable errors. It's used to
* build errors in applicator error handlers.
*/
export const getErrors: (normalizedErrors: NormalizedOutput, instance: JsonNode, localization: Localization) => Promise<ErrorObject[]>;

export type { Localization };

export const getErrors: (normalizedErrors: NormalizedOutput, rootInstance: JsonNode, language: Localization) => Promise<ErrorObject[]>;
export type ContainsRange = {
minContains?: number;
maxContains?: number;
};
6 changes: 3 additions & 3 deletions src/json-schema-errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@ import { Localization } from "./localization.js";
export const jsonSchemaErrors = async (errorOutput, schemaUri, instance, options = {}) => {
const normalizedErrors = await normalizedOutput(instance, errorOutput, schemaUri);
const rootInstance = Instance.fromJs(instance);
const localization = await Localization.forLocale(options.language ?? "en-US");
const localization = await Localization.forLocale(options.locale ?? "en-US");
return await getErrors(normalizedErrors, rootInstance, localization);
};

/** @type Record<string, API.NormalizationHandler> */
const normalizationHandlers = {};

/** @type API.setNormalizationHandler */
export const setNormalizationHandler = (uri, handler) => {
normalizationHandlers[uri] = handler;
export const setNormalizationHandler = (schemaUri, handler) => {
normalizationHandlers[schemaUri] = handler;
};

/** @type (instance: API.Json, errorOutput: API.OutputUnit, subjectUri: string) => Promise<API.NormalizedOutput> */
Expand Down
9 changes: 1 addition & 8 deletions src/localization.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,7 @@ import { FluentBundle, FluentResource } from "@fluent/bundle";

/**
* @import { FluentVariable} from "@fluent/bundle"
* @import { Json } from "./index.js"
*/

/**
* @typedef {{
* minContains?: number;
* maxContains?: number;
* }} ContainsRange
* @import { ContainsRange, Json } from "./index.d.ts"
*/

export class Localization {
Expand Down