Skip to content

Commit

Permalink
Properly type-check applySchema, in backwards compatible way using Fa…
Browse files Browse the repository at this point in the history
…ilToCompose
  • Loading branch information
diogob committed Jun 7, 2024
1 parent 8ad045f commit b490896
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 24 deletions.
51 changes: 28 additions & 23 deletions src/constructors.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { mapErrors } from './combinators.ts'
import { EnvironmentError, ErrorList, InputError } from './errors.ts'
import { Internal } from './internal/types.ts'
import type {
Composable,
ComposableWithSchema,
Expand Down Expand Up @@ -107,7 +108,9 @@ function withSchema<I, E>(
hander: (input: I, environment: E) => Output,
) => ComposableWithSchema<Output> {
return (handler) =>
applySchema(inputSchema, environmentSchema)(composable(handler))
applySchema(inputSchema, environmentSchema)(
composable(handler),
) as ComposableWithSchema<Awaited<ReturnType<typeof handler>>>
}

/**
Expand All @@ -132,36 +135,38 @@ function withSchema<I, E>(
* })))
* ```
*/
function applySchema<I, E>(
inputSchema?: ParserSchema<I>,
environmentSchema?: ParserSchema<E>,
): <R>(
fn: Composable<(input?: I, environment?: E) => R>,
) => ComposableWithSchema<R> {
return (fn) => {
return (input?: unknown, environment?: unknown) => {
function applySchema<ParsedInput, ParsedEnvironment>(
inputSchema?: ParserSchema<ParsedInput>,
environmentSchema?: ParserSchema<ParsedEnvironment>,
) {
return (<R, Input, Environment>(
fn: Composable<(input?: Input, environment?: Environment) => R>,
): ParsedInput extends Input
? ParsedEnvironment extends Environment ? ComposableWithSchema<R>
: Internal.FailToCompose<ParsedEnvironment, Environment>
: Internal.FailToCompose<ParsedInput, Input> => {
return ((input?: unknown, environment?: unknown) => {
const envResult = (environmentSchema ?? alwaysUnknownSchema).safeParse(
environment,
)
const result = (inputSchema ?? alwaysUnknownSchema).safeParse(input)

if (!result.success || !envResult.success) {
const inputErrors = result.success
? []
: result.error.issues.map(
(error) => new InputError(error.message, error.path as string[]),
)
const envErrors = envResult.success
? []
: envResult.error.issues.map(
(error) =>
new EnvironmentError(error.message, error.path as string[]),
)
const inputErrors = result.success ? [] : result.error.issues.map(
(error) => new InputError(error.message, error.path as string[]),
)
const envErrors = envResult.success ? [] : envResult.error.issues.map(
(error) =>
new EnvironmentError(error.message, error.path as string[]),
)
return Promise.resolve(failure([...inputErrors, ...envErrors]))
}
return fn(result.data as I, envResult.data as E)
}
}
return fn(result.data as Input, envResult.data as Environment)
}) as ParsedInput extends Input
? ParsedEnvironment extends Environment ? ComposableWithSchema<R>
: Internal.FailToCompose<ParsedEnvironment, Environment>
: Internal.FailToCompose<ParsedInput, Input>
})
}

const alwaysUnknownSchema: ParserSchema<unknown> = {
Expand Down
12 changes: 11 additions & 1 deletion src/tests/constructors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import {
z,
} from './prelude.ts'
import type {
ComposableWithSchema,
Composable,
ComposableWithSchema,
Result,
Success,
} from '../index.ts'
Expand All @@ -23,6 +23,7 @@ import {
withSchema,
} from '../index.ts'
import { applySchema } from '../index.ts'
import { Internal } from '../internal/types.ts'

const add = composable((a: number, b: number) => a + b)
const asyncAdd = (a: number, b: number) => Promise.resolve(a + b)
Expand Down Expand Up @@ -388,6 +389,15 @@ describe('applySchema', () => {
)
})

it('fails to compose when schema result is wider than composable input', async () => {
const inputSchema = z.string()

const handler = applySchema(inputSchema)(composable((x: 'a') => x))
type _R = Expect<
Equal<typeof handler, Internal.FailToCompose<string, 'a'>>
>
})

it('can be used as a layer on top of withSchema fn', async () => {
const fn = withSchema(z.object({ id: z.number() }))(({ id }) => id + 1)
const prepareSchema = z.string().transform((v) => ({ id: Number(v) }))
Expand Down

0 comments on commit b490896

Please sign in to comment.