160 lines · 4.6 KB · TypeScript
RawDownload
import * as Cause from "effect/Cause";import * as Effect from "effect/Effect";import * as Exit from "effect/Exit";import * as Option from "effect/Option";import * as Result from "effect/Result";import * as Schema from "effect/Schema";import * as SchemaGetter from "effect/SchemaGetter";import * as SchemaIssue from "effect/SchemaIssue";import * as SchemaTransformation from "effect/SchemaTransformation"; export const decodeJsonResult = <S extends Schema.Codec<unknown, unknown, never, never>>(  schema: S,) => {  const decode = Schema.decodeExit(Schema.fromJsonString(schema));  return (input: string) => {    const result = decode(input);    if (Exit.isFailure(result)) {      return Result.fail(result.cause);    }    return Result.succeed(result.value);  };}; export const decodeUnknownJsonResult = <S extends Schema.Codec<unknown, unknown, never, never>>(  schema: S,) => {  const decode = Schema.decodeUnknownExit(Schema.fromJsonString(schema));  return (input: unknown) => {    const result = decode(input);    if (Exit.isFailure(result)) {      return Result.fail(result.cause);    }    return Result.succeed(result.value);  };}; export const formatSchemaError = (cause: Cause.Cause<Schema.SchemaError>) => {  const squashed = Cause.squash(cause);  return Schema.isSchemaError(squashed)    ? SchemaIssue.makeFormatterDefault()(squashed.issue)    : Cause.pretty(cause);}; /** * A `Getter` that parses a lenient JSON string (tolerating trailing commas * and JS-style comments) into an unknown value. * * Mirrors `SchemaGetter.parseJson()` but uses `parseLenientJson` instead * of `JSON.parse`. */const parseLenientJsonGetter = SchemaGetter.onSome((input: string) =>  Effect.try({    try: () => {      // Strip single-line comments — alternation preserves quoted strings.      let stripped = input.replace(        /("(?:[^"\\]|\\.)*")|\/\/[^\n]*/g,        (match, stringLiteral: string | undefined) => (stringLiteral ? match : ""),      );       // Strip multi-line comments.      stripped = stripped.replace(        /("(?:[^"\\]|\\.)*")|\/\*[\s\S]*?\*\//g,        (match, stringLiteral: string | undefined) => (stringLiteral ? match : ""),      );       // Strip trailing commas before `}` or `]`.      stripped = stripped.replace(/,(\s*[}\]])/g, "$1");       return Option.some(JSON.parse(stripped));    },    catch: (e) => new SchemaIssue.InvalidValue(Option.some(input), { message: String(e) }),  }),); /** * Schema transformation: lenient JSONC string ↔ unknown. * * Same API as `SchemaTransformation.fromJsonString`, but the decode side * strips trailing commas and JS-style comments before parsing. * Encoding produces strict JSON via `JSON.stringify`. */export const fromLenientJsonString = new SchemaTransformation.Transformation(  parseLenientJsonGetter,  SchemaGetter.stringifyJson(),); export const prettyJsonString = SchemaGetter.parseJson<string>().compose(  SchemaGetter.stringifyJson({ space: 2 }),); /** * Build a schema that decodes a lenient JSON string into `A`. * * Drop-in replacement for `Schema.fromJsonString(schema)` that tolerates * trailing commas and comments in the input. */export const fromLenientJson = <S extends Schema.Top>(schema: S) =>  Schema.String.pipe(Schema.decodeTo(schema, fromLenientJsonString)); export function extractJsonObject(raw: string): string {  const trimmed = raw.trim();  if (trimmed.length === 0) {    return trimmed;  }   const start = trimmed.indexOf("{");  if (start < 0) {    return trimmed;  }   let depth = 0;  let inString = false;  let escaping = false;  for (let index = start; index < trimmed.length; index += 1) {    const char = trimmed[index];    if (inString) {      if (escaping) {        escaping = false;      } else if (char === "\\") {        escaping = true;      } else if (char === '"') {        inString = false;      }      continue;    }     if (char === '"') {      inString = true;      continue;    }     if (char === "{") {      depth += 1;      continue;    }     if (char === "}") {      depth -= 1;      if (depth === 0) {        return trimmed.slice(start, index + 1);      }    }  }   return trimmed.slice(start);} /** * Build a JSON string schema that encodes with stable 2-space formatting. * * Decode behavior matches `Schema.fromJsonString(schema)`. Encode behavior * keeps the transformation schema-based while preserving human-readable JSON. */export const fromJsonStringPretty = <S extends Schema.Top>(schema: S) =>  Schema.fromJsonString(schema).pipe(    Schema.encode({      decode: prettyJsonString,      encode: prettyJsonString,    }),  );