diff --git a/.changeset/direct-lanterns.md b/.changeset/direct-lanterns.md new file mode 100644 index 00000000000..dc973bcb8db --- /dev/null +++ b/.changeset/direct-lanterns.md @@ -0,0 +1,5 @@ +--- +"@effect/openapi-generator": patch +--- + +Reference generated type aliases directly instead of `typeof X.Type` in client signatures. diff --git a/.changeset/gentle-orbits.md b/.changeset/gentle-orbits.md new file mode 100644 index 00000000000..9b49df90e76 --- /dev/null +++ b/.changeset/gentle-orbits.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Render JSON-derived finite numbers as `Schema.Finite` in `SchemaRepresentation.toCodeDocument`. diff --git a/packages/effect/src/internal/schema/toCodeDocument.ts b/packages/effect/src/internal/schema/toCodeDocument.ts index 0e6f1804f10..4f48e2b1789 100644 --- a/packages/effect/src/internal/schema/toCodeDocument.ts +++ b/packages/effect/src/internal/schema/toCodeDocument.ts @@ -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" @@ -11,6 +12,14 @@ type CheckRepresentationAnnotation = SchemaRepresentation.CheckRepresentationAnn SchemaRepresentation.Representation > +function hasFiniteNumberCheck(checks: ReadonlyArray): 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 } @@ -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 @@ -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) } @@ -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": diff --git a/packages/effect/test/schema/representation/toCodeDocument.test.ts b/packages/effect/test/schema/representation/toCodeDocument.test.ts index 206ae1c9e5c..42f6da5c7ae 100644 --- a/packages/effect/test/schema/representation/toCodeDocument.test.ts +++ b/packages/effect/test/schema/representation/toCodeDocument.test.ts @@ -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", () => { @@ -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 }` ) } diff --git a/packages/tools/openapi-generator/src/OpenApiTransformer.ts b/packages/tools/openapi-generator/src/OpenApiTransformer.ts index 6a3c9bcc114..6196b03a162 100644 --- a/packages/tools/openapi-generator/src/OpenApiTransformer.ts +++ b/packages/tools/openapi-generator/src/OpenApiTransformer.ts @@ -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") } @@ -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 ) @@ -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}` diff --git a/packages/tools/openapi-generator/test/OpenApiGenerator.test.ts b/packages/tools/openapi-generator/test/OpenApiGenerator.test.ts index 1e581af5e4c..dbf5e93d175 100644 --- a/packages/tools/openapi-generator/test/OpenApiGenerator.test.ts +++ b/packages/tools/openapi-generator/test/OpenApiGenerator.test.ts @@ -763,7 +763,7 @@ export const make = ( export interface TestClient { readonly httpClient: HttpClient.HttpClient - readonly "getUser": (id: string, options: { readonly config?: Config | undefined } | undefined) => Effect.Effect, HttpClientError.HttpClientError | SchemaError> + readonly "getUser": (id: string, options: { readonly config?: Config | undefined } | undefined) => Effect.Effect, HttpClientError.HttpClientError | SchemaError> } export interface TestClientError { @@ -895,7 +895,7 @@ export const TestClientError = ( }, [ `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` @@ -957,7 +957,7 @@ export const TestClientError = ( [ `"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`, + `readonly "streamEventsSse": () => Stream.Stream`, `sseEventRequest(StreamEvents200Sse)`, `Stream.pipeThroughChannel(Sse.decodeSchema(schema))` ] @@ -1054,7 +1054,7 @@ export const TestClientError = ( `TestClientError<"500", undefined>` ], [ `"200": decodeSuccess(DownloadMixedContent200)`, - `typeof DownloadMixedContent200.Type | Uint8Array` + `DownloadMixedContent200 | Uint8Array` ])) it.effect("routes mixed and non-2xx bodiless success responses", () => @@ -1062,7 +1062,7 @@ export const TestClientError = ( `"200": decodeSuccess(MixedSuccess200)`, `"204": () => Effect.void`, `"304": () => Effect.void`, - `WithOptionalResponse` + `WithOptionalResponse` ])) })