New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More

@typespec/http

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@typespec/http - npm Package Compare versions

Comparing version

to
0.59.0-dev.9

import type { DecoratorContext, Interface, ModelProperty, Namespace, Operation, Type } from "@typespec/compiler";
export interface QueryOptions {
readonly name?: string;
readonly explode?: boolean;
readonly format?: "multi" | "csv" | "ssv" | "tsv" | "simple" | "form" | "pipes";
}
export interface PathOptions {
readonly name?: string;
readonly explode?: boolean;
readonly style?: "simple" | "label" | "matrix" | "fragment" | "path";
readonly allowReserved?: boolean;
}
/**

@@ -55,10 +66,10 @@ * Specify the status code for this response. Property type must be a status code integer or a union of status code integer.

* op read(@query select: string, @query("order-by") orderBy: string): void;
* op list(@query({name: "id", format: "multi"}) ids: string[]): void;
* op list(@query(#{name: "id", explode: true}) ids: string[]): void;
* ```
*/
export type QueryDecorator = (context: DecoratorContext, target: ModelProperty, queryNameOrOptions?: Type) => void;
export type QueryDecorator = (context: DecoratorContext, target: ModelProperty, queryNameOrOptions?: string | QueryOptions) => void;
/**
* Explicitly specify that this property is to be interpolated as a path parameter.
*
* @param paramName Optional name of the parameter in the url template.
* @param paramNameOrOptions Optional name of the parameter in the uri template or options.
* @example

@@ -70,3 +81,3 @@ * ```typespec

*/
export type PathDecorator = (context: DecoratorContext, target: ModelProperty, paramName?: string) => void;
export type PathDecorator = (context: DecoratorContext, target: ModelProperty, paramNameOrOptions?: string | PathOptions) => void;
/**

@@ -214,17 +225,22 @@ * Specify that the body resolution should be resolved from that property.

/**
* Defines the relative route URI for the target operation
* Defines the relative route URI template for the target operation as defined by [RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.3)
*
* The first argument should be a URI fragment that may contain one or more path parameter fields.
* If the namespace or interface that contains the operation is also marked with a `@route` decorator,
* it will be used as a prefix to the route URI of the operation.
*
* `@route` can only be applied to operations, namespaces, and interfaces.
*
* @param path Relative route path. Cannot include query parameters.
* @param options Set of parameters used to configure the route. Supports `{shared: true}` which indicates that the route may be shared by several operations.
* @example
* @param uriTemplate Uri template for this operation.
* @param options _DEPRECATED_ Set of parameters used to configure the route. Supports `{shared: true}` which indicates that the route may be shared by several operations.
* @example Simple path parameter
*
* ```typespec
* @route("/widgets")
* op getWidget(@path id: string): Widget;
* @route("/widgets/{id}") op getWidget(@path id: string): Widget;
* ```
* @example Reserved characters
* ```typespec
* @route("/files{+path}") op getFile(@path path: string): bytes;
* ```
* @example Query parameter
* ```typespec
* @route("/files") op list(select?: string, filter?: string): Files[];
* @route("/files{?select,filter}") op listFullUriTemplate(select?: string, filter?: string): Files[];
* ```
*/

@@ -231,0 +247,0 @@ export type RouteDecorator = (context: DecoratorContext, target: Namespace | Interface | Operation, path: string, options?: Type) => void;

@@ -59,32 +59,15 @@ import { SyntaxKind, createDiagnosticCollector, getDoc, ignoreDiagnostics, isArrayModelType, reportDeprecated, typespecTypeToJson, validateDecoratorTarget, validateDecoratorUniqueOnNode, } from "@typespec/compiler";

export const $query = (context, entity, queryNameOrOptions) => {
const paramName = typeof queryNameOrOptions === "string"
? queryNameOrOptions
: (queryNameOrOptions?.name ?? entity.name);
const userOptions = typeof queryNameOrOptions === "object" ? queryNameOrOptions : {};
if (userOptions.format) {
reportDeprecated(context.program, "The `format` option of `@query` decorator is deprecated. Use `explode: true` instead of `form` and `multi`. `csv` or `simple` is the default now.", entity);
}
const options = {
type: "query",
name: entity.name,
explode: userOptions.explode ?? (userOptions.format === "multi" || userOptions.format === "form"),
format: userOptions.format ?? (userOptions.explode ? "multi" : "csv"),
name: paramName,
};
if (queryNameOrOptions) {
if (queryNameOrOptions.kind === "String") {
options.name = queryNameOrOptions.value;
}
else if (queryNameOrOptions.kind === "Model") {
const name = queryNameOrOptions.properties.get("name")?.type;
if (name?.kind === "String") {
options.name = name.value;
}
const format = queryNameOrOptions.properties.get("format")?.type;
if (format?.kind === "String") {
options.format = format.value; // That value should have already been validated by the TypeSpec dec
}
}
else {
return;
}
}
if (entity.type.kind === "Model" &&
isArrayModelType(context.program, entity.type) &&
options.format === undefined) {
reportDiagnostic(context.program, {
code: "query-format-required",
target: context.decoratorTarget,
});
}
context.program.stateMap(HttpStateKeys.query).set(entity, options);

@@ -101,6 +84,13 @@ };

}
export const $path = (context, entity, paramName) => {
export const $path = (context, entity, paramNameOrOptions) => {
const paramName = typeof paramNameOrOptions === "string"
? paramNameOrOptions
: (paramNameOrOptions?.name ?? entity.name);
const userOptions = typeof paramNameOrOptions === "object" ? paramNameOrOptions : {};
const options = {
type: "path",
name: paramName ?? entity.name,
explode: userOptions.explode ?? false,
allowReserved: userOptions.allowReserved ?? false,
style: userOptions.style ?? "simple",
name: paramName,
};

@@ -107,0 +97,0 @@ context.program.stateMap(HttpStateKeys.path).set(entity, options);

@@ -42,3 +42,3 @@ import { DiagnosticResult, Type, type ModelProperty, type Program } from "@typespec/compiler";

export interface GetHttpPropertyOptions {
isImplicitPathParam?: (param: ModelProperty) => boolean;
implicitParameter?: (param: ModelProperty) => PathParameterOptions | QueryParameterOptions | undefined;
}

@@ -45,0 +45,0 @@ /**

@@ -23,10 +23,46 @@ import { compilerAssert, createDiagnosticCollector, walkPropertiesInherited, } from "@typespec/compiler";

const defined = Object.entries(annotations).filter((x) => !!x[1]);
const implicit = options.implicitParameter?.(property);
if (implicit && defined.length > 0) {
if (implicit.type === "path" && annotations.path) {
if (annotations.path.explode ||
annotations.path.style !== "simple" ||
annotations.path.allowReserved) {
diagnostics.push(createDiagnostic({
code: "use-uri-template",
format: {
param: property.name,
},
target: property,
}));
}
}
else if (implicit.type === "query" && annotations.query) {
if (annotations.query.explode) {
diagnostics.push(createDiagnostic({
code: "use-uri-template",
format: {
param: property.name,
},
target: property,
}));
}
}
else {
diagnostics.push(createDiagnostic({
code: "incompatible-uri-param",
format: {
param: property.name,
uriKind: implicit.type,
annotationKind: defined[0][0],
},
target: property,
}));
}
}
if (defined.length === 0) {
if (options.isImplicitPathParam && options.isImplicitPathParam(property)) {
if (implicit) {
return createResult({
kind: "path",
options: {
name: property.name,
type: "path",
},
kind: implicit.type,
options: implicit,
property,
});

@@ -33,0 +69,0 @@ }

@@ -10,3 +10,3 @@ export { $lib } from "./lib.js";

export * from "./operations.js";
export * from "./parameters.js";
export { getOperationParameters } from "./parameters.js";
export { HttpPart, getHttpFileModel, getHttpPart, isHttpFile, isOrExtendsHttpFile, } from "./private.decorators.js";

@@ -13,0 +13,0 @@ export * from "./responses.js";

@@ -8,3 +8,3 @@ export { $lib } from "./lib.js";

export * from "./operations.js";
export * from "./parameters.js";
export { getOperationParameters } from "./parameters.js";
export { getHttpFileModel, getHttpPart, isHttpFile, isOrExtendsHttpFile, } from "./private.decorators.js";

@@ -11,0 +11,0 @@ export * from "./responses.js";

@@ -5,5 +5,11 @@ export declare const $lib: import("@typespec/compiler").TypeSpecLibrary<{

};
"missing-path-param": {
"missing-uri-param": {
readonly default: import("@typespec/compiler").CallableMessage<["param"]>;
};
"incompatible-uri-param": {
readonly default: import("@typespec/compiler").CallableMessage<["param", "uriKind", "annotationKind"]>;
};
"use-uri-template": {
readonly default: import("@typespec/compiler").CallableMessage<["param"]>;
};
"optional-path-param": {

@@ -78,13 +84,16 @@ readonly default: import("@typespec/compiler").CallableMessage<["paramName"]>;

};
"query-format-required": {
readonly default: "A format must be specified for @query when type is an array. e.g. @query({format: \"multi\"})";
};
}, Record<string, any>, "path" | "file" | "query" | "authentication" | "header" | "body" | "bodyRoot" | "bodyIgnore" | "multipartBody" | "statusCode" | "verbs" | "servers" | "includeInapplicableMetadataInPayload" | "externalInterfaces" | "routeProducer" | "routes" | "sharedRoutes" | "routeOptions" | "httpPart">;
export declare const reportDiagnostic: <C extends "http-verb-duplicate" | "missing-path-param" | "optional-path-param" | "missing-server-param" | "duplicate-body" | "duplicate-route-decorator" | "operation-param-duplicate-type" | "duplicate-operation" | "multiple-status-codes" | "status-code-invalid" | "content-type-string" | "content-type-ignored" | "metadata-ignored" | "no-service-found" | "invalid-type-for-auth" | "shared-inconsistency" | "write-visibility-not-supported" | "multipart-invalid-content-type" | "multipart-model" | "multipart-part" | "multipart-nested" | "http-file-extra-property" | "formdata-no-part-name" | "header-format-required" | "query-format-required", M extends keyof {
export declare const reportDiagnostic: <C extends "http-verb-duplicate" | "missing-uri-param" | "incompatible-uri-param" | "use-uri-template" | "optional-path-param" | "missing-server-param" | "duplicate-body" | "duplicate-route-decorator" | "operation-param-duplicate-type" | "duplicate-operation" | "multiple-status-codes" | "status-code-invalid" | "content-type-string" | "content-type-ignored" | "metadata-ignored" | "no-service-found" | "invalid-type-for-auth" | "shared-inconsistency" | "write-visibility-not-supported" | "multipart-invalid-content-type" | "multipart-model" | "multipart-part" | "multipart-nested" | "http-file-extra-property" | "formdata-no-part-name" | "header-format-required", M extends keyof {
"http-verb-duplicate": {
readonly default: import("@typespec/compiler").CallableMessage<["entityName"]>;
};
"missing-path-param": {
"missing-uri-param": {
readonly default: import("@typespec/compiler").CallableMessage<["param"]>;
};
"incompatible-uri-param": {
readonly default: import("@typespec/compiler").CallableMessage<["param", "uriKind", "annotationKind"]>;
};
"use-uri-template": {
readonly default: import("@typespec/compiler").CallableMessage<["param"]>;
};
"optional-path-param": {

@@ -159,5 +168,2 @@ readonly default: import("@typespec/compiler").CallableMessage<["paramName"]>;

};
"query-format-required": {
readonly default: "A format must be specified for @query when type is an array. e.g. @query({format: \"multi\"})";
};
}[C]>(program: import("@typespec/compiler").Program, diag: import("@typespec/compiler").DiagnosticReport<{

@@ -167,5 +173,11 @@ "http-verb-duplicate": {

};
"missing-path-param": {
"missing-uri-param": {
readonly default: import("@typespec/compiler").CallableMessage<["param"]>;
};
"incompatible-uri-param": {
readonly default: import("@typespec/compiler").CallableMessage<["param", "uriKind", "annotationKind"]>;
};
"use-uri-template": {
readonly default: import("@typespec/compiler").CallableMessage<["param"]>;
};
"optional-path-param": {

@@ -240,12 +252,15 @@ readonly default: import("@typespec/compiler").CallableMessage<["paramName"]>;

};
"query-format-required": {
readonly default: "A format must be specified for @query when type is an array. e.g. @query({format: \"multi\"})";
};
}, C, M>) => void, createDiagnostic: <C extends "http-verb-duplicate" | "missing-path-param" | "optional-path-param" | "missing-server-param" | "duplicate-body" | "duplicate-route-decorator" | "operation-param-duplicate-type" | "duplicate-operation" | "multiple-status-codes" | "status-code-invalid" | "content-type-string" | "content-type-ignored" | "metadata-ignored" | "no-service-found" | "invalid-type-for-auth" | "shared-inconsistency" | "write-visibility-not-supported" | "multipart-invalid-content-type" | "multipart-model" | "multipart-part" | "multipart-nested" | "http-file-extra-property" | "formdata-no-part-name" | "header-format-required" | "query-format-required", M extends keyof {
}, C, M>) => void, createDiagnostic: <C extends "http-verb-duplicate" | "missing-uri-param" | "incompatible-uri-param" | "use-uri-template" | "optional-path-param" | "missing-server-param" | "duplicate-body" | "duplicate-route-decorator" | "operation-param-duplicate-type" | "duplicate-operation" | "multiple-status-codes" | "status-code-invalid" | "content-type-string" | "content-type-ignored" | "metadata-ignored" | "no-service-found" | "invalid-type-for-auth" | "shared-inconsistency" | "write-visibility-not-supported" | "multipart-invalid-content-type" | "multipart-model" | "multipart-part" | "multipart-nested" | "http-file-extra-property" | "formdata-no-part-name" | "header-format-required", M extends keyof {
"http-verb-duplicate": {
readonly default: import("@typespec/compiler").CallableMessage<["entityName"]>;
};
"missing-path-param": {
"missing-uri-param": {
readonly default: import("@typespec/compiler").CallableMessage<["param"]>;
};
"incompatible-uri-param": {
readonly default: import("@typespec/compiler").CallableMessage<["param", "uriKind", "annotationKind"]>;
};
"use-uri-template": {
readonly default: import("@typespec/compiler").CallableMessage<["param"]>;
};
"optional-path-param": {

@@ -320,5 +335,2 @@ readonly default: import("@typespec/compiler").CallableMessage<["paramName"]>;

};
"query-format-required": {
readonly default: "A format must be specified for @query when type is an array. e.g. @query({format: \"multi\"})";
};
}[C]>(diag: import("@typespec/compiler").DiagnosticReport<{

@@ -328,5 +340,11 @@ "http-verb-duplicate": {

};
"missing-path-param": {
"missing-uri-param": {
readonly default: import("@typespec/compiler").CallableMessage<["param"]>;
};
"incompatible-uri-param": {
readonly default: import("@typespec/compiler").CallableMessage<["param", "uriKind", "annotationKind"]>;
};
"use-uri-template": {
readonly default: import("@typespec/compiler").CallableMessage<["param"]>;
};
"optional-path-param": {

@@ -401,6 +419,3 @@ readonly default: import("@typespec/compiler").CallableMessage<["paramName"]>;

};
"query-format-required": {
readonly default: "A format must be specified for @query when type is an array. e.g. @query({format: \"multi\"})";
};
}, C, M>) => import("@typespec/compiler").Diagnostic, HttpStateKeys: Record<"path" | "file" | "query" | "authentication" | "header" | "body" | "bodyRoot" | "bodyIgnore" | "multipartBody" | "statusCode" | "verbs" | "servers" | "includeInapplicableMetadataInPayload" | "externalInterfaces" | "routeProducer" | "routes" | "sharedRoutes" | "routeOptions" | "httpPart", symbol>;
//# sourceMappingURL=lib.d.ts.map

@@ -11,3 +11,3 @@ import { createTypeSpecLibrary, paramMessage } from "@typespec/compiler";

},
"missing-path-param": {
"missing-uri-param": {
severity: "error",

@@ -18,2 +18,14 @@ messages: {

},
"incompatible-uri-param": {
severity: "error",
messages: {
default: paramMessage `Parameter '${"param"}' is defined in the uri as a ${"uriKind"} but is annotated as a ${"annotationKind"}.`,
},
},
"use-uri-template": {
severity: "error",
messages: {
default: paramMessage `Parameter '${"param"}' is already defined in the uri template. Explode, style and allowReserved property must be defined in the uri template as described by RFC 6570.`,
},
},
"optional-path-param": {

@@ -154,8 +166,2 @@ severity: "error",

},
"query-format-required": {
severity: "error",
messages: {
default: `A format must be specified for @query when type is an array. e.g. @query({format: "multi"})`,
},
},
},

@@ -162,0 +168,0 @@ state: {

@@ -140,3 +140,4 @@ import { $visibility, createDiagnosticCollector, getOverloadedOperation, getOverloads, getVisibility, listOperationsIn, listServices, navigateProgram, SyntaxKind, } from "@typespec/compiler";

path: route.path,
pathSegments: route.pathSegments,
uriTemplate: route.uriTemplate,
pathSegments: [],
verb: route.parameters.verb,

@@ -143,0 +144,0 @@ container: operation.interface ?? operation.namespace ?? program.getGlobalNamespaceType(),

import { Diagnostic, Operation, Program } from "@typespec/compiler";
import { HttpOperation, HttpOperationParameters, OperationParameterOptions } from "./types.js";
export declare function getOperationParameters(program: Program, operation: Operation, overloadBase?: HttpOperation, knownPathParamNames?: string[], options?: OperationParameterOptions): [HttpOperationParameters, readonly Diagnostic[]];
export declare function getOperationParameters(program: Program, operation: Operation, partialUriTemplate: string, overloadBase?: HttpOperation, options?: OperationParameterOptions): [HttpOperationParameters, readonly Diagnostic[]];
//# sourceMappingURL=parameters.d.ts.map

@@ -6,3 +6,4 @@ import { createDiagnosticCollector, } from "@typespec/compiler";

import { resolveHttpPayload } from "./payload.js";
export function getOperationParameters(program, operation, overloadBase, knownPathParamNames = [], options = {}) {
import { parseUriTemplate } from "./uri-template.js";
export function getOperationParameters(program, operation, partialUriTemplate, overloadBase, options = {}) {
const verb = (options?.verbSelector && options.verbSelector(program, operation)) ??

@@ -12,3 +13,3 @@ getOperationVerb(program, operation) ??

if (verb) {
return getOperationParametersForVerb(program, operation, verb, knownPathParamNames);
return getOperationParametersForVerb(program, operation, verb, partialUriTemplate);
}

@@ -19,17 +20,52 @@ // If no verb is explicitly specified, it is POST if there is a body and

// body if the verb is GET. In that rare case, GET is chosen arbitrarily.
const post = getOperationParametersForVerb(program, operation, "post", knownPathParamNames);
const post = getOperationParametersForVerb(program, operation, "post", partialUriTemplate);
return post[0].body
? post
: getOperationParametersForVerb(program, operation, "get", knownPathParamNames);
: getOperationParametersForVerb(program, operation, "get", partialUriTemplate);
}
function getOperationParametersForVerb(program, operation, verb, knownPathParamNames) {
const operatorToStyle = {
";": "matrix",
"#": "fragment",
".": "label",
"/": "path",
};
function getOperationParametersForVerb(program, operation, verb, partialUriTemplate) {
const diagnostics = createDiagnosticCollector();
const visibility = resolveRequestVisibility(program, operation, verb);
function isImplicitPathParam(param) {
const isTopLevel = param.model === operation.parameters;
return isTopLevel && knownPathParamNames.includes(param.name);
}
const parsedUriTemplate = parseUriTemplate(partialUriTemplate);
const parameters = [];
const { body: resolvedBody, metadata } = diagnostics.pipe(resolveHttpPayload(program, operation.parameters, visibility, "request", {
isImplicitPathParam,
implicitParameter: (param) => {
const isTopLevel = param.model === operation.parameters;
const uriParam = isTopLevel && parsedUriTemplate.parameters.find((x) => x.name === param.name);
if (!uriParam) {
return undefined;
}
const explode = uriParam.modifier?.type === "explode";
if (uriParam.operator === "?" || uriParam.operator === "&") {
return {
type: "query",
name: uriParam.name,
explode,
};
}
else if (uriParam.operator === "+") {
return {
type: "path",
name: uriParam.name,
explode,
allowReserved: true,
style: "simple",
};
}
else {
return {
type: "path",
name: uriParam.name,
explode,
allowReserved: false,
style: (uriParam.operator && operatorToStyle[uriParam.operator]) ?? "simple",
};
}
},
}));

@@ -36,0 +72,0 @@ for (const item of metadata) {

import { DecoratorContext, DiagnosticResult, Interface, Namespace, Operation, Program, Type } from "@typespec/compiler";
import { HttpOperation, HttpOperationParameters, RouteOptions, RoutePath, RouteProducer, RouteProducerResult, RouteResolutionOptions } from "./types.js";
export declare function joinPathSegments(rest: string[]): string;
export declare function resolvePathAndParameters(program: Program, operation: Operation, overloadBase: HttpOperation | undefined, options: RouteResolutionOptions): DiagnosticResult<{
readonly uriTemplate: string;
path: string;
pathSegments: string[];
parameters: HttpOperationParameters;

@@ -7,0 +8,0 @@ }>;

import { createDiagnosticCollector, validateDecoratorTarget, } from "@typespec/compiler";
import { HttpStateKeys, createDiagnostic, reportDiagnostic } from "./lib.js";
import { createDiagnostic, HttpStateKeys, reportDiagnostic } from "./lib.js";
import { getOperationParameters } from "./parameters.js";
import { extractParamsFromPath } from "./utils.js";
import { parseUriTemplate } from "./uri-template.js";
// The set of allowed segment separator characters

@@ -17,3 +17,3 @@ const AllowedSegmentSeparators = ["/", ":"];

}
function joinPathSegments(rest) {
export function joinPathSegments(rest) {
let current = "";

@@ -33,13 +33,15 @@ for (const [index, segment] of rest.entries()) {

const diagnostics = createDiagnosticCollector();
const { segments, parameters } = diagnostics.pipe(getRouteSegments(program, operation, overloadBase, options));
const { uriTemplate, parameters } = diagnostics.pipe(getUriTemplateAndParameters(program, operation, overloadBase, options));
const parsedUriTemplate = parseUriTemplate(uriTemplate);
// Pull out path parameters to verify what's in the path string
const paramByName = new Set(parameters.parameters.filter(({ type }) => type === "path").map((x) => x.name));
const paramByName = new Set(parameters.parameters
.filter(({ type }) => type === "path" || type === "query")
.map((x) => x.name));
// Ensure that all of the parameters defined in the route are accounted for in
// the operation parameters
const routeParams = segments.flatMap(extractParamsFromPath);
for (const routeParam of routeParams) {
if (!paramByName.has(routeParam)) {
for (const routeParam of parsedUriTemplate.parameters) {
if (!paramByName.has(routeParam.name)) {
diagnostics.add(createDiagnostic({
code: "missing-path-param",
format: { param: routeParam },
code: "missing-uri-param",
format: { param: routeParam.name },
target: operation,

@@ -49,8 +51,21 @@ }));

}
const path = produceLegacyPathFromUriTemplate(parsedUriTemplate);
return diagnostics.wrap({
path: buildPath(segments),
pathSegments: segments,
uriTemplate,
path,
parameters,
});
}
function produceLegacyPathFromUriTemplate(uriTemplate) {
let result = "";
for (const segment of uriTemplate.segments ?? []) {
if (typeof segment === "string") {
result += segment;
}
else if (segment.operator !== "?" && segment.operator !== "&") {
result += `{${segment.name}}`;
}
}
return result;
}
function collectSegmentsAndOptions(program, source) {

@@ -64,11 +79,13 @@ if (source === undefined)

}
function getRouteSegments(program, operation, overloadBase, options) {
const diagnostics = createDiagnosticCollector();
function getUriTemplateAndParameters(program, operation, overloadBase, options) {
const [parentSegments, parentOptions] = collectSegmentsAndOptions(program, operation.interface ?? operation.namespace);
const routeProducer = getRouteProducer(program, operation) ?? DefaultRouteProducer;
const result = diagnostics.pipe(routeProducer(program, operation, parentSegments, overloadBase, {
const [result, diagnostics] = routeProducer(program, operation, parentSegments, overloadBase, {
...parentOptions,
...options,
}));
return diagnostics.wrap(result);
});
return [
{ uriTemplate: buildPath([result.uriTemplate]), parameters: result.parameters },
diagnostics,
];
}

@@ -92,22 +109,41 @@ /**

const routePath = getRoutePath(program, operation)?.path;
const segments = !routePath && overloadBase
? overloadBase.pathSegments
: [...parentSegments, ...(routePath ? [routePath] : [])];
const routeParams = segments.flatMap(extractParamsFromPath);
const parameters = diagnostics.pipe(getOperationParameters(program, operation, overloadBase, routeParams, options.paramOptions));
const uriTemplate = !routePath && overloadBase
? overloadBase.uriTemplate
: joinPathSegments([...parentSegments, ...(routePath ? [routePath] : [])]);
const parsedUriTemplate = parseUriTemplate(uriTemplate);
const parameters = diagnostics.pipe(getOperationParameters(program, operation, uriTemplate, overloadBase, options.paramOptions));
// Pull out path parameters to verify what's in the path string
const unreferencedPathParamNames = new Set(parameters.parameters.filter(({ type }) => type === "path").map((x) => x.name));
const unreferencedPathParamNames = new Map(parameters.parameters
.filter(({ type }) => type === "path" || type === "query")
.map((x) => [x.name, x]));
// Compile the list of all route params that aren't represented in the route
for (const routeParam of routeParams) {
unreferencedPathParamNames.delete(routeParam);
for (const uriParam of parsedUriTemplate.parameters) {
unreferencedPathParamNames.delete(uriParam.name);
}
// Add any remaining declared path params
for (const paramName of unreferencedPathParamNames) {
segments.push(`{${paramName}}`);
}
const resolvedUriTemplate = addOperationTemplateToUriTemplate(uriTemplate, [
...unreferencedPathParamNames.values(),
]);
return diagnostics.wrap({
segments,
uriTemplate: resolvedUriTemplate,
parameters,
});
}
const styleToOperator = {
matrix: ";",
label: ".",
simple: "",
path: "/",
fragment: "#",
};
function addOperationTemplateToUriTemplate(uriTemplate, params) {
const pathParams = params
.filter((x) => x.type === "path")
.map((param) => {
const operator = param.allowReserved ? "+" : styleToOperator[param.style];
return `{${operator}${param.name}${param.explode ? "*" : ""}}`;
});
const queryParams = params.filter((x) => x.type === "query");
const pathPart = joinPathSegments([uriTemplate, ...pathParams]);
return (pathPart + (queryParams.length > 0 ? `{?${queryParams.map((x) => x.name).join(",")}}` : ""));
}
export function setRouteProducer(program, operation, routeProducer) {

@@ -114,0 +150,0 @@ program.stateMap(HttpStateKeys.routeProducer).set(operation, routeProducer);

import { DiagnosticResult, Interface, ListOperationOptions, Model, ModelProperty, Namespace, Operation, Program, Tuple, Type } from "@typespec/compiler";
import { PathOptions, QueryOptions } from "../generated-defs/TypeSpec.Http.js";
import { HeaderProperty, HttpProperty } from "./http-property.js";

@@ -213,3 +214,3 @@ /**

export interface RouteProducerResult {
segments: string[];
uriTemplate: string;
parameters: HttpOperationParameters;

@@ -227,18 +228,22 @@ }

}
export interface QueryParameterOptions {
export interface QueryParameterOptions extends Required<Omit<QueryOptions, "format">> {
type: "query";
name: string;
/**
* The string format of the array. "csv" and "simple" are used interchangeably, as are
* "multi" and "form".
* @deprecated use explode and `@encode` decorator instead.
*/
format?: "multi" | "csv" | "ssv" | "tsv" | "pipes" | "simple" | "form";
format?: "csv" | "multi" | "ssv" | "tsv" | "pipes" | "simple" | "form";
}
export interface PathParameterOptions {
export interface PathParameterOptions extends Required<PathOptions> {
type: "path";
name: string;
}
export type HttpOperationParameter = (HeaderFieldOptions | QueryParameterOptions | PathParameterOptions) & {
export type HttpOperationParameter = HttpOperationHeaderParameter | HttpOperationQueryParameter | HttpOperationPathParameter;
export type HttpOperationHeaderParameter = HeaderFieldOptions & {
param: ModelProperty;
};
export type HttpOperationQueryParameter = QueryParameterOptions & {
param: ModelProperty;
};
export type HttpOperationPathParameter = PathParameterOptions & {
param: ModelProperty;
};
/**

@@ -269,7 +274,15 @@ * @deprecated use {@link HttpOperationBody}

/**
* Route path
* The fully resolved uri template as defined by http://tools.ietf.org/html/rfc6570.
* @example "/foo/{bar}/baz{?qux}"
* @example "/foo/{+path}"
*/
readonly uriTemplate: string;
/**
* Route path.
* Not recommended use {@link uriTemplate} instead. This will not work for complex cases like not-escaping reserved chars.
*/
path: string;
/**
* Path segments
* @deprecated use {@link uriTemplate} instead
*/

@@ -276,0 +289,0 @@ pathSegments: string[];

{
"name": "@typespec/http",
"version": "0.59.0-dev.6",
"version": "0.59.0-dev.9",
"author": "Microsoft Corporation",

@@ -5,0 +5,0 @@ "description": "TypeSpec HTTP protocol binding",

@@ -329,3 +329,3 @@ # @typespec/http

```typespec
@TypeSpec.Http.path(paramName?: valueof string)
@TypeSpec.Http.path(paramNameOrOptions?: valueof string | TypeSpec.Http.PathOptions)
```

@@ -339,5 +339,5 @@

| Name | Type | Description |
| --------- | ---------------- | --------------------------------------------------- |
| paramName | `valueof string` | Optional name of the parameter in the url template. |
| Name | Type | Description |
| ------------------ | --------------------------------------------- | -------------------------------------------------------------- |
| paramNameOrOptions | `valueof string \| TypeSpec.Http.PathOptions` | Optional name of the parameter in the uri template or options. |

@@ -400,3 +400,3 @@ ##### Examples

```typespec
@TypeSpec.Http.query(queryNameOrOptions?: string | TypeSpec.Http.QueryOptions)
@TypeSpec.Http.query(queryNameOrOptions?: valueof string | TypeSpec.Http.QueryOptions)
```

@@ -410,5 +410,5 @@

| Name | Type | Description |
| ------------------ | -------------------------------------- | ------------------------------------------------------------------------------- |
| queryNameOrOptions | `string \| TypeSpec.Http.QueryOptions` | Optional name of the query when included in the url or query parameter options. |
| Name | Type | Description |
| ------------------ | ---------------------------------------------- | ------------------------------------------------------------------------------- |
| queryNameOrOptions | `valueof string \| TypeSpec.Http.QueryOptions` | Optional name of the query when included in the url or query parameter options. |

@@ -419,9 +419,3 @@ ##### Examples

op read(@query select: string, @query("order-by") orderBy: string): void;
op list(
@query({
name: "id",
format: "multi",
})
ids: string[],
): void;
op list(@query(#{ name: "id", explode: true }) ids: string[]): void;
```

@@ -431,8 +425,4 @@

Defines the relative route URI for the target operation
Defines the relative route URI template for the target operation as defined by [RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.3)
The first argument should be a URI fragment that may contain one or more path parameter fields.
If the namespace or interface that contains the operation is also marked with a `@route` decorator,
it will be used as a prefix to the route URI of the operation.
`@route` can only be applied to operations, namespaces, and interfaces.

@@ -450,14 +440,28 @@

| Name | Type | Description |
| ------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| path | `valueof string` | Relative route path. Cannot include query parameters. |
| options | `{...}` | Set of parameters used to configure the route. Supports `{shared: true}` which indicates that the route may be shared by several operations. |
| Name | Type | Description |
| ------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| path | `valueof string` | |
| options | `{...}` | _DEPRECATED_ Set of parameters used to configure the route. Supports `{shared: true}` which indicates that the route may be shared by several operations. |
##### Examples
###### Simple path parameter
```typespec
@route("/widgets")
op getWidget(@path id: string): Widget;
@route("/widgets/{id}") op getWidget(@path id: string): Widget;
```
###### Reserved characters
```typespec
@route("/files{+path}") op getFile(@path path: string): bytes;
```
###### Query parameter
```typespec
@route("/files") op list(select?: string, filter?: string): Files[];
@route("/files{?select,filter}") op listFullUriTemplate(select?: string, filter?: string): Files[];
```
#### `@server`

@@ -464,0 +468,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet