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
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// service_
"/resources/\(key)/toggle"
Original file line number Diff line number Diff line change
Expand Up @@ -199,50 +199,10 @@ export class GeneratedRequestWrapperImpl implements GeneratedRequestWrapper {
public getRequestProperties(context: SdkContext): GeneratedRequestWrapper.Property[] {
const properties: GeneratedRequestWrapper.Property[] = [];

for (const pathParameter of this.getPathParamsForRequestWrapper()) {
const type = context.type.getReferenceToType(pathParameter.valueType);
const hasDefaultValue = this.hasDefaultValue(pathParameter.valueType, context);
const propertyName = this.getPropertyNameOfPathParameter(pathParameter);
properties.push({
name: getPropertyKey(propertyName.propertyName),
safeName: getPropertyKey(propertyName.safeName),
type: type.typeNodeWithoutUndefined,
isOptional: type.isOptional || hasDefaultValue,
docs: pathParameter.docs != null ? [pathParameter.docs] : undefined
});
}

for (const queryParameter of this.getAllQueryParameters()) {
const type = context.type.getReferenceToType(queryParameter.valueType);
const hasDefaultValue = this.hasDefaultValue(queryParameter.valueType, context);
const propertyName = this.getPropertyNameOfQueryParameter(queryParameter);
properties.push({
name: getPropertyKey(propertyName.propertyName),
safeName: getPropertyKey(propertyName.safeName),
type: queryParameter.allowMultiple
? ts.factory.createUnionTypeNode([
type.typeNodeWithoutUndefined,
ts.factory.createArrayTypeNode(type.typeNodeWithoutUndefined)
])
: type.typeNodeWithoutUndefined,
isOptional: type.isOptional || hasDefaultValue,
docs: queryParameter.docs != null ? [queryParameter.docs] : undefined
});
}

for (const header of this.getAllNonLiteralHeaders(context)) {
const type = context.type.getReferenceToType(header.valueType);
const hasDefaultValue = this.hasDefaultValue(header.valueType, context);
const headerName = this.getPropertyNameOfNonLiteralHeader(header);
properties.push({
name: getPropertyKey(headerName.propertyName),
safeName: getPropertyKey(headerName.safeName),
type: type.typeNodeWithoutUndefined,
isOptional: type.isOptional || hasDefaultValue,
docs: header.docs != null ? [header.docs] : undefined
});
}

// First, collect body properties to build a set of names that should not be duplicated
// from path/query/header parameters. This handles the case where a body property has
// the same name as a path parameter (e.g., both have a "key" field).
const bodyProperties: GeneratedRequestWrapper.Property[] = [];
const requestBody = this.endpoint.requestBody;
if (requestBody != null) {
HttpRequestBody._visit(requestBody, {
Expand All @@ -252,14 +212,14 @@ export class GeneratedRequestWrapperImpl implements GeneratedRequestWrapper {
inlinedRequestBody,
context
);
properties.push(...inlinedProperties);
bodyProperties.push(...inlinedProperties);
} else {
for (const property of this.getAllNonLiteralPropertiesFromInlinedRequest({
inlinedRequestBody,
context
})) {
const requestProperty = this.getInlineProperty(inlinedRequestBody, property, context);
properties.push(requestProperty);
bodyProperties.push(requestProperty);
}
}
},
Expand All @@ -269,7 +229,7 @@ export class GeneratedRequestWrapperImpl implements GeneratedRequestWrapper {
referenceToRequestBody,
context
);
properties.push(...referencedProperties);
bodyProperties.push(...referencedProperties);
} else {
const type = context.type.getReferenceToType(referenceToRequestBody.requestBodyType);
const name = this.getReferencedBodyPropertyName();
Expand All @@ -280,7 +240,7 @@ export class GeneratedRequestWrapperImpl implements GeneratedRequestWrapper {
isOptional: type.isOptional,
docs: referenceToRequestBody.docs != null ? [referenceToRequestBody.docs] : undefined
};
properties.push(requestProperty);
bodyProperties.push(requestProperty);
}
},
fileUpload: (fileUploadRequest) => {
Expand All @@ -291,7 +251,7 @@ export class GeneratedRequestWrapperImpl implements GeneratedRequestWrapper {
return;
}
const propertyName = this.getPropertyNameOfFileParameterFromName(fileProperty.key);
properties.push({
bodyProperties.push({
name: getPropertyKey(propertyName.propertyName),
safeName: getPropertyKey(propertyName.safeName),
type: this.getFileParameterType(fileProperty, context),
Expand All @@ -300,7 +260,9 @@ export class GeneratedRequestWrapperImpl implements GeneratedRequestWrapper {
});
},
bodyProperty: (inlinedProperty) => {
properties.push(this.getInlineProperty(fileUploadRequest, inlinedProperty, context));
bodyProperties.push(
this.getInlineProperty(fileUploadRequest, inlinedProperty, context)
);
},
_other: () => {
throw new Error("Unknown FileUploadRequestProperty: " + property.type);
Expand All @@ -317,6 +279,72 @@ export class GeneratedRequestWrapperImpl implements GeneratedRequestWrapper {
});
}

// Build a set of body property names to skip duplicates from path/query/header parameters.
// We use `name` (the actual interface property key) for deduplication, not `safeName`.
const bodyPropertyNames = new Set(bodyProperties.map((p) => p.name));

// Add path parameters, skipping any that conflict with body properties
for (const pathParameter of this.getPathParamsForRequestWrapper()) {
const propertyName = this.getPropertyNameOfPathParameter(pathParameter);
const name = getPropertyKey(propertyName.propertyName);
if (bodyPropertyNames.has(name)) {
continue;
}
const type = context.type.getReferenceToType(pathParameter.valueType);
const hasDefaultValue = this.hasDefaultValue(pathParameter.valueType, context);
properties.push({
name,
safeName: getPropertyKey(propertyName.safeName),
type: type.typeNodeWithoutUndefined,
isOptional: type.isOptional || hasDefaultValue,
docs: pathParameter.docs != null ? [pathParameter.docs] : undefined
});
}

// Add query parameters, skipping any that conflict with body properties
for (const queryParameter of this.getAllQueryParameters()) {
const propertyName = this.getPropertyNameOfQueryParameter(queryParameter);
const name = getPropertyKey(propertyName.propertyName);
if (bodyPropertyNames.has(name)) {
continue;
}
const type = context.type.getReferenceToType(queryParameter.valueType);
const hasDefaultValue = this.hasDefaultValue(queryParameter.valueType, context);
properties.push({
name,
safeName: getPropertyKey(propertyName.safeName),
type: queryParameter.allowMultiple
? ts.factory.createUnionTypeNode([
type.typeNodeWithoutUndefined,
ts.factory.createArrayTypeNode(type.typeNodeWithoutUndefined)
])
: type.typeNodeWithoutUndefined,
isOptional: type.isOptional || hasDefaultValue,
docs: queryParameter.docs != null ? [queryParameter.docs] : undefined
});
}

// Add headers, skipping any that conflict with body properties
for (const header of this.getAllNonLiteralHeaders(context)) {
const headerName = this.getPropertyNameOfNonLiteralHeader(header);
const name = getPropertyKey(headerName.propertyName);
if (bodyPropertyNames.has(name)) {
continue;
}
const type = context.type.getReferenceToType(header.valueType);
const hasDefaultValue = this.hasDefaultValue(header.valueType, context);
properties.push({
name,
safeName: getPropertyKey(headerName.safeName),
type: type.typeNodeWithoutUndefined,
isOptional: type.isOptional || hasDefaultValue,
docs: header.docs != null ? [header.docs] : undefined
});
}

// Add body properties at the end
properties.push(...bodyProperties);

return properties;
}

Expand Down
11 changes: 11 additions & 0 deletions generators/typescript/sdk/versions.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
# yaml-language-server: $schema=../../../fern-versions-yml.schema.json
- version: 3.37.1
changelogEntry:
- summary: |
Fix duplicate properties in request wrapper interfaces when a path parameter has the same name as a body property.
Previously, if an endpoint had a path parameter (e.g., `key`) and the request body also contained a property with the same name,
the generated request wrapper interface would have duplicate properties, causing TypeScript compilation errors.
The fix ensures that body properties take precedence, and duplicate path/query/header parameters are skipped.
type: fix
createdAt: "2025-12-08"
irVersion: 62

- version: 3.37.0
changelogEntry:
- summary: |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"type": "object",
"properties": {
"success": {
"oneOf": [
{
"type": "boolean"
},
{
"type": "null"
}
]
}
},
"additionalProperties": false,
"definitions": {}
}
1 change: 1 addition & 0 deletions seed/ruby-sdk/seed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ allowedFailures:
- package-yml
- pagination
- pagination-custom
- path-body-property-collision
- path-parameters
- plain-text
- property-access
Expand Down
5 changes: 5 additions & 0 deletions seed/ts-sdk/path-body-property-collision/.fern/metadata.json

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

78 changes: 78 additions & 0 deletions seed/ts-sdk/path-body-property-collision/.github/workflows/ci.yml

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

3 changes: 3 additions & 0 deletions seed/ts-sdk/path-body-property-collision/.gitignore

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

Loading
Loading