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
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,8 @@ export interface _PagedEvaluation {
value: Evaluation[];
/** The link to the next page of items */
nextLink?: string;
/** An opaque, globally-unique, client-generated string identifier for the request. */
clientRequestId?: string;
}

export function _pagedEvaluationDeserializer(item: any): _PagedEvaluation {
Expand Down Expand Up @@ -723,6 +725,8 @@ export interface _PagedEvaluationSchedule {
value: EvaluationSchedule[];
/** The link to the next page of items */
nextLink?: string;
/** An opaque, globally-unique, client-generated string identifier for the request. */
clientRequestId?: string;
}

export function _pagedEvaluationScheduleDeserializer(item: any): _PagedEvaluationSchedule {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ export interface _TodoPage {
pageSize: number;
/** The total number of items */
totalSize: number;
/** The limit to the number of items */
limit?: number;
/** The offset to start paginating at */
offset?: number;
/** A link to the previous page, if it exists */
prevLink?: string;
/** A link to the next page, if it exists */
Expand Down
38 changes: 36 additions & 2 deletions packages/typespec-ts/src/modular/emitModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -693,13 +693,31 @@ function buildModelInterface(
type: SdkModelType
): InterfaceDeclarationStructure {
const flattenPropertySet = new Set<SdkModelPropertyType>();
// For non-input models (output-only, exception, etc.), filter out metadata
// properties (@header, @query, @path) since they are deserialized separately.
// For input models, keep metadata properties — users need to pass them.
const hasInputUsage = (type.usage & UsageFlags.Input) === UsageFlags.Input;
const isArmResource = isArmResourceModel(type);
const interfaceStructure = {
kind: StructureKind.Interface,
name: normalizeModelName(context, type, NameType.Interface, true),
isExported: true,
properties: type.properties
.filter((p) => !isMetadata(context.program, p.__raw!))
.filter((p) => {
if (!hasInputUsage && p.__raw && isMetadata(context.program, p.__raw)) {
return false;
}
// Skip the "name" metadata property on ARM resource models.
// ARM resource "name" is a @path property inherited from the base Resource type
// and is handled by the ARM infrastructure, not set by the user directly.
if (
isArmResource &&
p.name === "name" &&
p.__raw &&
isMetadata(context.program, p.__raw)
) {
return false;
}
// filter out the flatten property to be processed later
if (p.flatten && p.type.kind === "model") {
flattenPropertySet.add(p);
Expand All @@ -718,7 +736,7 @@ function buildModelInterface(
context,
flatten.type,
getAllAncestors(flatten.type)
).filter((p) => !isMetadata(context.program, p.__raw!));
);
interfaceStructure.properties!.push(
...allProperties.map((p) => {
// when the flattened property is optional, all its child properties should be optional too
Expand Down Expand Up @@ -893,6 +911,22 @@ export function normalizeModelName(
return `${internalModelPrefix}${normalizeName(namespacePrefix + type.name, nameType, true)}${unionSuffix}`;
}

/**
* Checks if a model extends an ARM resource base type (TrackedResource, ProxyResource, etc.)
* by walking the ancestor chain and checking for Azure.ResourceManager types.
*/
function isArmResourceModel(type: SdkModelType): boolean {
const ancestors = getAllAncestors(type);
return ancestors.some(
(ancestor) =>
ancestor.kind === "model" &&
((ancestor.crossLanguageDefinitionId ?? "").startsWith(
"Azure.ResourceManager"
) ||
(ancestor.namespace ?? "").startsWith("Azure.ResourceManager"))
);
}

function buildModelPolymorphicType(context: SdkContext, type: SdkModelType) {
// Only include direct subtypes in this union
const directSubtypes = getDirectSubtypes(type);
Expand Down
107 changes: 80 additions & 27 deletions packages/typespec-ts/src/modular/helpers/operationHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ import {
SdkLroPagingServiceMethod,
SdkLroServiceMethod,
SdkMethod,
SdkMethodParameter,
SdkModelPropertyType,
SdkModelType,
SdkPagingServiceMethod,
Expand Down Expand Up @@ -1590,14 +1589,7 @@ function getHeaderAndBodyParameters(

if (parametersImplementation.header.length) {
paramStr = `${paramStr}\nheaders: {${parametersImplementation.header
.map((i) =>
buildHeaderParameter(
dpgContext.program,
i.paramMap,
i.param,
optionalParamName
)
)
.map((i) => buildHeaderParameter(dpgContext.program, i.paramMap, i.param))
.join(",\n")}, ...${optionalParamName}.requestOptions?.headers },`;
}
if (
Expand All @@ -1617,10 +1609,8 @@ function getHeaderAndBodyParameters(
function buildHeaderParameter(
program: Program,
paramMap: string,
param: SdkHttpParameter,
optionalParamName: string = "options"
param: SdkHttpParameter
): string {
const paramName = param.name;
const effectiveOptional = getEffectiveOptional(param);
if (!effectiveOptional && isTypeNullable(param.type) === true) {
reportDiagnostic(program, {
Expand All @@ -1629,12 +1619,17 @@ function buildHeaderParameter(
});
return paramMap;
}

// paramMap is in the format '"key": valueExpr', extract the value expression
// to use as the condition accessor instead of recomputing it.
const valueExpr = paramMap.substring(paramMap.indexOf(": ") + 2);

const conditions = [];
if (effectiveOptional) {
conditions.push(`${optionalParamName}?.${paramName} !== undefined`);
conditions.push(`${valueExpr} !== undefined`);
}
if (isTypeNullable(param.type) === true) {
conditions.push(`${optionalParamName}?.${paramName} !== null`);
conditions.push(`${valueExpr} !== null`);
}
return conditions.length > 0
? `...(${conditions.join(" && ")} ? {${paramMap}} : {})`
Expand Down Expand Up @@ -1795,13 +1790,21 @@ export function getParameterMap(
);
}

const methodParamExpr = getMethodParamExpr(param);

// if the parameter or property is optional, we don't need to handle the default value
if (isOptional(param)) {
return getOptional(context, param, optionalParamName, serializedName);
return getOptional(
context,
param,
optionalParamName,
serializedName,
methodParamExpr
);
}

if (isRequired(param)) {
return getRequired(context, param, serializedName);
return getRequired(context, param, serializedName, methodParamExpr);
}

reportDiagnostic(context.program, {
Expand Down Expand Up @@ -1895,9 +1898,12 @@ function isRequired(param: SdkHttpParameter) {
function getRequired(
context: SdkContext,
param: SdkHttpParameter,
serializedName: string
serializedName: string,
methodParamExpr?: string
) {
const clientValue = `${param.onClient ? "context." : ""}${param.name}`;
const clientValue = param.onClient
? `context.${param.name}`
: (methodParamExpr ?? param.name);
if (param.type.kind === "model") {
const propertiesStr = getRequestModelMapping(
context,
Expand Down Expand Up @@ -1936,9 +1942,22 @@ function getOptional(
context: SdkContext,
param: SdkHttpParameter,
optionalParamName: string,
serializedName: string
serializedName: string,
methodParamExpr?: string
) {
const paramName = `${param.onClient ? "context." : `${optionalParamName}?.`}${param.name}`;
let paramName: string;
if (param.onClient) {
paramName = `context.${param.name}`;
} else if (methodParamExpr) {
const methodParam = param.methodParameterSegments[0]?.[0];
if (methodParam?.optional) {
paramName = `${optionalParamName}?.${methodParamExpr}`;
} else {
paramName = methodParamExpr;
}
} else {
paramName = `${optionalParamName}?.${param.name}`;
}

// Apply client default value if present and type matches
const defaultSuffix =
Expand Down Expand Up @@ -2010,7 +2029,7 @@ function getPathParameters(
const methodParam = param.methodParameterSegments[0]?.[0];
if (methodParam) {
pathParams.push(
`"${param.serializedName}": ${getPathParamExpr(methodParam, getDefaultValue(param) as string, optionalParamName)}`
`"${param.serializedName}": ${getPathParamExpr(param, getDefaultValue(param) as string, optionalParamName)}`
);
}
}
Expand Down Expand Up @@ -2075,19 +2094,53 @@ function escapeUriTemplateParamName(name: string) {
});
}

/** Builds a property accessor expression from the param's methodParameterSegments. */
function getMethodParamExpr(param: SdkHttpParameter): string | undefined {
const segments = param.methodParameterSegments;
if (segments.length === 0) {
return undefined;
}
const path = segments[0];
if (!path || path.length < 1) {
return undefined;
}

const parts: string[] = [];
for (let i = 0; i < path.length; i++) {
const segment = path[i]!;
if (i === 0) {
parts.push(segment.name);
} else {
const needsOptionalChain = path[i - 1]!.optional;
parts.push(`${needsOptionalChain ? "?." : "."}${segment.name}`);
}
}
return parts.join("");
}

function getPathParamExpr(
param: SdkMethodParameter | SdkModelPropertyType,
param: SdkHttpParameter,
defaultValue?: string,
optionalParamName: string = "options"
) {
if (isConstant(param.type)) {
return getConstantValue(param.type);
}
const paramName = param.onClient
? `context.${param.name}`
: param.optional
? `${optionalParamName}["${param.name}"]`
: param.name;
const methodParamExpr = getMethodParamExpr(param);
let paramName: string;
if (param.onClient) {
paramName = `context.${param.name}`;
} else if (methodParamExpr) {
// Only prefix with optionalParamName when the method param itself is optional.
const methodParam = param.methodParameterSegments[0]?.[0];
if (param.optional && methodParam?.optional) {
paramName = `${optionalParamName}?.${methodParamExpr}`;
} else {
paramName = methodParamExpr;
}
} else {
paramName = param.name;
}
return defaultValue
? typeof defaultValue === "string"
? `${paramName} ?? "${defaultValue}"`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2469,7 +2469,9 @@ export function bSerializer(item: B): any {
/* eslint-disable @typescript-eslint/naming-convention */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/** model interface A */
export interface A extends B {}
export interface A extends B {
name: string;
}
```

## Model function aSerializer
Expand Down
Loading
Loading