Skip to content

Commit

Permalink
fix(typescript): support object type declarations with extraProperties (
Browse files Browse the repository at this point in the history
  • Loading branch information
dsinghvi authored Dec 3, 2024
1 parent 1628dd3 commit 945414c
Show file tree
Hide file tree
Showing 478 changed files with 15,404 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,16 @@ export class GeneratedObjectTypeImpl<Context extends ModelContext>
};

return propertyNode;
})
}),
...(this.shape.extraProperties
? [
{
name: "[key: string]", // This is the simpler way to add an index signature
type: "any",
docs: [{ description: "Accepts any additional properties" }]
}
]
: [])
],
isExported: true
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ export class GeneratedObjectTypeSchemaImpl<Context extends ModelContext>
schema = schema.extend(context.typeSchema.getSchemaOfNamedType(extension, { isGeneratingSchema: true }));
}

if (this.shape.extraProperties) {
schema = schema.passthrough();
}

return schema;
}

Expand All @@ -46,14 +50,24 @@ export class GeneratedObjectTypeSchemaImpl<Context extends ModelContext>
extends: this.shape.extends.map((extension) =>
getTextOfTsNode(context.typeSchema.getReferenceToRawNamedType(extension).getTypeNode())
),
properties: this.shape.properties.map((property) => {
const type = context.typeSchema.getReferenceToRawType(property.valueType);
return {
name: `"${property.name.wireValue}"`,
type: getTextOfTsNode(type.typeNodeWithoutUndefined),
hasQuestionToken: type.isOptional
};
})
properties: [
...this.shape.properties.map((property) => {
const type = context.typeSchema.getReferenceToRawType(property.valueType);
return {
name: `"${property.name.wireValue}"`,
type: getTextOfTsNode(type.typeNodeWithoutUndefined),
hasQuestionToken: type.isOptional
};
}),
...(this.shape.extraProperties
? [
{
name: "[key: string]",
type: "any"
}
]
: [])
]
});
}

Expand Down
12 changes: 12 additions & 0 deletions generators/typescript/sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.42.7] - 2024-12-03

- Feature: Support `additionalProperties` in OpenAPI or `extra-properties` in the Fern Defnition. Now
an object that has additionalProperties marked as true will generate the following interface:

```ts
interface User {
propertyOne: string
[key: string]: any
}
```

## [0.42.6] - 2024-11-23

- Fix: Remove the generated `APIPromise` since it is not compatible on certain node versions.
Expand Down
2 changes: 1 addition & 1 deletion generators/typescript/sdk/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.42.6
0.42.7
Original file line number Diff line number Diff line change
Expand Up @@ -221,15 +221,19 @@ export class GeneratedSdkEndpointTypeSchemasImpl implements GeneratedSdkEndpoint
switch (this.endpoint.requestBody.requestBodyType.type) {
case "unknown":
return referenceToParsedRequest;
case "named":
case "named": {
const typeDeclaration = context.type.getTypeDeclaration(this.endpoint.requestBody.requestBodyType);
return context.typeSchema
.getSchemaOfNamedType(this.endpoint.requestBody.requestBodyType, { isGeneratingSchema: false })
.jsonOrThrow(referenceToParsedRequest, {
...getSchemaOptions({
allowExtraFields: this.allowExtraFields,
allowExtraFields:
this.allowExtraFields ??
(typeDeclaration.shape.type === "object" && typeDeclaration.shape.extraProperties),
omitUndefined: this.omitUndefined
})
});
}
case "primitive":
case "container":
if (this.generatedRequestSchema == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export class GeneratedSdkInlinedRequestBodySchemaImpl
}
return this.getReferenceToZurgSchema(context).jsonOrThrow(referenceToParsedRequest, {
...getSchemaOptions({
allowExtraFields: this.allowExtraFields,
allowExtraFields: this.allowExtraFields ?? this.inlinedRequestBody.extraProperties,
omitUndefined: this.omitUndefined
})
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export class SdkInlinedRequestBodySchemaGenerator {
inlinedRequestBody: endpoint.requestBody,
typeName,
includeSerdeLayer: this.includeSerdeLayer,
allowExtraFields: this.allowExtraFields,
allowExtraFields: this.allowExtraFields ?? endpoint.requestBody.extraProperties,
omitUndefined: this.omitUndefined
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export declare namespace Zurg {

interface ObjectUtils {
extend: (extension: Zurg.Schema) => ObjectSchema;
passthrough: () => ObjectSchema;
}

interface Property {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,28 @@ export class ZurgImpl extends CoreUtility implements Zurg {

private getObjectUtils(objectSchema: Zurg.BaseSchema): Zurg.ObjectUtils {
return {
extend: (extension) => this.extend(objectSchema, extension)
extend: (extension) => this.extend(objectSchema, extension),
passthrough: () => {
const baseSchema: Zurg.BaseSchema = {
isOptional: false,
toExpression: () =>
ts.factory.createCallExpression(
ts.factory.createPropertyAccessExpression(
objectSchema.toExpression(),
ts.factory.createIdentifier("passthrough")
),
undefined,
[]
)
};

return {
...baseSchema,
...this.getSchemaUtils(baseSchema),
...this.getObjectLikeUtils(baseSchema),
...this.getObjectUtils(baseSchema)
};
}
};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { object, string, stringLiteral } from "../../builders";
import { itJson, itParse, itSchema } from "../utils/itSchema";
import { itValidate } from "../utils/itValidate";

describe("passthrough", () => {
const baseSchema = object({
foo: string(),
bar: stringLiteral("bar")
});

describe("parse", () => {
itParse("includes unknown values", baseSchema.passthrough(), {
raw: {
foo: "hello",
bar: "bar",
baz: "extra"
},
parsed: {
foo: "hello",
bar: "bar",
baz: "extra"
}
});

itValidate(
"preserves schema validation",
baseSchema.passthrough(),
{
foo: 123,
bar: "bar",
baz: "extra"
},
[
{
path: ["foo"],
message: "Expected string. Received 123."
}
]
);
});

describe("json", () => {
itJson("includes unknown values", baseSchema.passthrough(), {
raw: {
foo: "hello",
bar: "bar",

baz: "extra"
},
parsed: {
foo: "hello",
bar: "bar",

baz: "extra"
}
});

itValidate(
"preserves schema validation",
baseSchema.passthrough(),
{
foo: "hello",
bar: "wrong",
baz: "extra"
},
[
{
path: ["bar"],
message: 'Expected "bar". Received "wrong".'
}
]
);
});

itSchema("preserves schema validation in both directions", baseSchema.passthrough(), {
raw: {
foo: "hello",
bar: "bar",
extra: 42
},
parsed: {
foo: "hello",
bar: "bar",
extra: 42
}
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,47 @@ export function getObjectUtils<Raw, Parsed>(schema: BaseObjectSchema<Raw, Parsed
getType: () => SchemaType.OBJECT
};

return {
...baseSchema,
...getSchemaUtils(baseSchema),
...getObjectLikeUtils(baseSchema),
...getObjectUtils(baseSchema)
};
},
passthrough: () => {
const baseSchema: BaseObjectSchema<Raw & { [key: string]: unknown }, Parsed & { [key: string]: unknown }> =
{
_getParsedProperties: () => schema._getParsedProperties(),
_getRawProperties: () => schema._getRawProperties(),
parse: (raw, opts) => {
const transformed = schema.parse(raw, { ...opts, unrecognizedObjectKeys: "passthrough" });
if (!transformed.ok) {
return transformed;
}
return {
ok: true,
value: {
...(raw as any),
...transformed.value
}
};
},
json: (parsed, opts) => {
const transformed = schema.json(parsed, { ...opts, unrecognizedObjectKeys: "passthrough" });
if (!transformed.ok) {
return transformed;
}
return {
ok: true,
value: {
...(parsed as any),
...transformed.value
}
};
},
getType: () => SchemaType.OBJECT
};

return {
...baseSchema,
...getSchemaUtils(baseSchema),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface ObjectUtils<Raw, Parsed> {
extend: <RawExtension, ParsedExtension>(
schemas: ObjectSchema<RawExtension, ParsedExtension>
) => ObjectSchema<Raw & RawExtension, Parsed & ParsedExtension>;
passthrough: () => ObjectSchema<Raw & { [key: string]: unknown }, Parsed & { [key: string]: unknown }>;
}

export type inferRawObject<O extends ObjectSchema<any, any>> = O extends ObjectSchema<infer Raw, any> ? Raw : never;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 945414c

Please sign in to comment.