Skip to content

Commit 08d8f39

Browse files
committed
Merge branch 'python-sample-test-generation-optimization' of https://github.com/microsoft/typespec into python-sample-test-generation-optimization
2 parents 56bbcbd + 7372d76 commit 08d8f39

File tree

97 files changed

+359
-102
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

97 files changed

+359
-102
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
3+
changeKind: fix
4+
packages:
5+
- "@typespec/openapi3"
6+
---
7+
8+
Handle use of `.now()` constructor on date time types in examples and default.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
3+
changeKind: feature
4+
packages:
5+
- "@typespec/compiler"
6+
---
7+
8+
[API] `serializeValueAsJson` throws a `UnsupportedScalarConstructorError` for unsupported scalar constructor instead of crashing
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
changeKind: fix
3+
packages:
4+
- "@typespec/http-client-python"
5+
---
6+
7+
Properly cache enum values

eng/common/config/area.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export const CIRules = {
5555
"!.prettierrc.json",
5656
"!cspell.yaml", // CSpell is already run as its dedicated CI(via github action)
5757
"!eslint.config.json", // Eslint is already run as its dedicated CI(via github action)
58+
"!.chronus/**/*", // Used across emitters
5859
...ignore(isolatedEmitters),
5960
...ignore(AreaPaths["emitter:client:csharp"]),
6061
...ignore(AreaPaths["emitter:client:java"]),
File renamed without changes.

packages/astro-utils/src/expressive-code/plugins/tsp-tryit-code.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ export default function (playgroundUrl: string) {
4343
display: flex;
4444
align-items: center;
4545
padding: 0 0.5rem;
46+
text-decoration: none;
47+
color: var(--colorPaletteGreenBackground3);
48+
49+
&:hover {
50+
color: var(--colorPaletteGreenBackground4);
51+
}
4652
}
4753
4854
.tryit-link.with-title {
@@ -59,12 +65,7 @@ export default function (playgroundUrl: string) {
5965
right: 0px;
6066
height: 34px;
6167
}
62-
6368
64-
.tryit-link {
65-
text-decoration: none;
66-
color: var(--colorPaletteGreenBackground3);
67-
}
6869
6970
.play {
7071
height: 20px;
@@ -85,7 +86,7 @@ export default function (playgroundUrl: string) {
8586

8687
const compilerOptions = JSON.parse(tryitStr);
8788
const extraElements = [];
88-
const hasTitle = metaOptions.getString("title");
89+
const hasTitle = codeBlock.props.title || metaOptions.getString("title");
8990
extraElements.push(
9091
h(
9192
"a",

packages/astro-utils/tsconfig.build.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"allowImportingTsExtensions": false,
55
"noEmit": false,
66
"declaration": true,
7+
"sourceMap": true,
78
"rootDir": "src",
89
"outDir": "dist"
910
},

packages/compiler/src/lib/examples.ts

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,38 @@ import {
1212
} from "../core/types.js";
1313
import { getEncode, resolveEncodedName, type EncodeData } from "./decorators.js";
1414

15+
/**
16+
* Error thrown when a scalar value cannot be serialized because it uses an unsupported constructor.
17+
*/
18+
export class UnsupportedScalarConstructorError extends Error {
19+
constructor(
20+
public readonly scalarName: string,
21+
public readonly constructorName: string,
22+
public readonly supportedConstructors: readonly string[],
23+
) {
24+
super(
25+
`Cannot serialize scalar '${scalarName}' with constructor '${constructorName}'. Supported constructors: ${supportedConstructors.join(", ")}`,
26+
);
27+
this.name = "UnsupportedScalarConstructorError";
28+
}
29+
}
30+
31+
export interface ValueJsonSerializers {
32+
/** Custom handler to serialize a scalar value
33+
* @param value The scalar value to serialize
34+
* @param type The type of the scalar value in the current context
35+
* @param encodeAs The encoding information for the scalar value, if any
36+
* @param originalFn The original serialization function to fall back to. Throws `UnsupportedScalarConstructorError` if the scalar constructor is not supported.
37+
* @returns The serialized value
38+
*/
39+
serializeScalarValue?: (
40+
value: ScalarValue,
41+
type: Type,
42+
encodeAs: EncodeData | undefined,
43+
originalFn: (value: ScalarValue, type: Type, encodeAs: EncodeData | undefined) => unknown,
44+
) => unknown;
45+
}
46+
1547
/**
1648
* Serialize the given TypeSpec value as a JSON object using the given type and its encoding annotations.
1749
* The Value MUST be assignable to the given type.
@@ -21,9 +53,16 @@ export function serializeValueAsJson(
2153
value: Value,
2254
type: Type,
2355
encodeAs?: EncodeData,
56+
handlers?: ValueJsonSerializers,
2457
): unknown {
2558
if (type.kind === "ModelProperty") {
26-
return serializeValueAsJson(program, value, type.type, encodeAs ?? getEncode(program, type));
59+
return serializeValueAsJson(
60+
program,
61+
value,
62+
type.type,
63+
encodeAs ?? getEncode(program, type),
64+
handlers,
65+
);
2766
}
2867
switch (value.valueKind) {
2968
case "NullValue":
@@ -48,7 +87,7 @@ export function serializeValueAsJson(
4887
case "ObjectValue":
4988
return serializeObjectValueAsJson(program, value, type);
5089
case "ScalarValue":
51-
return serializeScalarValueAsJson(program, value, type, encodeAs);
90+
return serializeScalarValueAsJson(program, value, type, encodeAs, handlers);
5291
}
5392
}
5493

@@ -149,7 +188,17 @@ function serializeScalarValueAsJson(
149188
value: ScalarValue,
150189
type: Type,
151190
encodeAs: EncodeData | undefined,
191+
handlers?: ValueJsonSerializers,
152192
): unknown {
193+
if (handlers?.serializeScalarValue) {
194+
return handlers.serializeScalarValue(
195+
value,
196+
type,
197+
encodeAs,
198+
serializeScalarValueAsJson.bind(null, program, value, type, encodeAs, undefined),
199+
);
200+
}
201+
153202
const result = resolveKnownScalar(program, value.scalar);
154203
if (result === undefined) {
155204
return serializeValueAsJson(program, value.value.args[0], value.value.args[0].type);
@@ -159,15 +208,30 @@ function serializeScalarValueAsJson(
159208

160209
switch (result.scalar.name) {
161210
case "utcDateTime":
162-
return ScalarSerializers.utcDateTime((value.value.args[0] as any as any).value, encodeAs);
211+
if (value.value.name === "fromISO") {
212+
return ScalarSerializers.utcDateTime((value.value.args[0] as any).value, encodeAs);
213+
}
214+
throw new UnsupportedScalarConstructorError("utcDateTime", value.value.name, ["fromISO"]);
163215
case "offsetDateTime":
164-
return ScalarSerializers.offsetDateTime((value.value.args[0] as any).value, encodeAs);
216+
if (value.value.name === "fromISO") {
217+
return ScalarSerializers.offsetDateTime((value.value.args[0] as any).value, encodeAs);
218+
}
219+
throw new UnsupportedScalarConstructorError("offsetDateTime", value.value.name, ["fromISO"]);
165220
case "plainDate":
166-
return ScalarSerializers.plainDate((value.value.args[0] as any).value);
221+
if (value.value.name === "fromISO") {
222+
return ScalarSerializers.plainDate((value.value.args[0] as any).value);
223+
}
224+
throw new UnsupportedScalarConstructorError("plainDate", value.value.name, ["fromISO"]);
167225
case "plainTime":
168-
return ScalarSerializers.plainTime((value.value.args[0] as any).value);
226+
if (value.value.name === "fromISO") {
227+
return ScalarSerializers.plainTime((value.value.args[0] as any).value);
228+
}
229+
throw new UnsupportedScalarConstructorError("plainTime", value.value.name, ["fromISO"]);
169230
case "duration":
170-
return ScalarSerializers.duration((value.value.args[0] as any).value, encodeAs);
231+
if (value.value.name === "fromISO") {
232+
return ScalarSerializers.duration((value.value.args[0] as any).value, encodeAs);
233+
}
234+
throw new UnsupportedScalarConstructorError("duration", value.value.name, ["fromISO"]);
171235
}
172236
}
173237

packages/http-client-csharp/emitter/src/lib/type-converter.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,18 @@ export function fromSdkType<T extends SdkType>(
107107
retVar = fromSdkArrayType(sdkContext, sdkType);
108108
break;
109109
case "constant":
110+
// Don't transform optional Content-Type headers into enums - keep them as constants
111+
const isContentTypeHeader =
112+
sdkProperty &&
113+
"kind" in sdkProperty &&
114+
sdkProperty.kind === "header" &&
115+
"serializedName" in sdkProperty &&
116+
typeof sdkProperty.serializedName === "string" &&
117+
sdkProperty.serializedName.toLocaleLowerCase() === "content-type";
118+
110119
if (
111120
sdkProperty &&
121+
!isContentTypeHeader &&
112122
(sdkProperty.optional || sdkProperty?.type.kind === "nullable") &&
113123
sdkProperty?.type.kind !== "boolean" &&
114124
sdkType.valueType.kind !== "boolean"

packages/http-client-csharp/emitter/test/Unit/operation-converter.test.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,5 +366,95 @@ describe("Operation Converter", () => {
366366
strictEqual(response.bodyType, undefined);
367367
});
368368
});
369+
370+
describe("Optional Content-Type header", () => {
371+
it("Optional body should have Content-Type remain as Constant (not transformed to enum)", async () => {
372+
const program = await typeSpecCompile(
373+
`
374+
model BodyModel {
375+
name: string;
376+
}
377+
378+
@post
379+
op withOptionalBody(@body body?: BodyModel): void;
380+
`,
381+
runner,
382+
);
383+
const context = createEmitterContext(program);
384+
const sdkContext = await createCSharpSdkContext(context);
385+
const root = createModel(sdkContext);
386+
387+
strictEqual(root.clients.length, 1);
388+
strictEqual(root.clients[0].methods.length, 1);
389+
390+
const method = root.clients[0].methods[0];
391+
ok(method);
392+
393+
// validate operation
394+
const operation = method.operation;
395+
ok(operation);
396+
397+
// Find Content-Type parameter
398+
const contentTypeParam = operation.parameters.find((p) => p.name === "contentType");
399+
ok(contentTypeParam, "Content-Type parameter should exist");
400+
strictEqual(contentTypeParam.kind, "header");
401+
strictEqual(contentTypeParam.serializedName, "Content-Type");
402+
strictEqual(contentTypeParam.optional, true, "Content-Type should be optional");
403+
strictEqual(
404+
contentTypeParam.scope,
405+
"Constant",
406+
"Content-Type should remain Constant scope",
407+
);
408+
strictEqual(
409+
contentTypeParam.type.kind,
410+
"constant",
411+
"Content-Type should remain a constant type, not transformed to enum",
412+
);
413+
});
414+
415+
it("Required body should have Content-Type with Constant scope", async () => {
416+
const program = await typeSpecCompile(
417+
`
418+
model BodyModel {
419+
name: string;
420+
}
421+
422+
@post
423+
op withRequiredBody(@body body: BodyModel): void;
424+
`,
425+
runner,
426+
);
427+
const context = createEmitterContext(program);
428+
const sdkContext = await createCSharpSdkContext(context);
429+
const root = createModel(sdkContext);
430+
431+
strictEqual(root.clients.length, 1);
432+
strictEqual(root.clients[0].methods.length, 1);
433+
434+
const method = root.clients[0].methods[0];
435+
ok(method);
436+
437+
// validate operation
438+
const operation = method.operation;
439+
ok(operation);
440+
441+
// Find Content-Type parameter
442+
const contentTypeParam = operation.parameters.find((p) => p.name === "contentType");
443+
ok(contentTypeParam, "Content-Type parameter should exist");
444+
strictEqual(contentTypeParam.kind, "header");
445+
strictEqual(contentTypeParam.serializedName, "Content-Type");
446+
strictEqual(contentTypeParam.optional, false, "Content-Type should be required");
447+
strictEqual(
448+
contentTypeParam.scope,
449+
"Constant",
450+
"Content-Type should have Constant scope for required body",
451+
);
452+
strictEqual(
453+
contentTypeParam.type.kind,
454+
"constant",
455+
"Content-Type should be a constant type",
456+
);
457+
});
458+
});
369459
});
370460
});

0 commit comments

Comments
 (0)