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
Comment thread
timotheeguerin marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: fix
packages:
- "@typespec/http-server-csharp"
---

Apply C# naming conventions to generated type file names.
5 changes: 3 additions & 2 deletions packages/http-server-csharp/src/components/enums/enums.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export function Enums(props: EnumsProps): Children {
<For each={allEnums}>
{(info) => {
const namePolicy = cs.useCSharpNamePolicy();
const enumName = namePolicy.getName(info.name, "enum");
const subNsParts = getSubNamespaceParts(info.namespace, props.serviceNamespace);

const enumDecl = (
Expand All @@ -93,7 +94,7 @@ export function Enums(props: EnumsProps): Children {
/>
<hbr />
<cs.EnumDeclaration
name={namePolicy.getName(info.name, "enum")}
name={enumName}
public
refkey={efRefkey(info.type)}
doc={getDocComments($, info.type)}
Expand Down Expand Up @@ -124,7 +125,7 @@ export function Enums(props: EnumsProps): Children {
);

return (
<CSharpFile path={`${info.name}.cs`} using={["System.Text.Json"]}>
<CSharpFile path={`${enumName}.cs`} using={["System.Text.Json"]}>
Comment thread
timotheeguerin marked this conversation as resolved.
{wrappedContent}
</CSharpFile>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export function Models(props: ModelsProps): Children {
const needsJsonNodes = modelNeedsJsonNodes($, model);
const usings = needsJsonNodes ? [...modelUsings, "System.Text.Json.Nodes"] : modelUsings;
const modelName = getModelEmitName($.program, model);
const modelFileName = cs.useCSharpNamePolicy().getName(modelName, "class");
const subNsParts = getSubNamespaceParts(model.namespace, props.serviceNamespace);

const modelContent = <ServerClassDeclaration type={model} emitName={modelName} />;
Expand All @@ -75,7 +76,7 @@ export function Models(props: ModelsProps): Children {
);

return (
<CSharpFile path={`${modelName}.cs`} using={usings}>
<CSharpFile path={`${modelFileName}.cs`} using={usings}>
{wrappedContent}
</CSharpFile>
);
Expand Down
13 changes: 7 additions & 6 deletions packages/http-server-csharp/src/components/render-root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ export function ControllersAndInterfaces(props: ControllersAndInterfacesProps):
const interfaceOps = props.interfaces.map((iface) => ({
iface,
ops: props.canonicalOpsMap.get(iface.name) ?? [],
name: namePolicy.getName(iface.name, "class"),
}));

// Collect operations that need request model classes
const requestModels: RequestModelInfo[] = [];
for (const { iface, ops } of interfaceOps) {
for (const { iface, ops, name } of interfaceOps) {
for (const op of ops) {
// GET requests don't have body parameters in the server
if (op.method === "get") continue;
Expand All @@ -45,7 +46,7 @@ export function ControllersAndInterfaces(props: ControllersAndInterfacesProps):
const hasExplicitBody = body.bodies[0].property !== undefined;
if (!hasExplicitBody) {
const opName = namePolicy.getName(op.name, "class-method");
const requestModelName = `${iface.name}${opName}Request`;
const requestModelName = `${name}${opName}Request`;
requestModels.push({ name: requestModelName, op, ifaceName: iface.name });
}
}
Expand All @@ -60,13 +61,13 @@ export function ControllersAndInterfaces(props: ControllersAndInterfacesProps):
</SourceDirectory>
<SourceDirectory path="operations">
<For each={interfaceOps}>
{({ iface, ops }) => {
{({ iface, ops, name }) => {
const hasMultipart = ops.some(
(op) => op.requestParameters.body?.bodyKind === "multipart",
);
return (
<CSharpFile
path={`I${iface.name}.cs`}
path={`I${name}.cs`}
using={[
"System",
"System.Collections.Generic",
Expand All @@ -86,13 +87,13 @@ export function ControllersAndInterfaces(props: ControllersAndInterfacesProps):
<SourceDirectory path="controllers">
<Namespace name="Controllers">
<For each={interfaceOps}>
{({ iface, ops }) => {
{({ iface, ops, name }) => {
const hasMultipart = ops.some(
(op) => op.requestParameters.body?.bodyKind === "multipart",
);
return (
<CSharpFile
path={`${iface.name}Controller.cs`}
path={`${name}Controller.cs`}
using={[
"System",
"System.Net",
Expand Down
37 changes: 37 additions & 0 deletions packages/http-server-csharp/test/emitter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,40 @@ it("uses deterministic default ports for project files", async () => {
expect(launchSettings).toContain("https://localhost:7000;http://localhost:5000");
expect(launchSettings).toContain("http://localhost:5000");
});

it("uses C# type names for generated type files", async () => {
const [result] = await compileAndDiagnose(
tester,
getStandardService(`
enum camelEnum {
value
}

model camelModel {}

@route("/items")
interface camelInterface {
@post op create(item: string): void;
}
`),
{ "emit-mocks": "mocks-and-project-files", "skip-format": true },
);
const files = [...result.fs.fs.keys()];

expect(files.some((path) => path.endsWith("/generated/models/CamelEnum.cs"))).toBe(true);
expect(files.some((path) => path.endsWith("/generated/models/CamelModel.cs"))).toBe(true);
expect(
files.some((path) => path.endsWith("/generated/models/CamelInterfaceCreateRequest.cs")),
).toBe(true);
expect(files.some((path) => path.endsWith("/generated/operations/ICamelInterface.cs"))).toBe(
true,
);
expect(
files.some((path) => path.endsWith("/generated/controllers/CamelInterfaceController.cs")),
).toBe(true);
expect(files.some((path) => path.endsWith("/mocks/CamelInterface.cs"))).toBe(true);
expect(files.some((path) => path.includes("/generated/models/camel"))).toBe(false);
// cspell:ignore Icamel
expect(files.some((path) => path.includes("/generated/operations/Icamel"))).toBe(false);
expect(files.some((path) => path.includes("/generated/controllers/camel"))).toBe(false);
});
Loading