Hyperlinkv0.8.0-beta.28

Schema

Schema.toFormatterfunctioneffect/Schema.ts:13135
<S extends Constraint>(
  schema: S,
  options?: {
    readonly onBefore?:
      | ((
          ast: SchemaAST.AST,
          recur: (ast: SchemaAST.AST) => Formatter<any>
        ) => Formatter<any> | undefined)
      | undefined
  }
): Formatter<S["Type"]>

Derives a string formatter function from a schema. The formatter converts a value to its human-readable string representation, recursing into structs, arrays, and unions.

Details

The optional onBefore hook lets you intercept specific AST nodes before the default formatting logic runs.

Formatter
Source effect/Schema.ts:13135134 lines
export function toFormatter<S extends Constraint>(schema: S, options?: {
  readonly onBefore?:
    | ((ast: SchemaAST.AST, recur: (ast: SchemaAST.AST) => Formatter<any>) => Formatter<any> | undefined)
    | undefined
}): Formatter<S["Type"]> {
  return recur(schema.ast)

  function recur(ast: SchemaAST.AST): Formatter<S["Type"]> {
    // ---------------------------------------------
    // handle annotation
    // ---------------------------------------------
    const annotation = InternalAnnotations.resolve(ast)?.["toFormatter"]
    if (typeof annotation === "function") {
      return annotation(SchemaAST.isDeclaration(ast) ? ast.typeParameters.map(recur) : [])
    }
    // ---------------------------------------------
    // handle onBefore
    // ---------------------------------------------
    if (options?.onBefore) {
      const onBefore = options.onBefore(ast, recur)
      if (onBefore !== undefined) {
        return onBefore
      }
    }
    // ---------------------------------------------
    // handle base case
    // ---------------------------------------------
    return on(ast)
  }

  function on(ast: SchemaAST.AST): Formatter<any> {
    switch (ast._tag) {
      default:
        return format
      case "Never":
        return () => "never"
      case "Void":
        return () => "void"
      case "Arrays": {
        const elements = ast.elements.map(recur)
        const rest = ast.rest.map(recur)
        return (t) => {
          const out: Array<string> = []
          let i = 0
          // ---------------------------------------------
          // handle elements
          // ---------------------------------------------
          for (; i < elements.length; i++) {
            if (t.length < i + 1) {
              if (SchemaAST.isOptional(ast.elements[i])) {
                continue
              }
            } else {
              out.push(elements[i](t[i]))
            }
          }
          // ---------------------------------------------
          // handle rest element
          // ---------------------------------------------
          if (rest.length > 0) {
            const [head, ...tail] = rest
            for (; i < t.length - tail.length; i++) {
              out.push(head(t[i]))
            }
            // ---------------------------------------------
            // handle post rest elements
            // ---------------------------------------------
            for (let j = 0; j < tail.length; j++) {
              out.push(tail[j](t[i + j]))
            }
          }

          return "[" + out.join(", ") + "]"
        }
      }
      case "Objects": {
        const propertySignatures = ast.propertySignatures.map((ps) => recur(ps.type))
        const indexSignatures = ast.indexSignatures.map((is) => recur(is.type))
        if (ast.propertySignatures.length === 0 && ast.indexSignatures.length === 0) {
          return format
        }
        return (t) => {
          const out: Array<string> = []
          const visited = new Set<PropertyKey>()
          // ---------------------------------------------
          // handle property signatures
          // ---------------------------------------------
          for (let i = 0; i < propertySignatures.length; i++) {
            const ps = ast.propertySignatures[i]
            const name = ps.name
            visited.add(name)
            if (SchemaAST.isOptional(ps.type) && !Object.hasOwn(t, name)) {
              continue
            }
            out.push(`${formatPropertyKey(name)}: ${propertySignatures[i](t[name])}`)
          }
          // ---------------------------------------------
          // handle index signatures
          // ---------------------------------------------
          for (let i = 0; i < indexSignatures.length; i++) {
            const keys = SchemaAST.getIndexSignatureKeys(t, ast.indexSignatures[i].parameter)
            for (const key of keys) {
              if (visited.has(key)) {
                continue
              }
              visited.add(key)
              out.push(`${formatPropertyKey(key)}: ${indexSignatures[i](t[key])}`)
            }
          }

          return out.length > 0 ? "{ " + out.join(", ") + " }" : "{}"
        }
      }
      case "Union": {
        const getCandidates = (t: any) => SchemaAST.getCandidates(t, ast.types)
        return (t) => {
          const candidates = getCandidates(t)
          const refinements = candidates.map(SchemaParser._is)
          for (let i = 0; i < candidates.length; i++) {
            const is = refinements[i]
            if (is(t)) {
              return recur(candidates[i])(t)
            }
          }
          return format(t)
        }
      }
      case "Suspend": {
        const get = SchemaAST.memoizeThunk(() => recur(ast.thunk()))
        return (t) => get()(t)
      }
    }
  }
}