diff --git a/src/parser.ts b/src/parser.ts index e36ccc2..5e3eb8f 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -14,10 +14,20 @@ export class ParseError extends Error { constructor( message: string, public position?: number, + public schema?: string, + public value?: string, ) { - super( - position !== undefined ? `${message} at position ${position}` : message, - ); + let fullMessage = message; + if (position !== undefined) { + fullMessage += ` at position ${position}`; + } + if (schema !== undefined) { + fullMessage += `. Schema: ${schema}`; + } + if (value !== undefined) { + fullMessage += `. Value: ${value}`; + } + super(fullMessage); this.name = "ParseError"; } } diff --git a/src/value-parser.ts b/src/value-parser.ts index efda476..6f8b070 100644 --- a/src/value-parser.ts +++ b/src/value-parser.ts @@ -7,13 +7,16 @@ import type { UnionSchema, } from "./types"; import { ParseError } from "./parser"; +import { schemaToTypeString } from "./type-utils"; class ValueParser { private input: string; + private schemaString: string; private pos: number = 0; - constructor(input: string) { + constructor(input: string, schemaString: string) { this.input = input; + this.schemaString = schemaString; } private peek(): string { @@ -71,6 +74,8 @@ class ValueParser { throw new ParseError( `Unknown schema type: ${(schema as { type: string }).type}`, this.pos, + this.schemaString, + this.input, ); } } @@ -109,7 +114,12 @@ class ValueParser { } const num = parseFloat(numStr); if (isNaN(num)) { - throw new ParseError("Invalid number", this.pos - numStr.length); + throw new ParseError( + "Invalid number", + this.pos - numStr.length, + this.schemaString, + this.input, + ); } return num; } @@ -121,10 +131,20 @@ class ValueParser { } const num = parseFloat(numStr); if (isNaN(num)) { - throw new ParseError("Invalid number", this.pos - numStr.length); + throw new ParseError( + "Invalid number", + this.pos - numStr.length, + this.schemaString, + this.input, + ); } if (!Number.isInteger(num)) { - throw new ParseError("Expected integer value", this.pos - numStr.length); + throw new ParseError( + "Expected integer value", + this.pos - numStr.length, + this.schemaString, + this.input, + ); } return num; } @@ -140,7 +160,12 @@ class ValueParser { if (this.consumeStr("false")) { return false; } - throw new ParseError("Expected true or false", this.pos); + throw new ParseError( + "Expected true or false", + this.pos, + this.schemaString, + this.input, + ); } private parseStringLiteralValue(schema: StringLiteralSchema): string { @@ -174,6 +199,8 @@ class ValueParser { throw new ParseError( `Invalid value '"${value}"'. Expected '"${schema.value}"'`, this.pos, + this.schemaString, + this.input, ); } @@ -183,7 +210,12 @@ class ValueParser { } } - throw new ParseError("Unterminated string literal", this.pos); + throw new ParseError( + "Unterminated string literal", + this.pos, + this.schemaString, + this.input, + ); } else { // 不带引号的字符串,像普通字符串一样解析 let value = ""; @@ -201,6 +233,8 @@ class ValueParser { throw new ParseError( `Invalid value '${value}'. Expected '${schema.value}'`, this.pos - value.length, + this.schemaString, + this.input, ); } @@ -227,6 +261,8 @@ class ValueParser { throw new ParseError( `Value does not match any union member. Tried ${schema.members.length} alternatives.`, this.pos, + this.schemaString, + this.input, ); } @@ -240,7 +276,12 @@ class ValueParser { this.consume(); hasOpenBracket = true; } else if (!allowOmitBrackets) { - throw new ParseError("Expected [", this.pos); + throw new ParseError( + "Expected [", + this.pos, + this.schemaString, + this.input, + ); } this.skipWhitespace(); @@ -272,7 +313,12 @@ class ValueParser { if (i < schema.elements.length - 1) { if (!this.consumeStr(";")) { - throw new ParseError("Expected ;", this.pos); + throw new ParseError( + "Expected ;", + this.pos, + this.schemaString, + this.input, + ); } } } @@ -281,7 +327,12 @@ class ValueParser { if (hasOpenBracket) { if (!this.consumeStr("]")) { - throw new ParseError("Expected ]", this.pos); + throw new ParseError( + "Expected ]", + this.pos, + this.schemaString, + this.input, + ); } } @@ -325,7 +376,12 @@ class ValueParser { } if (!hasOpenBracket && !allowOmitBrackets && !elementIsTupleOrArray) { - throw new ParseError("Expected [", this.pos); + throw new ParseError( + "Expected [", + this.pos, + this.schemaString, + this.input, + ); } this.skipWhitespace(); @@ -351,7 +407,12 @@ class ValueParser { if (hasOpenBracket) { if (!this.consumeStr("]")) { - throw new ParseError("Expected ]", this.pos); + throw new ParseError( + "Expected ]", + this.pos, + this.schemaString, + this.input, + ); } } @@ -405,7 +466,12 @@ class ValueParser { if (hasOpenBracket) { if (!this.consumeStr("]")) { - throw new ParseError("Expected ]", this.pos); + throw new ParseError( + "Expected ]", + this.pos, + this.schemaString, + this.input, + ); } } @@ -433,13 +499,23 @@ class ValueParser { } } -export function parseValue(schema: Schema, valueString: string): unknown { - const parser = new ValueParser(valueString.trim()); +export function parseValue( + schema: Schema, + valueString: string, + schemaString?: string, +): unknown { + const sStr = schemaString || schemaToTypeString(schema); + const parser = new ValueParser(valueString.trim(), sStr); const allowOmitBrackets = schema.type === "tuple" || schema.type === "array"; const value = parser.parseValue(schema, allowOmitBrackets); if (parser.getPosition() < parser.getInputLength()) { - throw new ParseError("Unexpected input after value", parser.getPosition()); + throw new ParseError( + "Unexpected input after value", + parser.getPosition(), + sStr, + valueString.trim(), + ); } return value;