Skip to content

Commit

Permalink
fix(csharp): Serialize headers and querystring params more reliably (#…
Browse files Browse the repository at this point in the history
…5706)

---------

Co-authored-by: fern-support <[email protected]>
  • Loading branch information
Swimburger and fern-support authored Jan 22, 2025
1 parent 71c6f9b commit 89b06f2
Show file tree
Hide file tree
Showing 116 changed files with 4,996 additions and 48 deletions.
5 changes: 5 additions & 0 deletions fern/pages/changelogs/cli/2025-01-22.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## 0.50.17
**`(fix):`** Improve performance of `fern docs dev` by only reloading the markdown content when
only markdown files are changed, avoiding unnecessary recompilation of the full docs.


10 changes: 10 additions & 0 deletions fern/pages/changelogs/csharp-sdk/2025-01-22.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
## 1.9.12
**`(fix):`** Change serialization logic for headers and querystring parameters:
- Strings, enums, dates, times, and date times are serialized as before.
- Booleans are now serialized as `true` and `false` instead of `True` and `False`.
- Objects, lists, maps, dictionaries, undiscriminated, and discriminated unions are serialized to JSON.


**`(fix):`** Only use `.Value` on nullable structs when serializing parameters to headers and querystring parameters.


142 changes: 139 additions & 3 deletions generators/csharp/sdk/src/endpoint/request/WrappedEndpointRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { csharp } from "@fern-api/csharp-codegen";
import {
HttpEndpoint,
HttpHeader,
HttpService,
Name,
PrimitiveTypeV1,
QueryParameter,
SdkRequest,
SdkRequestWrapper,
Expand Down Expand Up @@ -189,7 +189,12 @@ export class WrappedEndpointRequest extends EndpointRequest {
allowOptionals?: boolean;
}): csharp.CodeBlock {
const parameter = parameterOverride ?? `${this.getParameterName()}.${name.pascalCase.safeName}`;
const maybeDotValue = this.isOptional({ typeReference: reference }) && (allowOptionals ?? true) ? ".Value" : "";
const maybeDotValue =
this.isOptional({ typeReference: reference }) &&
this.isStruct({ typeReference: reference }) &&
(allowOptionals ?? true)
? ".Value"
: "";
if (this.isString(reference)) {
return csharp.codeblock(`${parameter}`);
} else if (this.isDateOrDateTime({ type: "datetime", typeReference: reference })) {
Expand All @@ -210,8 +215,18 @@ export class WrappedEndpointRequest extends EndpointRequest {
writer.addNamespace(this.context.getCoreNamespace());
writer.write(`${parameter}${maybeDotValue}.Stringify()`);
});
} else if (this.shouldJsonSerialize({ typeReference: reference })) {
return csharp.codeblock((writer) => {
writer.writeNode(
csharp.invokeMethod({
on: this.context.getJsonUtilsClassReference(),
method: "Serialize",
arguments_: [csharp.codeblock(`${parameter}${maybeDotValue}`)]
})
);
});
} else {
return csharp.codeblock(`${parameter}.ToString()`);
return csharp.codeblock(`${parameter}${maybeDotValue}.ToString()`);
}
}

Expand Down Expand Up @@ -312,6 +327,84 @@ export class WrappedEndpointRequest extends EndpointRequest {
}
}

/**
* Whether is a struct in .NET
*/
private isStruct({ typeReference }: { typeReference: TypeReference }): boolean {
return typeReference._visit<boolean>({
container: (container) => {
return container._visit<boolean>({
list: () => false,
map: () => false,
set: () => false,
literal: (literal) => {
return literal._visit({
string: () => false,
boolean: () => true,
_other: () => false
});
},
optional: (optional) => this.isStruct({ typeReference: optional }),
_other: () => false
});
},
named: (named) => {
const declaration = this.context.getTypeDeclarationOrThrow(named.typeId);
return declaration.shape._visit<boolean>({
alias: (alias) => this.isStruct({ typeReference: alias.aliasOf }),
object: () => false,
undiscriminatedUnion: () => false,
union: () => false,
// this won't be true for forward compatible enums
enum: () => true,
_other: () => false
});
},
primitive: (primitive) => {
return (
primitive.v2?._visit<boolean | undefined>({
integer: () => true,
long: () => true,
uint: () => true,
uint64: () => true,
float: () => true,
double: () => true,
boolean: () => true,
date: () => true,
dateTime: () => true,
uuid: () => true,
bigInteger: () => true,
string: () => false,
// if typed as bytes, it's a struct
// if typed as a string, it's not
base64: () => false,
_other: () => undefined
}) ??
PrimitiveTypeV1._visit<boolean>(primitive.v1, {
integer: () => true,
long: () => true,
uint: () => true,
uint64: () => true,
float: () => true,
double: () => true,
boolean: () => true,
date: () => true,
dateTime: () => true,
uuid: () => true,
bigInteger: () => true,
string: () => false,
// if typed as bytes, it's a struct
// if typed as a string, it's not
base64: () => false,
_other: () => false
})
);
},
unknown: () => false,
_other: () => false
});
}

private isDateOrDateTime({
type,
typeReference
Expand Down Expand Up @@ -367,4 +460,47 @@ export class WrappedEndpointRequest extends EndpointRequest {
}
}
}

private shouldJsonSerialize({ typeReference }: { typeReference: TypeReference }): boolean {
return typeReference._visit({
container: (container) => {
return container._visit({
list: () => true,
map: () => true,
set: () => true,
literal: (literal) => {
return literal._visit({
string: () => false,
boolean: () => true,
_other: () => false
});
},
optional: (optional) => {
return this.shouldJsonSerialize({ typeReference: optional });
},
_other: () => false
});
},
named: (named) => {
const declaration = this.context.getTypeDeclarationOrThrow(named.typeId);
return declaration.shape._visit({
alias: (alias) => this.shouldJsonSerialize({ typeReference: alias.aliasOf }),
object: () => true,
undiscriminatedUnion: () => true,
union: () => true,
enum: () => false,
_other: () => false
});
},
primitive: (primitive) => {
const isBoolean = primitive.v2?.type === "boolean" || primitive.v1 === "BOOLEAN";
if (isBoolean) {
return true;
}
return false;
},
unknown: () => true,
_other: () => false
});
}
}
13 changes: 13 additions & 0 deletions generators/csharp/sdk/versions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@
# The C# SDK now uses forward-compatible enums which are not compatible with the previously generated enums.
# Set `enable-forward-compatible-enums` to `false` in the configuration to generate the old enums.
# irVersion: 53
- version: 1.9.12
createdAt: "2025-01-22"
changelogEntry:
- type: fix
summary: |
Change serialization logic for headers and querystring parameters:
- Strings, enums, dates, times, and date times are serialized as before.
- Booleans are now serialized as `true` and `false` instead of `True` and `False`.
- Objects, lists, maps, dictionaries, undiscriminated, and discriminated unions are serialized to JSON.
- type: fix
summary: |
Only use `.Value` on nullable structs when serializing parameters to headers and querystring parameters.
irVersion: 53
- version: 1.9.11
createdAt: "2024-11-25"
changelogEntry:
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.

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.

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.

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.

6 changes: 6 additions & 0 deletions seed/csharp-sdk/literal/.mock/definition/inlined.yml

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

1 change: 1 addition & 0 deletions seed/csharp-sdk/literal/.mock/definition/reference.yml

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.

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

Loading

0 comments on commit 89b06f2

Please sign in to comment.