{"version":3,"file":"index.mjs","names":[],"sources":["../src/sanitize-issues.ts","../src/index.ts"],"sourcesContent":["import type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { ValidationTargets } from 'hono'\n\nconst RESTRICTED_DATA_FIELDS = {\n  header: ['cookie'],\n}\n\n/**\n * Sanitizes validation issues by removing sensitive data fields from error messages.\n *\n * This function removes potentially sensitive information (like cookies) from validation\n * error messages before they are returned to the client. It handles different validation\n * library formats based on the vendor string.\n *\n * @param issues - Array of validation issues from Standard Schema validation\n * @param vendor - The validation library vendor identifier (e.g., 'arktype', 'valibot')\n * @param target - The validation target being processed ('header', 'json', etc.)\n * @returns Sanitized array of validation issues with sensitive data removed\n *\n * @example\n * ```ts\n * const issues = [{ message: 'Invalid header', data: { cookie: 'secret' } }]\n * const sanitized = sanitizeIssues(issues, 'arktype', 'header')\n * // Returns issues with cookie field removed from data\n * ```\n */\nexport function sanitizeIssues(\n  issues: readonly StandardSchemaV1.Issue[],\n  vendor: string,\n  target: keyof ValidationTargets\n): readonly StandardSchemaV1.Issue[] {\n  if (!(target in RESTRICTED_DATA_FIELDS)) {\n    return issues\n  }\n\n  const restrictedFields =\n    RESTRICTED_DATA_FIELDS[target as keyof typeof RESTRICTED_DATA_FIELDS] || []\n\n  if (vendor === 'arktype') {\n    return sanitizeArktypeIssues(issues, restrictedFields)\n  }\n\n  if (vendor === 'valibot') {\n    return sanitizeValibotIssues(issues, restrictedFields)\n  }\n\n  return issues\n}\n\nfunction sanitizeArktypeIssues(\n  issues: readonly StandardSchemaV1.Issue[],\n  restrictedFields: string[]\n): readonly StandardSchemaV1.Issue[] {\n  return issues.map((issue) => {\n    if (\n      issue &&\n      typeof issue === 'object' &&\n      'data' in issue &&\n      typeof issue.data === 'object' &&\n      issue.data !== null &&\n      !Array.isArray(issue.data)\n    ) {\n      const dataCopy = { ...(issue.data as Record<string, unknown>) }\n      for (const field of restrictedFields) {\n        delete dataCopy[field]\n      }\n      // Preserve prototype chain to maintain toJSON method\n      const sanitizedIssue = Object.create(Object.getPrototypeOf(issue))\n      Object.assign(sanitizedIssue, issue, { data: dataCopy })\n      return sanitizedIssue\n    }\n    return issue\n  }) as readonly StandardSchemaV1.Issue[]\n}\n\nfunction sanitizeValibotIssues(\n  issues: readonly StandardSchemaV1.Issue[],\n  restrictedFields: string[]\n): readonly StandardSchemaV1.Issue[] {\n  return issues.map((issue) => {\n    if (issue && typeof issue === 'object' && 'path' in issue && Array.isArray(issue.path)) {\n      for (const path of issue.path) {\n        if (\n          typeof path === 'object' &&\n          'input' in path &&\n          typeof path.input === 'object' &&\n          path.input !== null &&\n          !Array.isArray(path.input)\n        ) {\n          for (const field of restrictedFields) {\n            delete path.input[field]\n          }\n        }\n      }\n    }\n    return issue\n  }) as readonly StandardSchemaV1.Issue[]\n}\n","import type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { Context, Env, Input, TypedResponse, ValidationTargets } from 'hono'\nimport type { Handler } from 'hono/types'\nimport { validator } from 'hono/validator'\nimport type { InferInput } from 'hono/validator'\nimport { sanitizeIssues } from './sanitize-issues'\n\ntype HasUndefined<T> = undefined extends T ? true : false\n\nexport type FailedResponse<T> = Response &\n  TypedResponse<\n    {\n      readonly success: false\n      readonly error: readonly StandardSchemaV1.Issue[]\n      readonly data: T\n    },\n    400,\n    'json'\n  >\ntype MustBeResponse<T> =\n  T extends Promise<infer U>\n    ? Promise<MustBeResponse<U>>\n    : T extends Response | TypedResponse\n      ? T\n      : never\n\ntype Hook<\n  T,\n  E extends Env,\n  P extends string,\n  Target extends keyof ValidationTargets = keyof ValidationTargets,\n  R extends\n    void | Response | TypedResponse<unknown> | Promise<Response | TypedResponse<unknown> | void> =\n    void | Response | TypedResponse<unknown> | Promise<Response | TypedResponse<unknown> | void>,\n> = (\n  result: (\n    | { success: true; data: T }\n    | { success: false; error: readonly StandardSchemaV1.Issue[]; data: T }\n  ) & {\n    target: Target\n  },\n  c: Context<E, P>\n) => R\n\n/**\n * Validation middleware for libraries that support [Standard Schema](https://standardschema.dev/) specification.\n *\n * This middleware validates incoming request data against a provided schema\n * that conforms to the Standard Schema specification. It supports validation\n * of JSON bodies, headers, queries, forms, and other request targets.\n *\n * @param target - The request target to validate ('json', 'header', 'query', 'form', etc.)\n * @param schema - A schema object conforming to Standard Schema specification\n * @param hook - Optional hook function called with validation results for custom error handling\n * @returns A Hono middleware handler that validates requests and makes validated data available via `c.req.valid()`\n *\n * @example Basic JSON validation\n * ```ts\n * import { z } from 'zod'\n * import { sValidator } from '@hono/standard-validator'\n *\n * const schema = z.object({\n *   name: z.string(),\n *   age: z.number(),\n * })\n *\n * app.post('/author', sValidator('json', schema), (c) => {\n *   const data = c.req.valid('json')\n *   return c.json({\n *     success: true,\n *     message: `${data.name} is ${data.age}`,\n *   })\n * })\n * ```\n *\n * @example With custom error handling hook\n * ```ts\n * app.post(\n *   '/post',\n *   sValidator('json', schema, (result, c) => {\n *     if (!result.success) {\n *       return c.text('Invalid!', 400)\n *     }\n *   }),\n *   (c) => {\n *     // Handler code\n *   }\n * )\n * ```\n *\n * @example Header validation\n * ```ts\n * import { object, string } from 'valibot'\n *\n * const schema = object({\n *   'content-type': string(),\n *   'user-agent': string(),\n * })\n *\n * app.post('/author', sValidator('header', schema), (c) => {\n *   const headers = c.req.valid('header')\n *   // do something with headers\n * })\n * ```\n */\nconst sValidator = <\n  Schema extends StandardSchemaV1,\n  Target extends keyof ValidationTargets,\n  E extends Env,\n  P extends string,\n  In = StandardSchemaV1.InferInput<Schema>,\n  Out = StandardSchemaV1.InferOutput<Schema>,\n  I extends Input = {\n    in: HasUndefined<In> extends true\n      ? {\n          [K in Target]?: [In] extends [ValidationTargets[K]] ? In : InferInput<In, K>\n        }\n      : {\n          [K in Target]: [In] extends [ValidationTargets[K]] ? In : InferInput<In, K>\n        }\n    out: { [K in Target]: Out }\n  },\n  V extends I = I,\n  R extends\n    void | Response | TypedResponse<unknown> | Promise<Response | TypedResponse<unknown> | void> =\n    FailedResponse<ValidationTargets[Target]>,\n>(\n  target: Target,\n  schema: Schema,\n  hook?: Hook<StandardSchemaV1.InferInput<Schema>, E, P, Target, R>\n): Handler<E, P, V, MustBeResponse<R>> =>\n  // @ts-expect-error not typed well\n  validator(target, async (value, c) => {\n    const result = await schema['~standard'].validate(value)\n\n    if (hook) {\n      const hookResult = await hook(\n        result.issues\n          ? { data: value, error: result.issues, success: false, target }\n          : { data: value, success: true, target },\n        c\n      )\n      if (hookResult) {\n        if (hookResult instanceof Response) {\n          return hookResult\n        }\n\n        if (typeof hookResult === 'object' && 'response' in hookResult) {\n          return hookResult.response\n        }\n      }\n    }\n\n    if (result.issues) {\n      const processedIssues = sanitizeIssues(result.issues, schema['~standard'].vendor, target)\n\n      return c.json({ data: value, error: processedIssues, success: false }, 400)\n    }\n\n    return (result as { value: StandardSchemaV1.InferOutput<Schema> }).value\n  })\n\nexport type { Hook }\nexport { sValidator }\n\ninterface FlattenedErrorObject {\n  formErrors: string[]\n  fieldErrors: Record<string, string[]>\n}\n\n/**\n * Sorts validation errors by their paths.\n * @param issues An array of {@link StandardSchemaV1.Issue validation issues}.\n * @returns An object with sorted form and field errors.\n */\nexport const flattenErrors = (issues: readonly StandardSchemaV1.Issue[]): FlattenedErrorObject => {\n  const formErrors: string[] = []\n  const fieldErrors: Record<PropertyKey, string[]> = {}\n\n  for (const { path = [], message } of issues) {\n    const [issuePath] = path\n    const key = typeof issuePath === 'object' ? issuePath.key : issuePath\n\n    if (typeof key !== 'undefined' && !fieldErrors[key]) {\n      fieldErrors[key] = []\n    }\n\n    const errors = typeof key !== 'undefined' ? fieldErrors[key] : formErrors\n    errors?.push(message)\n  }\n\n  return { formErrors, fieldErrors }\n}\n"],"mappings":";;AAGA,MAAM,yBAAyB,EAC7B,QAAQ,CAAC,QAAQ,EACnB;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,eACd,QACA,QACA,QACmC;CACnC,IAAI,EAAE,UAAU,yBACd,OAAO;CAGT,MAAM,mBACJ,uBAAuB,WAAkD,CAAC;CAE5E,IAAI,WAAW,WACb,OAAO,sBAAsB,QAAQ,gBAAgB;CAGvD,IAAI,WAAW,WACb,OAAO,sBAAsB,QAAQ,gBAAgB;CAGvD,OAAO;AACT;AAEA,SAAS,sBACP,QACA,kBACmC;CACnC,OAAO,OAAO,KAAK,UAAU;EAC3B,IACE,SACA,OAAO,UAAU,YACjB,UAAU,SACV,OAAO,MAAM,SAAS,YACtB,MAAM,SAAS,QACf,CAAC,MAAM,QAAQ,MAAM,IAAI,GACzB;GACA,MAAM,WAAW,EAAE,GAAI,MAAM,KAAiC;GAC9D,KAAK,MAAM,SAAS,kBAClB,OAAO,SAAS;GAGlB,MAAM,iBAAiB,OAAO,OAAO,OAAO,eAAe,KAAK,CAAC;GACjE,OAAO,OAAO,gBAAgB,OAAO,EAAE,MAAM,SAAS,CAAC;GACvD,OAAO;EACT;EACA,OAAO;CACT,CAAC;AACH;AAEA,SAAS,sBACP,QACA,kBACmC;CACnC,OAAO,OAAO,KAAK,UAAU;EAC3B,IAAI,SAAS,OAAO,UAAU,YAAY,UAAU,SAAS,MAAM,QAAQ,MAAM,IAAI;QAC9E,MAAM,QAAQ,MAAM,MACvB,IACE,OAAO,SAAS,YAChB,WAAW,QACX,OAAO,KAAK,UAAU,YACtB,KAAK,UAAU,QACf,CAAC,MAAM,QAAQ,KAAK,KAAK,GAEzB,KAAK,MAAM,SAAS,kBAClB,OAAO,KAAK,MAAM;EAAA;EAK1B,OAAO;CACT,CAAC;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACQA,MAAM,cAsBJ,QACA,QACA,SAGA,UAAU,QAAQ,OAAO,OAAO,MAAM;CACpC,MAAM,SAAS,MAAM,OAAO,YAAY,CAAC,SAAS,KAAK;CAEvD,IAAI,MAAM;EACR,MAAM,aAAa,MAAM,KACvB,OAAO,SACH;GAAE,MAAM;GAAO,OAAO,OAAO;GAAQ,SAAS;GAAO;EAAO,IAC5D;GAAE,MAAM;GAAO,SAAS;GAAM;EAAO,GACzC,CACF;EACA,IAAI,YAAY;GACd,IAAI,sBAAsB,UACxB,OAAO;GAGT,IAAI,OAAO,eAAe,YAAY,cAAc,YAClD,OAAO,WAAW;EAEtB;CACF;CAEA,IAAI,OAAO,QAAQ;EACjB,MAAM,kBAAkB,eAAe,OAAO,QAAQ,OAAO,YAAY,CAAC,QAAQ,MAAM;EAExF,OAAO,EAAE,KAAK;GAAE,MAAM;GAAO,OAAO;GAAiB,SAAS;EAAM,GAAG,GAAG;CAC5E;CAEA,OAAQ,OAA2D;AACrE,CAAC;;;;;;AAeH,MAAa,iBAAiB,WAAoE;CAChG,MAAM,aAAuB,CAAC;CAC9B,MAAM,cAA6C,CAAC;CAEpD,KAAK,MAAM,EAAE,OAAO,CAAC,GAAG,aAAa,QAAQ;EAC3C,MAAM,CAAC,aAAa;EACpB,MAAM,MAAM,OAAO,cAAc,WAAW,UAAU,MAAM;EAE5D,IAAI,OAAO,QAAQ,eAAe,CAAC,YAAY,MAC7C,YAAY,OAAO,CAAC;EAItB,CADe,OAAO,QAAQ,cAAc,YAAY,OAAO,WAAA,EACvD,KAAK,OAAO;CACtB;CAEA,OAAO;EAAE;EAAY;CAAY;AACnC"}