Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/direct-lanterns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect/openapi-generator": patch
---

Reference generated type aliases directly instead of `typeof X.Type` in client signatures.
5 changes: 5 additions & 0 deletions .changeset/gentle-orbits.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": patch
---

Render JSON-derived finite numbers as `Schema.Finite` in `SchemaRepresentation.toCodeDocument`.
27 changes: 26 additions & 1 deletion packages/effect/src/internal/schema/toCodeDocument.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as Arr from "../../Array.ts"
import { format, formatPropertyKey } from "../../Formatter.ts"
import type * as Schema from "../../Schema.ts"
import * as SchemaAST from "../../SchemaAST.ts"
import type * as SchemaRepresentation from "../../SchemaRepresentation.ts"
import { errorWithPath } from "../errors.ts"
import * as InternalRecord from "../record.ts"
Expand All @@ -11,6 +12,14 @@ type CheckRepresentationAnnotation = SchemaRepresentation.CheckRepresentationAnn
SchemaRepresentation.Representation
>

function hasFiniteNumberCheck(checks: ReadonlyArray<SchemaRepresentation.Check>): boolean {
return checks.some((check) =>
check._tag === "Filter"
? check.representation?.id === "effect/schema/isFinite" || check.representation?.id === "effect/schema/isInt"
: hasFiniteNumberCheck(check.checks)
)
}

/** @internal */
export function makeCode(runtime: string, Type: string): SchemaRepresentation.Code {
return { runtime, Type }
Expand Down Expand Up @@ -330,6 +339,10 @@ export function toCodeDocument(
return rendered === undefined ? "" : `.${method}(${rendered})`
}

function defaultFiniteAnnotations(): string {
return runtimeAnnotate(SchemaAST.isFinite().annotations)
}

function compileCheck(
check: SchemaRepresentation.Check,
path: Path
Expand Down Expand Up @@ -365,6 +378,18 @@ export function toCodeDocument(
for (let index = 0; index < representation.checks.length; index++) {
const check = representation.checks[index]
const brands = checkBrands(check)
// `Schema.Finite` already carries this check, so an unannotated or
// default-annotated `isFinite` filter adds nothing to the rendered code.
if (
base.runtime === "Schema.Finite" &&
check._tag === "Filter" &&
check.representation?.id === "effect/schema/isFinite" &&
!check.aborted &&
brands.length === 0
) {
const rendered = runtimeAnnotate(check.annotations)
if (rendered === "" || rendered === defaultFiniteAnnotations()) continue
}
runtime += `.check(${compileCheck(check, [...path, "checks", index])})${runtimeBrands(brands)}`
if (includeTypeBrands) Type += typeBrands(brands)
}
Expand Down Expand Up @@ -434,7 +459,7 @@ export function toCodeDocument(
case "String":
return makeCode("Schema.String", "string")
case "Number":
return makeCode("Schema.Number", "number")
return makeCode(hasFiniteNumberCheck(representation.checks) ? "Schema.Finite" : "Schema.Number", "number")
case "Boolean":
return makeCode("Schema.Boolean", "boolean")
case "BigInt":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,42 @@ describe("toCodeDocument", () => {
}
)
})

it("Number & unannotated isFinite", () => {
assertSchema(
{ schema: Schema.Number.check(Schema.isFinite({ expected: undefined })) },
{
codes: makeCode("Schema.Finite", "number")
}
)
})

it("Number & default isFinite", () => {
assertSchema(
{ schema: Schema.Number.check(Schema.isFinite()) },
{
codes: makeCode("Schema.Finite", "number")
}
)
})

it("Number & annotated isFinite", () => {
assertSchema(
{ schema: Schema.Number.check(Schema.isFinite({ expected: undefined, description: "finite" })) },
{
codes: makeCode(`Schema.Finite.check(Schema.isFinite().annotate({ "description": "finite" }))`, "number")
}
)
})

it("Number & isInt", () => {
assertSchema(
{ schema: Schema.Number.check(Schema.isInt({ expected: undefined })) },
{
codes: makeCode("Schema.Finite.check(Schema.isInt())", "number")
}
)
})
})

it("Boolean", () => {
Expand Down Expand Up @@ -1850,7 +1886,7 @@ describe("toCodeDocument", () => {
{
$ref: "A",
code: makeCode(
`Schema.Struct({ "b": Schema.Number.check(Schema.isFinite()), "a": Schema.String }).annotate({ "identifier": "A" })`,
`Schema.Struct({ "b": Schema.Finite, "a": Schema.String }).annotate({ "identifier": "A" })`,
`{ readonly "b": number, readonly "a": string }`
)
}
Expand Down
8 changes: 4 additions & 4 deletions packages/tools/openapi-generator/src/OpenApiTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ ${clientErrorSource(name)}`
args.push(`options: { ${options.join("; ")} } | undefined`)
}

const successTypes = new Set(Array.from(responses.successSchemas.values(), (schema) => `typeof ${schema}.Type`))
const successTypes = new Set(responses.successSchemas.values())
if (responses.binarySuccessStatuses.size > 0) {
successTypes.add("Uint8Array")
}
Expand All @@ -161,7 +161,7 @@ ${clientErrorSource(name)}`
if (responses.errorSchemas.size > 0) {
Utils.spreadElementsInto(
Array.from(responses.errorSchemas.values()).map(
(schema) => `${name}Error<"${schema}", typeof ${schema}.Type>`
(schema) => `${name}Error<"${schema}", ${schema}>`
),
errors
)
Expand Down Expand Up @@ -206,8 +206,8 @@ ${clientErrorSource(name)}`
const methodKey = `readonly "${operation.id}Sse"`
const parameters = args.join(", ")
const value = responses.sseSchemaMode === "event"
? `typeof ${responses.sseSchema}.Type`
: `{ readonly event: string; readonly id: string | undefined; readonly data: typeof ${responses.sseSchema}.Type }`
? responses.sseSchema
: `{ readonly event: string; readonly id: string | undefined; readonly data: ${responses.sseSchema} }`
const returnType =
`Stream.Stream<${value}, HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, typeof ${responses.sseSchema}.DecodingServices>`
return `${jsdoc}${methodKey}: (${parameters}) => ${returnType}`
Expand Down
10 changes: 5 additions & 5 deletions packages/tools/openapi-generator/test/OpenApiGenerator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -763,7 +763,7 @@ export const make = (

export interface TestClient {
readonly httpClient: HttpClient.HttpClient
readonly "getUser": <Config extends OperationConfig>(id: string, options: { readonly config?: Config | undefined } | undefined) => Effect.Effect<WithOptionalResponse<typeof GetUser200.Type, Config>, HttpClientError.HttpClientError | SchemaError>
readonly "getUser": <Config extends OperationConfig>(id: string, options: { readonly config?: Config | undefined } | undefined) => Effect.Effect<WithOptionalResponse<GetUser200, Config>, HttpClientError.HttpClientError | SchemaError>
}

export interface TestClientError<Tag extends string, E> {
Expand Down Expand Up @@ -895,7 +895,7 @@ export const TestClientError = <Tag extends string, E>(
},
[
`import * as Sse from "effect/unstable/encoding/Sse"`,
`readonly "streamEventsSse": () => Stream.Stream<{ readonly event: string; readonly id: string | undefined; readonly data: typeof StreamEvents200Sse.Type }, HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, typeof StreamEvents200Sse.DecodingServices>`,
`readonly "streamEventsSse": () => Stream.Stream<{ readonly event: string; readonly id: string | undefined; readonly data: StreamEvents200Sse }, HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, typeof StreamEvents200Sse.DecodingServices>`,
`"streamEventsSse": () => HttpClientRequest.get(\`/events\`).pipe(`,
`sseRequest(StreamEvents200Sse)`,
`schema: Schema.ConstraintDecoder<Type, DecodingServices>`
Expand Down Expand Up @@ -957,7 +957,7 @@ export const TestClientError = <Tag extends string, E>(
[
`"id": Schema.optionalKey(Schema.String), "event": Schema.Literal("message"), "data": Schema.String`,
`"id": Schema.optionalKey(Schema.String), "event": Schema.Literal("effect/httpapi/stream/failure"), "data": Schema.String`,
`readonly "streamEventsSse": () => Stream.Stream<typeof StreamEvents200Sse.Type, HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, typeof StreamEvents200Sse.DecodingServices>`,
`readonly "streamEventsSse": () => Stream.Stream<StreamEvents200Sse, HttpClientError.HttpClientError | SchemaError | Sse.Retry | Sse.SseError, typeof StreamEvents200Sse.DecodingServices>`,
`sseEventRequest(StreamEvents200Sse)`,
`Stream.pipeThroughChannel(Sse.decodeSchema(schema))`
]
Expand Down Expand Up @@ -1054,15 +1054,15 @@ export const TestClientError = <Tag extends string, E>(
`TestClientError<"500", undefined>`
], [
`"200": decodeSuccess(DownloadMixedContent200)`,
`typeof DownloadMixedContent200.Type | Uint8Array`
`DownloadMixedContent200 | Uint8Array`
]))

it.effect("routes mixed and non-2xx bodiless success responses", () =>
assertRuntimeIncludes(voidSuccessSpec, [
`"200": decodeSuccess(MixedSuccess200)`,
`"204": () => Effect.void`,
`"304": () => Effect.void`,
`WithOptionalResponse<typeof MixedSuccess200.Type | void, Config>`
`WithOptionalResponse<MixedSuccess200 | void, Config>`
]))
})

Expand Down