microsoft/typespec

Public

mirrored from https://github.com/microsoft/typespecAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
3040a83d6de0cc6876163b48ec9be61eefa3ebdd

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

packages/http/src/parameters.ts

216lines · modecode

1import {
2 createDiagnosticCollector,
3 Diagnostic,
4 filterModelProperties,
5 ModelProperty,
6 Operation,
7 Program,
8 Type,
9} from "@typespec/compiler";
10import { getContentTypes, isContentTypeHeader } from "./content-types.js";
11import {
12 getHeaderFieldOptions,
13 getOperationVerb,
14 getPathParamOptions,
15 getQueryParamOptions,
16 isBody,
17} from "./decorators.js";
18import { createDiagnostic } from "./lib.js";
19import { gatherMetadata, isMetadata, resolveRequestVisibility } from "./metadata.js";
20import {
21 HttpOperation,
22 HttpOperationParameter,
23 HttpOperationParameters,
24 HttpOperationRequestBody,
25 HttpVerb,
26 OperationParameterOptions,
27} from "./types.js";
28
29export function getOperationParameters(
30 program: Program,
31 operation: Operation,
32 overloadBase?: HttpOperation,
33 knownPathParamNames: string[] = [],
34 options: OperationParameterOptions = {}
35): [HttpOperationParameters, readonly Diagnostic[]] {
36 const verb =
37 (options?.verbSelector && options.verbSelector(program, operation)) ??
38 getOperationVerb(program, operation) ??
39 overloadBase?.verb;
40
41 if (verb) {
42 return getOperationParametersForVerb(program, operation, verb, knownPathParamNames);
43 }
44
45 // If no verb is explicitly specified, it is POST if there is a body and
46 // GET otherwise. Theoretically, it is possible to use @visibility
47 // strangely such that there is no body if the verb is POST and there is a
48 // body if the verb is GET. In that rare case, GET is chosen arbitrarily.
49 const post = getOperationParametersForVerb(program, operation, "post", knownPathParamNames);
50 return post[0].body
51 ? post
52 : getOperationParametersForVerb(program, operation, "get", knownPathParamNames);
53}
54
55function getOperationParametersForVerb(
56 program: Program,
57 operation: Operation,
58 verb: HttpVerb,
59 knownPathParamNames: string[]
60): [HttpOperationParameters, readonly Diagnostic[]] {
61 const diagnostics = createDiagnosticCollector();
62 const visibility = resolveRequestVisibility(program, operation, verb);
63 const rootPropertyMap = new Map<ModelProperty, ModelProperty>();
64 const metadata = gatherMetadata(
65 program,
66 diagnostics,
67 operation.parameters,
68 visibility,
69 (_, param) => isMetadata(program, param) || isImplicitPathParam(param),
70 rootPropertyMap
71 );
72
73 function isImplicitPathParam(param: ModelProperty) {
74 const isTopLevel = param.model === operation.parameters;
75 return isTopLevel && knownPathParamNames.includes(param.name);
76 }
77
78 const parameters: HttpOperationParameter[] = [];
79 let bodyType: Type | undefined;
80 let bodyParameter: ModelProperty | undefined;
81 let contentTypes: string[] | undefined;
82
83 for (const param of metadata) {
84 const queryOptions = getQueryParamOptions(program, param);
85 const pathOptions =
86 getPathParamOptions(program, param) ??
87 (isImplicitPathParam(param) && { type: "path", name: param.name });
88 const headerOptions = getHeaderFieldOptions(program, param);
89 const bodyParam = isBody(program, param);
90 const defined = [
91 ["query", queryOptions],
92 ["path", pathOptions],
93 ["header", headerOptions],
94 ["body", bodyParam],
95 ].filter((x) => !!x[1]);
96 if (defined.length >= 2) {
97 diagnostics.add(
98 createDiagnostic({
99 code: "operation-param-duplicate-type",
100 format: { paramName: param.name, types: defined.map((x) => x[0]).join(", ") },
101 target: param,
102 })
103 );
104 }
105
106 if (queryOptions) {
107 parameters.push({
108 ...queryOptions,
109 param,
110 });
111 } else if (pathOptions) {
112 if (param.optional) {
113 diagnostics.add(
114 createDiagnostic({
115 code: "optional-path-param",
116 format: { paramName: param.name },
117 target: operation,
118 })
119 );
120 }
121 parameters.push({
122 ...pathOptions,
123 param,
124 });
125 } else if (headerOptions) {
126 if (isContentTypeHeader(program, param)) {
127 contentTypes = diagnostics.pipe(getContentTypes(param));
128 }
129 parameters.push({
130 ...headerOptions,
131 param,
132 });
133 } else if (bodyParam) {
134 if (bodyType === undefined) {
135 bodyParameter = param;
136 bodyType = param.type;
137 } else {
138 diagnostics.add(createDiagnostic({ code: "duplicate-body", target: param }));
139 }
140 }
141 }
142
143 const bodyRoot = bodyParameter ? rootPropertyMap.get(bodyParameter) : undefined;
144 const unannotatedProperties = filterModelProperties(
145 program,
146 operation.parameters,
147 (p) => !metadata.has(p) && p !== bodyRoot
148 );
149
150 if (unannotatedProperties.properties.size > 0) {
151 if (bodyType === undefined) {
152 bodyType = unannotatedProperties;
153 } else {
154 diagnostics.add(
155 createDiagnostic({
156 code: "duplicate-body",
157 messageId: "bodyAndUnannotated",
158 target: operation,
159 })
160 );
161 }
162 }
163 const body = diagnostics.pipe(
164 computeHttpOperationBody(operation, bodyType, bodyParameter, contentTypes)
165 );
166
167 return diagnostics.wrap({
168 parameters,
169 verb,
170 body,
171 get bodyType() {
172 return body?.type;
173 },
174 get bodyParameter() {
175 return body?.parameter;
176 },
177 });
178}
179
180function computeHttpOperationBody(
181 operation: Operation,
182 bodyType: Type | undefined,
183 bodyProperty: ModelProperty | undefined,
184 contentTypes: string[] | undefined
185): [HttpOperationRequestBody | undefined, readonly Diagnostic[]] {
186 contentTypes ??= [];
187 const diagnostics: Diagnostic[] = [];
188 if (bodyType === undefined) {
189 if (contentTypes.length > 0) {
190 diagnostics.push(
191 createDiagnostic({
192 code: "content-type-ignored",
193 target: operation.parameters,
194 })
195 );
196 }
197 return [undefined, diagnostics];
198 }
199
200 if (contentTypes.includes("multipart/form-data") && bodyType.kind !== "Model") {
201 diagnostics.push(
202 createDiagnostic({
203 code: "multipart-model",
204 target: bodyProperty ?? operation.parameters,
205 })
206 );
207 return [undefined, diagnostics];
208 }
209
210 const body: HttpOperationRequestBody = {
211 type: bodyType,
212 parameter: bodyProperty,
213 contentTypes,
214 };
215 return [body, diagnostics];
216}
217