Skip to content
Draft
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,7 @@
---
changeKind: fix
packages:
- "@typespec/http-server-csharp"
---

add arrayDeclarationContext
8 changes: 8 additions & 0 deletions packages/http-server-csharp/src/lib/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,14 @@ export async function $onEmit(context: EmitContext<CSharpServiceEmitterOptions>)
return "TypeSpec.Service";
}

arrayDeclarationContext(array: Model, name: string, elementType: Type) {
const arrayName = ensureCSharpIdentifier(this.emitter.getProgram(), array, name);
const arrayFile = this.emitter.createSourceFile(`generated/models/${arrayName}.cs`);
arrayFile.meta[this.#sourceTypeKey] = CSharpSourceType.Model;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could potentially use this to not emit the useless array model. Ideally, we could create a context without an emitted file.

const arrayNamespace = this.#getOrAddNamespace(array.namespace);
return this.#createModelContext(arrayNamespace, arrayFile, arrayName);
}

arrayDeclaration(array: Model, name: string, elementType: Type): EmitterOutput<string> {
return this.collectionDeclaration(elementType, array);
}
Expand Down
103 changes: 103 additions & 0 deletions packages/http-server-csharp/test/generation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3396,6 +3396,109 @@ describe("collection type: defined as emitter option", () => {
});
});

describe("arrayDeclarationContext", () => {
it("generates a dedicated file for array model declarations", async () => {
await compileAndValidateMultiple(
tester,
`
model Tags is Array<string>;
@route("/tags") @get op getTags(): Tags;
`,
[
[
"Tags.cs",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we generating a file for this? What do we expect the file to contain? Shouldn't we find a way to not emit the array type (while making sure we emit the item type)?

[
"// Generated by @typespec/http-server-csharp",
"using System;",
"using System.Text.Json;",
"using System.Text.Json.Serialization;",
"using TypeSpec.Helpers.JsonConverters;",
"using TypeSpec.Helpers;",
],
],
["IContosoOperations.cs", ["Task<string[]> GetTagsAsync( )"]],
],
);
});

it("generates a dedicated file for array model with custom namespace", async () => {
await compileAndValidateMultiple(
tester,
[
`
model Items is Array<int32>;
@route("/items") @get op getItems(): Items;
`,
"My.Custom.Ns",
],
[
["Items.cs", ["// Generated by @typespec/http-server-csharp", "using System;"]],
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment

["INsOperations.cs", ["Task<int[]> GetItemsAsync( )"]],
],
);
});

it("generates a dedicated file for array model with complex element type", async () => {
await compileAndValidateMultiple(
tester,
`
model Widget {
id: int32;
name: string;
}
model WidgetList is Array<Widget>;
@route("/widgets") @get op getWidgets(): WidgetList;
`,
[
[
"Widget.cs",
[
"public partial class Widget",
"public int Id { get; set; }",
"public string Name { get; set; }",
],
],
["WidgetList.cs", ["// Generated by @typespec/http-server-csharp", "using System;"]],
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, this file shouldn't exist

["IContosoOperations.cs", ["Task<Widget[]> GetWidgetsAsync( )"]],
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we should make sure there is an appropriate using for the namespace containing Widget

],
);
});

it("generates a dedicated file for named array with union element type", async () => {
await compileAndValidateMultiple(
tester,
`
model ToolCall {
id: string;
type: "function";
name: string;
}

model CustomToolCall {
id: string;
type: "custom";
payload: string;
}

/** The tool calls generated by the model, such as function calls. */
model ToolCalls is (ToolCall | CustomToolCall)[];

model AssistantMessage {
role: "assistant";
tool_calls?: ToolCalls;
}

@route("/chat") op chat(): AssistantMessage;
`,
[
["AssistantMessage.cs", [`public string Role { get; } = "assistant";`]],
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to test for the rest of the defintion here (I think it should either be an Object or a generic json type

["ToolCalls.cs", ["// Generated by @typespec/http-server-csharp"]],
["IContosoOperations.cs", ["Task<AssistantMessage> ChatAsync( );"]],
],
);
});
});

it("emits class for model extending another model with no additional properties", async () => {
await compileAndValidateMultiple(
tester,
Expand Down
Loading