@azure-tools/typespec-ts 0.49.0 → 0.49.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/src/framework/hooks/binder.d.ts.map +1 -1
  3. package/dist/src/framework/hooks/binder.js +8 -17
  4. package/dist/src/framework/hooks/binder.js.map +1 -1
  5. package/dist/src/index.d.ts.map +1 -1
  6. package/dist/src/index.js +13 -11
  7. package/dist/src/index.js.map +1 -1
  8. package/dist/src/lib.d.ts +8 -0
  9. package/dist/src/lib.d.ts.map +1 -1
  10. package/dist/src/lib.js +10 -0
  11. package/dist/src/lib.js.map +1 -1
  12. package/dist/src/modular/buildOperations.d.ts.map +1 -1
  13. package/dist/src/modular/buildOperations.js +10 -5
  14. package/dist/src/modular/buildOperations.js.map +1 -1
  15. package/dist/src/modular/buildProjectFiles.d.ts.map +1 -1
  16. package/dist/src/modular/buildProjectFiles.js +13 -10
  17. package/dist/src/modular/buildProjectFiles.js.map +1 -1
  18. package/dist/src/modular/emitModels.d.ts.map +1 -1
  19. package/dist/src/modular/emitModels.js +43 -40
  20. package/dist/src/modular/emitModels.js.map +1 -1
  21. package/dist/src/modular/emitSamples.d.ts.map +1 -1
  22. package/dist/src/modular/emitSamples.js +9 -0
  23. package/dist/src/modular/emitSamples.js.map +1 -1
  24. package/dist/src/modular/helpers/clientHelpers.d.ts.map +1 -1
  25. package/dist/src/modular/helpers/clientHelpers.js +4 -1
  26. package/dist/src/modular/helpers/clientHelpers.js.map +1 -1
  27. package/dist/src/modular/helpers/namingHelpers.d.ts +7 -0
  28. package/dist/src/modular/helpers/namingHelpers.d.ts.map +1 -1
  29. package/dist/src/modular/helpers/namingHelpers.js +15 -0
  30. package/dist/src/modular/helpers/namingHelpers.js.map +1 -1
  31. package/dist/src/modular/helpers/operationHelpers.d.ts +19 -2
  32. package/dist/src/modular/helpers/operationHelpers.d.ts.map +1 -1
  33. package/dist/src/modular/helpers/operationHelpers.js +410 -39
  34. package/dist/src/modular/helpers/operationHelpers.js.map +1 -1
  35. package/dist/src/modular/serialization/buildDeserializerFunction.d.ts.map +1 -1
  36. package/dist/src/modular/serialization/buildDeserializerFunction.js +9 -3
  37. package/dist/src/modular/serialization/buildDeserializerFunction.js.map +1 -1
  38. package/dist/src/modular/serialization/buildXmlSerializerFunction.d.ts +12 -0
  39. package/dist/src/modular/serialization/buildXmlSerializerFunction.d.ts.map +1 -1
  40. package/dist/src/modular/serialization/buildXmlSerializerFunction.js +259 -18
  41. package/dist/src/modular/serialization/buildXmlSerializerFunction.js.map +1 -1
  42. package/dist/src/modular/static-helpers-metadata.d.ts +10 -0
  43. package/dist/src/modular/static-helpers-metadata.d.ts.map +1 -1
  44. package/dist/src/modular/static-helpers-metadata.js +10 -0
  45. package/dist/src/modular/static-helpers-metadata.js.map +1 -1
  46. package/dist/src/modular/type-expressions/get-model-expression.d.ts +3 -1
  47. package/dist/src/modular/type-expressions/get-model-expression.d.ts.map +1 -1
  48. package/dist/src/modular/type-expressions/get-model-expression.js +50 -9
  49. package/dist/src/modular/type-expressions/get-model-expression.js.map +1 -1
  50. package/dist/src/modular/type-expressions/get-nullable-expression.d.ts.map +1 -1
  51. package/dist/src/modular/type-expressions/get-nullable-expression.js +8 -0
  52. package/dist/src/modular/type-expressions/get-nullable-expression.js.map +1 -1
  53. package/dist/src/modular/type-expressions/get-type-expression.d.ts +3 -2
  54. package/dist/src/modular/type-expressions/get-type-expression.d.ts.map +1 -1
  55. package/dist/src/modular/type-expressions/get-type-expression.js.map +1 -1
  56. package/dist/src/transform/transform.d.ts.map +1 -1
  57. package/dist/src/transform/transform.js +10 -10
  58. package/dist/src/transform/transform.js.map +1 -1
  59. package/dist/src/transform/transformSchemas.d.ts.map +1 -1
  60. package/dist/src/transform/transformSchemas.js +4 -4
  61. package/dist/src/transform/transformSchemas.js.map +1 -1
  62. package/dist/src/transform/transfromRLCOptions.d.ts +1 -1
  63. package/dist/src/transform/transfromRLCOptions.d.ts.map +1 -1
  64. package/dist/src/transform/transfromRLCOptions.js +37 -24
  65. package/dist/src/transform/transfromRLCOptions.js.map +1 -1
  66. package/dist/src/utils/clientUtils.d.ts +1 -1
  67. package/dist/src/utils/clientUtils.d.ts.map +1 -1
  68. package/dist/src/utils/clientUtils.js +40 -17
  69. package/dist/src/utils/clientUtils.js.map +1 -1
  70. package/dist/src/utils/crossLanguageDef.d.ts.map +1 -1
  71. package/dist/src/utils/crossLanguageDef.js +9 -3
  72. package/dist/src/utils/crossLanguageDef.js.map +1 -1
  73. package/dist/src/utils/interfaces.d.ts +2 -1
  74. package/dist/src/utils/interfaces.d.ts.map +1 -1
  75. package/dist/src/utils/modelUtils.d.ts +2 -2
  76. package/dist/src/utils/modelUtils.d.ts.map +1 -1
  77. package/dist/src/utils/modelUtils.js +15 -16
  78. package/dist/src/utils/modelUtils.js.map +1 -1
  79. package/dist/tsconfig.tsbuildinfo +1 -1
  80. package/package.json +22 -22
  81. package/src/framework/hooks/binder.ts +12 -22
  82. package/src/index.ts +17 -10
  83. package/src/lib.ts +20 -0
  84. package/src/modular/buildOperations.ts +12 -4
  85. package/src/modular/buildProjectFiles.ts +19 -16
  86. package/src/modular/emitModels.ts +72 -44
  87. package/src/modular/emitSamples.ts +11 -1
  88. package/src/modular/helpers/clientHelpers.ts +5 -1
  89. package/src/modular/helpers/namingHelpers.ts +19 -0
  90. package/src/modular/helpers/operationHelpers.ts +533 -40
  91. package/src/modular/serialization/buildDeserializerFunction.ts +11 -2
  92. package/src/modular/serialization/buildXmlSerializerFunction.ts +375 -24
  93. package/src/modular/static-helpers-metadata.ts +10 -0
  94. package/src/modular/type-expressions/get-model-expression.ts +78 -13
  95. package/src/modular/type-expressions/get-nullable-expression.ts +9 -0
  96. package/src/modular/type-expressions/get-type-expression.ts +3 -1
  97. package/src/transform/transform.ts +5 -2
  98. package/src/transform/transformSchemas.ts +4 -1
  99. package/src/transform/transfromRLCOptions.ts +65 -24
  100. package/src/utils/clientUtils.ts +49 -16
  101. package/src/utils/crossLanguageDef.ts +8 -0
  102. package/src/utils/interfaces.ts +2 -1
  103. package/src/utils/modelUtils.ts +22 -18
  104. package/static/static-helpers/serialization/serialize-record.ts +3 -3
  105. package/static/static-helpers/serialization/xml-helpers.ts +91 -36
  106. package/static/static-helpers/urlTemplate.ts +5 -5
@@ -12,6 +12,7 @@ import { SdkContext } from "../../utils/interfaces.js";
12
12
  import {
13
13
  getAllAncestors,
14
14
  getAllProperties,
15
+ getPropertySerializedName,
15
16
  getResponseMapping
16
17
  } from "../helpers/operationHelpers.js";
17
18
  import {
@@ -218,8 +219,12 @@ function buildPolymorphicDeserializer(
218
219
  `);
219
220
  });
220
221
 
222
+ // Use wire format name for the switch since item is raw JSON from the service
223
+ const discriminatorWireName = getPropertySerializedName(
224
+ type.discriminatorProperty
225
+ );
221
226
  statements.push(`
222
- switch (item.${normalizeName(type.discriminatorProperty.name, NameType.Property)}) {
227
+ switch (item["${discriminatorWireName}"]) {
223
228
  ${cases.join("\n")}
224
229
  default:
225
230
  return item;
@@ -300,8 +305,12 @@ function buildDiscriminatedUnionDeserializer(
300
305
  return ${subtypeDeserializerName}(item as ${subTypeName});
301
306
  `);
302
307
  }
308
+ // Use wire format name for the switch since item is raw JSON from the service
309
+ const discriminatorWireName = type.discriminatorProperty
310
+ ? getPropertySerializedName(type.discriminatorProperty)
311
+ : "unknown";
303
312
  output.push(`
304
- switch (item.${type.discriminatorProperty ? normalizeName(type.discriminatorProperty.name, NameType.Property) : "unknown"}) {
313
+ switch (item["${discriminatorWireName}"]) {
305
314
  ${cases.join("\n")}
306
315
  default:
307
316
  return ${baseDeserializerName}(item);
@@ -29,8 +29,7 @@ import { NoTarget } from "@typespec/compiler";
29
29
  import { isMetadata } from "@typespec/http";
30
30
  import { normalizeModelPropertyName } from "../type-expressions/get-type-expression.js";
31
31
  import { isReadOnly } from "@azure-tools/typespec-client-generator-core";
32
- import { buildModelSerializer } from "./buildSerializerFunction.js";
33
- import { buildModelDeserializer } from "./buildDeserializerFunction.js";
32
+ import { useDependencies } from "../../framework/hooks/useDependencies.js";
34
33
 
35
34
  /**
36
35
  * Checks if a model type has XML serialization options defined
@@ -172,6 +171,215 @@ export function buildXmlModelSerializer(
172
171
  return serializerFunction;
173
172
  }
174
173
 
174
+ /**
175
+ * Builds an XML object serializer function for a model type.
176
+ * This serializer returns a plain object with XML property names (not an XML string).
177
+ * Used for nested objects within XML serialization.
178
+ */
179
+ export function buildXmlObjectModelSerializer(
180
+ context: SdkContext,
181
+ type: SdkModelType,
182
+ options: ModelSerializeOptions = {
183
+ nameOnly: false,
184
+ skipDiscriminatedUnionSuffix: false
185
+ }
186
+ ): FunctionDeclarationStructure | string | undefined {
187
+ if (!isSupportedSerializeType(type)) {
188
+ return undefined;
189
+ }
190
+
191
+ if (!type.name) {
192
+ reportDiagnostic(context.program, {
193
+ code: "anonymous-type-serialization",
194
+ target: type.__raw || NoTarget
195
+ });
196
+ return undefined;
197
+ }
198
+
199
+ if (
200
+ !type.usage ||
201
+ (type.usage !== undefined &&
202
+ (type.usage & UsageFlags.Input) !== UsageFlags.Input)
203
+ ) {
204
+ return undefined;
205
+ }
206
+
207
+ if (isAzureCoreErrorType(context.program, type.__raw!)) {
208
+ return undefined;
209
+ }
210
+
211
+ const serializerFunctionName =
212
+ options.predefinedName ??
213
+ `${normalizeModelName(
214
+ context,
215
+ type,
216
+ NameType.Operation,
217
+ options.skipDiscriminatedUnionSuffix
218
+ )}XmlObjectSerializer`;
219
+
220
+ if (options.nameOnly) {
221
+ return resolveReference(refkey(type, "xmlObjectSerializer"));
222
+ }
223
+
224
+ const properties = getAllProperties(context, type, getAllAncestors(type));
225
+
226
+ // Build the object literal with XML property names
227
+ const propertyAssignments = buildXmlObjectPropertyAssignments(
228
+ context,
229
+ properties
230
+ );
231
+
232
+ const statements: string[] = [];
233
+
234
+ // Check if the model is a dictionary type (has additionalProperties)
235
+ // For Record<T> types, we need to spread the item properties
236
+ const isDictType = type.additionalProperties !== undefined;
237
+
238
+ if (isDictType && propertyAssignments.length === 0) {
239
+ // Pure dictionary type - spread all properties
240
+ statements.push(
241
+ `return { ...item } as ${resolveReference(XmlHelpers.XmlSerializedObject)};`
242
+ );
243
+ } else if (isDictType) {
244
+ // Model with both defined properties and additional properties
245
+ statements.push(`return {${propertyAssignments}, ...item};`);
246
+ } else {
247
+ statements.push(`return {${propertyAssignments}};`);
248
+ }
249
+
250
+ const xmlSerializedObjectRef = resolveReference(
251
+ XmlHelpers.XmlSerializedObject
252
+ );
253
+
254
+ // Use _item when there are no properties and not a dict type to avoid unused parameter lint error
255
+ const paramName =
256
+ propertyAssignments.length === 0 && !isDictType ? "_item" : "item";
257
+
258
+ const serializerFunction: FunctionDeclarationStructure = {
259
+ kind: StructureKind.Function,
260
+ name: serializerFunctionName,
261
+ isExported: true,
262
+ parameters: [
263
+ {
264
+ name: paramName,
265
+ type: resolveReference(refkey(type))
266
+ }
267
+ ],
268
+ returnType: xmlSerializedObjectRef,
269
+ statements
270
+ };
271
+
272
+ return serializerFunction;
273
+ }
274
+
275
+ /**
276
+ * Builds the property assignments for XML object serializer.
277
+ * Maps client property names to XML property names in the output object.
278
+ */
279
+ function buildXmlObjectPropertyAssignments(
280
+ context: SdkContext,
281
+ properties: SdkModelPropertyType[]
282
+ ): string {
283
+ const assignments: string[] = [];
284
+
285
+ for (const property of properties) {
286
+ if (property.kind !== "property") {
287
+ continue;
288
+ }
289
+ if (isReadOnly(property)) {
290
+ continue;
291
+ }
292
+ if (isMetadata(context.program, property.__raw!)) {
293
+ continue;
294
+ }
295
+
296
+ const xmlOptions = property.serializationOptions?.xml;
297
+ const propertyName = normalizeModelPropertyName(context, property);
298
+ const cleanPropertyName = propertyName.replace(/^"|"$/g, "");
299
+
300
+ // Use XML name if available, fall back to serializedName, then JSON name, then property name
301
+ const xmlName = xmlOptions?.name ?? property.name;
302
+
303
+ // Handle nested objects and arrays
304
+ const nestedSerializer = getNestedXmlSerializer(context, property.type);
305
+
306
+ let valueExpr: string;
307
+ if (nestedSerializer && property.type.kind === "model") {
308
+ // Nested object - use XML object serializer
309
+ valueExpr = `item["${cleanPropertyName}"] !== undefined ? ${nestedSerializer}(item["${cleanPropertyName}"]) : undefined`;
310
+ } else if (
311
+ nestedSerializer &&
312
+ property.type.kind === "array" &&
313
+ property.type.valueType.kind === "model"
314
+ ) {
315
+ // Array of objects - map each item through XML object serializer
316
+ valueExpr = `item["${cleanPropertyName}"]?.map((i: any) => ${nestedSerializer}(i))`;
317
+ } else {
318
+ // Handle type-specific serialization
319
+ valueExpr = buildXmlValueSerializationExpr(
320
+ context,
321
+ property.type,
322
+ `item["${cleanPropertyName}"]`
323
+ );
324
+ }
325
+
326
+ assignments.push(`"${xmlName}": ${valueExpr}`);
327
+ }
328
+
329
+ return assignments.join(",\n ");
330
+ }
331
+
332
+ /**
333
+ * Builds the serialization expression for a value based on its type.
334
+ * Handles bytes (base64), dates, and arrays of these types.
335
+ */
336
+ function buildXmlValueSerializationExpr(
337
+ context: SdkContext,
338
+ type: SdkType,
339
+ valueExpr: string
340
+ ): string {
341
+ const uint8ArrayToStringRef = resolveReference(
342
+ useDependencies().uint8ArrayToString
343
+ );
344
+
345
+ switch (type.kind) {
346
+ case "bytes":
347
+ // Convert Uint8Array to base64 string
348
+ return `${valueExpr} !== undefined ? ${uint8ArrayToStringRef}(${valueExpr}, "base64") : undefined`;
349
+
350
+ case "utcDateTime": {
351
+ // Convert Date to appropriate string format
352
+ const encoding = (type.encode as string) ?? "rfc3339";
353
+ if (encoding === "unixTimestamp") {
354
+ return `${valueExpr} !== undefined ? ${valueExpr}.getTime() : undefined`;
355
+ } else if (encoding === "rfc7231") {
356
+ return `${valueExpr} !== undefined ? ${valueExpr}.toUTCString() : undefined`;
357
+ }
358
+ // Default rfc3339
359
+ return `${valueExpr} !== undefined ? ${valueExpr}.toISOString() : undefined`;
360
+ }
361
+
362
+ case "array": {
363
+ // Handle arrays - need to serialize each element
364
+ const itemExpr = buildXmlValueSerializationExpr(
365
+ context,
366
+ type.valueType,
367
+ "i"
368
+ );
369
+ if (itemExpr !== "i") {
370
+ // If items need transformation, map them
371
+ return `${valueExpr}?.map((i: any) => ${itemExpr})`;
372
+ }
373
+ // No transformation needed
374
+ return valueExpr;
375
+ }
376
+
377
+ default:
378
+ // Primitive types - use directly
379
+ return valueExpr;
380
+ }
381
+ }
382
+
175
383
  /**
176
384
  * Builds the property metadata array for XML serialization
177
385
  */
@@ -214,6 +422,12 @@ function buildPropertyMetadataArray(
214
422
  if (typeInfo.dateEncoding) {
215
423
  metadataObj.push(`dateEncoding: "${typeInfo.dateEncoding}"`);
216
424
  }
425
+ if (typeInfo.bytesEncoding) {
426
+ metadataObj.push(`bytesEncoding: "${typeInfo.bytesEncoding}"`);
427
+ }
428
+ if (typeInfo.itemType) {
429
+ metadataObj.push(`itemType: "${typeInfo.itemType}"`);
430
+ }
217
431
 
218
432
  // Add serializer for complex types
219
433
  const nestedSerializer = getNestedXmlSerializer(context, property.type);
@@ -273,10 +487,30 @@ function buildXmlOptionsString(xmlOptions?: {
273
487
  function getPropertyTypeInfo(type: SdkType): {
274
488
  type?: "array" | "object" | "primitive" | "date" | "bytes" | "dict";
275
489
  dateEncoding?: "rfc3339" | "rfc7231" | "unixTimestamp";
490
+ bytesEncoding?: "base64" | "base64url";
491
+ itemType?: "primitive" | "date" | "bytes";
276
492
  } {
277
493
  switch (type.kind) {
278
- case "array":
279
- return { type: "array" };
494
+ case "array": {
495
+ // For arrays, also extract item type info for bytes/date items
496
+ const itemInfo = getPropertyTypeInfo(type.valueType);
497
+ const result: ReturnType<typeof getPropertyTypeInfo> = { type: "array" };
498
+ // Only include item type info for types that need special serialization
499
+ if (
500
+ itemInfo.type === "bytes" ||
501
+ itemInfo.type === "date" ||
502
+ itemInfo.type === "primitive"
503
+ ) {
504
+ result.itemType = itemInfo.type as "primitive" | "date" | "bytes";
505
+ }
506
+ if (itemInfo.dateEncoding) {
507
+ result.dateEncoding = itemInfo.dateEncoding;
508
+ }
509
+ if (itemInfo.bytesEncoding) {
510
+ result.bytesEncoding = itemInfo.bytesEncoding;
511
+ }
512
+ return result;
513
+ }
280
514
  case "model":
281
515
  return { type: "object" };
282
516
  case "dict":
@@ -287,24 +521,30 @@ function getPropertyTypeInfo(type: SdkType): {
287
521
  dateEncoding:
288
522
  (type.encode as "rfc3339" | "rfc7231" | "unixTimestamp") ?? "rfc3339"
289
523
  };
290
- case "bytes":
291
- return { type: "bytes" };
524
+ case "bytes": {
525
+ const encode = (type as any).encode as string | undefined;
526
+ // Default to base64 if no encoding specified
527
+ const bytesEncoding =
528
+ encode === "base64url" ? "base64url" : ("base64" as const);
529
+ return { type: "bytes", bytesEncoding };
530
+ }
292
531
  default:
293
532
  return { type: "primitive" };
294
533
  }
295
534
  }
296
535
 
297
536
  /**
298
- * Gets the nested XML serializer function reference for complex types
537
+ * Gets the nested XML serializer function reference for complex types.
538
+ * Uses the XML object serializer to properly handle XML property names.
299
539
  */
300
540
  function getNestedXmlSerializer(
301
541
  context: SdkContext,
302
542
  type: SdkType
303
543
  ): string | undefined {
304
544
  if (type.kind === "model") {
305
- // For nested objects, use the regular JSON serializer which returns objects
306
- // The XML builder will convert these objects to XML elements
307
- const serializerName = buildModelSerializer(context, type, {
545
+ // For nested objects, use the XML object serializer which returns objects
546
+ // with XML property names for proper serialization
547
+ const serializerName = buildXmlObjectModelSerializer(context, type, {
308
548
  nameOnly: true,
309
549
  skipDiscriminatedUnionSuffix: false
310
550
  });
@@ -313,12 +553,16 @@ function getNestedXmlSerializer(
313
553
  }
314
554
  }
315
555
  if (type.kind === "array" && type.valueType.kind === "model") {
316
- // For arrays, use the regular JSON serializer which returns objects
556
+ // For arrays, use the XML object serializer for items
317
557
  // The XML helper calls this for each item and handles the array mapping
318
- const itemSerializer = buildModelSerializer(context, type.valueType, {
319
- nameOnly: true,
320
- skipDiscriminatedUnionSuffix: false
321
- });
558
+ const itemSerializer = buildXmlObjectModelSerializer(
559
+ context,
560
+ type.valueType,
561
+ {
562
+ nameOnly: true,
563
+ skipDiscriminatedUnionSuffix: false
564
+ }
565
+ );
322
566
  if (typeof itemSerializer === "string") {
323
567
  return itemSerializer;
324
568
  }
@@ -465,6 +709,12 @@ function buildDeserializePropertyMetadataArray(
465
709
  if (typeInfo.dateEncoding) {
466
710
  metadataObj.push(`dateEncoding: "${typeInfo.dateEncoding}"`);
467
711
  }
712
+ if (typeInfo.bytesEncoding) {
713
+ metadataObj.push(`bytesEncoding: "${typeInfo.bytesEncoding}"`);
714
+ }
715
+ if (typeInfo.itemType) {
716
+ metadataObj.push(`itemType: "${typeInfo.itemType}"`);
717
+ }
468
718
 
469
719
  // Add deserializer for complex types
470
720
  const nestedDeserializer = getNestedXmlDeserializer(context, property.type);
@@ -479,16 +729,17 @@ function buildDeserializePropertyMetadataArray(
479
729
  }
480
730
 
481
731
  /**
482
- * Gets the nested XML deserializer function reference for complex types
732
+ * Gets the nested XML deserializer function reference for complex types.
733
+ * Uses the XML object deserializer to properly handle XML property names.
483
734
  */
484
735
  function getNestedXmlDeserializer(
485
736
  context: SdkContext,
486
737
  type: SdkType
487
738
  ): string | undefined {
488
739
  if (type.kind === "model") {
489
- // For nested objects, use the regular JSON deserializer which takes parsed objects
490
- // The XML parser has already converted the XML to an object structure
491
- const deserializerName = buildModelDeserializer(context, type, {
740
+ // For nested objects, use the XML object deserializer which takes parsed XML objects
741
+ // and uses XML property names for mapping
742
+ const deserializerName = buildXmlObjectModelDeserializer(context, type, {
492
743
  nameOnly: true,
493
744
  skipDiscriminatedUnionSuffix: false
494
745
  });
@@ -497,15 +748,115 @@ function getNestedXmlDeserializer(
497
748
  }
498
749
  }
499
750
  if (type.kind === "array" && type.valueType.kind === "model") {
500
- // For arrays, use the regular JSON deserializer which takes parsed objects
751
+ // For arrays, use the XML object deserializer for items
501
752
  // The XML helper calls this for each item and handles the array mapping
502
- const itemDeserializer = buildModelDeserializer(context, type.valueType, {
503
- nameOnly: true,
504
- skipDiscriminatedUnionSuffix: false
505
- });
753
+ const itemDeserializer = buildXmlObjectModelDeserializer(
754
+ context,
755
+ type.valueType,
756
+ {
757
+ nameOnly: true,
758
+ skipDiscriminatedUnionSuffix: false
759
+ }
760
+ );
506
761
  if (typeof itemDeserializer === "string") {
507
762
  return itemDeserializer;
508
763
  }
509
764
  }
510
765
  return undefined;
511
766
  }
767
+
768
+ /**
769
+ * Builds an XML object deserializer function for a model type.
770
+ * This deserializer takes a pre-parsed XML object (not an XML string)
771
+ * and uses XML property names for mapping. Used for nested objects.
772
+ */
773
+ export function buildXmlObjectModelDeserializer(
774
+ context: SdkContext,
775
+ type: SdkModelType,
776
+ options: ModelSerializeOptions = {
777
+ nameOnly: false,
778
+ skipDiscriminatedUnionSuffix: false
779
+ }
780
+ ): FunctionDeclarationStructure | string | undefined {
781
+ if (!isSupportedSerializeType(type)) {
782
+ return undefined;
783
+ }
784
+
785
+ if (!type.name) {
786
+ reportDiagnostic(context.program, {
787
+ code: "anonymous-type-deserialization",
788
+ target: type.__raw || NoTarget
789
+ });
790
+ return undefined;
791
+ }
792
+
793
+ if (
794
+ !type.usage ||
795
+ (type.usage !== undefined &&
796
+ (type.usage & UsageFlags.Output) !== UsageFlags.Output &&
797
+ (type.usage & UsageFlags.Exception) !== UsageFlags.Exception)
798
+ ) {
799
+ return undefined;
800
+ }
801
+
802
+ if (isAzureCoreErrorType(context.program, type.__raw!)) {
803
+ return undefined;
804
+ }
805
+
806
+ const deserializerFunctionName =
807
+ options.predefinedName ??
808
+ `${normalizeModelName(
809
+ context,
810
+ type,
811
+ NameType.Operation,
812
+ options.skipDiscriminatedUnionSuffix
813
+ )}XmlObjectDeserializer`;
814
+
815
+ if (options.nameOnly) {
816
+ return resolveReference(refkey(type, "xmlObjectDeserializer"));
817
+ }
818
+
819
+ const deserializeXmlObjectRef = resolveReference(
820
+ XmlHelpers.deserializeXmlObject
821
+ );
822
+ const xmlPropertyDeserializeMetadataRef = resolveReference(
823
+ XmlHelpers.XmlPropertyDeserializeMetadata
824
+ );
825
+
826
+ const properties = getAllProperties(context, type, getAllAncestors(type));
827
+
828
+ // Build property metadata array for deserialization
829
+ const propertyMetadata = buildDeserializePropertyMetadataArray(
830
+ context,
831
+ properties
832
+ );
833
+
834
+ const statements: string[] = [];
835
+
836
+ // Generate the properties metadata constant
837
+ statements.push(
838
+ `const properties: ${xmlPropertyDeserializeMetadataRef}[] = [${propertyMetadata}];`
839
+ );
840
+
841
+ // Generate the deserialization call - no rootName needed for object deserializer
842
+ const typeRef = resolveReference(refkey(type));
843
+ statements.push(
844
+ `return ${deserializeXmlObjectRef}<${typeRef}>(xmlObject, properties);`
845
+ );
846
+
847
+ const deserializerFunction: FunctionDeclarationStructure = {
848
+ kind: StructureKind.Function,
849
+ name: deserializerFunctionName,
850
+ isExported: true,
851
+ parameters: [
852
+ {
853
+ name: "xmlObject",
854
+ type: "Record<string, unknown>"
855
+ }
856
+ ],
857
+ returnType: resolveReference(refkey(type)),
858
+ statements
859
+ };
860
+
861
+ return deserializerFunction;
862
+ }
@@ -209,6 +209,11 @@ export const XmlHelpers = {
209
209
  name: "deserializeFromXml",
210
210
  location: "serialization/xml-helpers.ts"
211
211
  },
212
+ deserializeXmlObject: {
213
+ kind: "function",
214
+ name: "deserializeXmlObject",
215
+ location: "serialization/xml-helpers.ts"
216
+ },
212
217
  isXmlContentType: {
213
218
  kind: "function",
214
219
  name: "isXmlContentType",
@@ -218,5 +223,10 @@ export const XmlHelpers = {
218
223
  kind: "function",
219
224
  name: "isJsonContentType",
220
225
  location: "serialization/xml-helpers.ts"
226
+ },
227
+ XmlSerializedObject: {
228
+ kind: "interface",
229
+ name: "XmlSerializedObject",
230
+ location: "serialization/xml-helpers.ts"
221
231
  }
222
232
  } as const;
@@ -4,12 +4,17 @@ import {
4
4
  normalizeModelPropertyName
5
5
  } from "./get-type-expression.js";
6
6
 
7
- import { SdkModelType } from "@azure-tools/typespec-client-generator-core";
7
+ import {
8
+ SdkModelPropertyType,
9
+ SdkModelType,
10
+ SdkServiceResponseHeader
11
+ } from "@azure-tools/typespec-client-generator-core";
8
12
  import { refkey } from "../../framework/refkey.js";
9
13
  import { resolveReference } from "../../framework/reference.js";
10
14
  import { shouldEmitInline } from "./utils.js";
11
15
  import { useContext } from "../../contextManager.js";
12
16
  import { SdkContext } from "../../utils/interfaces.js";
17
+ import { MultipartHelpers } from "../static-helpers-metadata.js";
13
18
 
14
19
  export interface ModelExpressionOptions extends EmitTypeOptions {
15
20
  skipPolymorphicUnion?: boolean;
@@ -26,18 +31,7 @@ export function getModelExpression(
26
31
  }
27
32
 
28
33
  if (shouldEmitInline(type, options)) {
29
- // generate Record<string, any> for empty anonymous object
30
- if (type.properties.length === 0) {
31
- return "Record<string, any>";
32
- }
33
- return `{
34
- ${type.properties
35
- .map(
36
- (p) =>
37
- `${normalizeModelPropertyName(context, p)}${p.optional ? "?" : ""}: ${getTypeExpression(context, p.type)}`
38
- )
39
- .join(",\n")}
40
- }`;
34
+ return emitInlineModel(context, type.properties);
41
35
  } else {
42
36
  if (!options.skipPolymorphicUnion && type.discriminatedSubtypes) {
43
37
  return resolveReference(refkey(type, "polymorphicType"));
@@ -52,6 +46,77 @@ const externalModels: Record<string, string> = {
52
46
  "Azure.Core.Foundations.ErrorResponse": "AzureCoreErrorResponse"
53
47
  };
54
48
 
49
+ export function emitInlineModel(
50
+ context: SdkContext,
51
+ properties: (SdkModelPropertyType | SdkServiceResponseHeader)[]
52
+ ): string {
53
+ // generate Record<string, any> for empty anonymous object
54
+ if (properties.length === 0) {
55
+ return "Record<string, any>";
56
+ }
57
+ return `{
58
+ ${properties
59
+ .map(
60
+ (p) =>
61
+ `${normalizeModelPropertyName(context, p)}${p.optional ? "?" : ""}: ${getPropertyTypeExpression(context, p)}`
62
+ )
63
+ .join(",\n")}
64
+ }`;
65
+ }
66
+
67
+ function getPropertyTypeExpression(
68
+ context: SdkContext,
69
+ property: SdkModelPropertyType | SdkServiceResponseHeader
70
+ ): string {
71
+ if (
72
+ property.kind === "property" &&
73
+ property.serializationOptions.multipart?.isFilePart
74
+ ) {
75
+ return getMultipartFileTypeExpression(context, property);
76
+ }
77
+ return getTypeExpression(context, property.type);
78
+ }
79
+
80
+ export function getMultipartFileTypeExpression(
81
+ context: SdkContext,
82
+ property: SdkModelPropertyType
83
+ ): string {
84
+ const multipartOptions = property.serializationOptions.multipart;
85
+
86
+ const isContentTypeOptional =
87
+ multipartOptions?.contentType === undefined ||
88
+ multipartOptions.contentType.optional ||
89
+ multipartOptions.defaultContentTypes.length > 0;
90
+ const isFilenameOptional =
91
+ multipartOptions?.filename === undefined ||
92
+ multipartOptions.filename.optional;
93
+
94
+ const contentTypeType = multipartOptions?.contentType
95
+ ? getTypeExpression(context, multipartOptions.contentType.type)
96
+ : "string";
97
+ const filenameType = multipartOptions?.filename
98
+ ? getTypeExpression(context, multipartOptions.filename.type)
99
+ : "string";
100
+
101
+ let typeExpression = "{";
102
+ typeExpression += `contents: ${resolveReference(MultipartHelpers.FileContents)};`;
103
+ typeExpression += `contentType${isContentTypeOptional ? "?" : ""}: ${contentTypeType};`;
104
+ typeExpression += `filename${isFilenameOptional ? "?" : ""}: ${filenameType};`;
105
+ typeExpression += "}";
106
+
107
+ if (isContentTypeOptional && isFilenameOptional) {
108
+ typeExpression = `(${resolveReference(MultipartHelpers.FileContents)}) | ${typeExpression}`;
109
+ } else {
110
+ typeExpression = `File | ${typeExpression}`;
111
+ }
112
+
113
+ if (property.type.kind === "array") {
114
+ typeExpression = `Array<${typeExpression}>`;
115
+ }
116
+
117
+ return typeExpression;
118
+ }
119
+
55
120
  export function getExternalModel(type: SdkModelType) {
56
121
  const commonName = externalModels[type.crossLanguageDefinitionId];
57
122
 
@@ -10,7 +10,16 @@ export function getNullableExpression(
10
10
  options: EmitTypeOptions = {}
11
11
  ): string {
12
12
  if (shouldEmitInline(type, options)) {
13
+ // Check if we should ignore null for optional properties
14
+ const ignoreNullableOnOptional =
15
+ context.rlcOptions?.ignoreNullableOnOptional ?? false;
16
+ const isOptional = options.isOptional ?? false;
17
+
13
18
  const nonNullableType = getTypeExpression(context, type.type, options);
19
+ // If the property is optional and we should ignore nullable, just return the non-nullable type
20
+ if (ignoreNullableOnOptional && isOptional) {
21
+ return nonNullableType;
22
+ }
14
23
  return `(${nonNullableType}) | null`;
15
24
  } else {
16
25
  return resolveReference(type);
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  SdkHttpParameter,
3
3
  SdkModelPropertyType,
4
+ SdkServiceResponseHeader,
4
5
  SdkType
5
6
  } from "@azure-tools/typespec-client-generator-core";
6
7
  import { getCredentialExpression } from "./get-credential-expression.js";
@@ -13,11 +14,12 @@ import { getNullableExpression } from "./get-nullable-expression.js";
13
14
 
14
15
  export interface EmitTypeOptions {
15
16
  emitInline?: boolean;
17
+ isOptional?: boolean;
16
18
  }
17
19
 
18
20
  export function normalizeModelPropertyName(
19
21
  context: SdkContext,
20
- property: SdkModelPropertyType | SdkHttpParameter
22
+ property: SdkModelPropertyType | SdkHttpParameter | SdkServiceResponseHeader
21
23
  ): string {
22
24
  const normalizedPropName = normalizeName(property.name, NameType.Property);
23
25
  return context.rlcOptions?.ignorePropertyNameNormalize