@typespec/http
Advanced tools
Comparing version
@@ -14,4 +14,6 @@ export declare const $lib: import("@typespec/compiler").TypeSpecLibrary<{ | ||
}; | ||
"optional-needs-path-expansion": { | ||
readonly default: import("@typespec/compiler").CallableMessage<["paramName", "paramName", "paramName"]>; | ||
"double-slash": { | ||
readonly default: import("@typespec/compiler").CallableMessage<["paramName"]>; | ||
readonly optionalUnset: import("@typespec/compiler").CallableMessage<["paramName"]>; | ||
readonly optionalSet: import("@typespec/compiler").CallableMessage<["paramName"]>; | ||
}; | ||
@@ -98,3 +100,3 @@ "missing-server-param": { | ||
}, Record<string, any>, "file" | "path" | "query" | "authentication" | "header" | "cookie" | "body" | "bodyRoot" | "bodyIgnore" | "multipartBody" | "statusCode" | "verbs" | "patchOptions" | "servers" | "includeInapplicableMetadataInPayload" | "externalInterfaces" | "routeProducer" | "routes" | "sharedRoutes" | "routeOptions" | "httpPart">; | ||
export declare const reportDiagnostic: <C extends "http-verb-duplicate" | "missing-uri-param" | "incompatible-uri-param" | "use-uri-template" | "optional-needs-path-expansion" | "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" | "response-cookie-not-supported" | "no-service-found" | "invalid-type-for-auth" | "shared-inconsistency" | "multipart-invalid-content-type" | "multipart-model" | "no-implicit-multipart" | "multipart-part" | "multipart-nested" | "http-file-extra-property" | "http-file-disallowed-metadata" | "formdata-no-part-name" | "http-file-structured" | "http-file-content-type-not-string" | "http-file-contents-not-scalar", M extends keyof { | ||
export declare const reportDiagnostic: <C extends "http-verb-duplicate" | "missing-uri-param" | "incompatible-uri-param" | "use-uri-template" | "double-slash" | "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" | "response-cookie-not-supported" | "no-service-found" | "invalid-type-for-auth" | "shared-inconsistency" | "multipart-invalid-content-type" | "multipart-model" | "no-implicit-multipart" | "multipart-part" | "multipart-nested" | "http-file-extra-property" | "http-file-disallowed-metadata" | "formdata-no-part-name" | "http-file-structured" | "http-file-content-type-not-string" | "http-file-contents-not-scalar", M extends keyof { | ||
"http-verb-duplicate": { | ||
@@ -112,4 +114,6 @@ readonly default: import("@typespec/compiler").CallableMessage<["entityName"]>; | ||
}; | ||
"optional-needs-path-expansion": { | ||
readonly default: import("@typespec/compiler").CallableMessage<["paramName", "paramName", "paramName"]>; | ||
"double-slash": { | ||
readonly default: import("@typespec/compiler").CallableMessage<["paramName"]>; | ||
readonly optionalUnset: import("@typespec/compiler").CallableMessage<["paramName"]>; | ||
readonly optionalSet: import("@typespec/compiler").CallableMessage<["paramName"]>; | ||
}; | ||
@@ -208,4 +212,6 @@ "missing-server-param": { | ||
}; | ||
"optional-needs-path-expansion": { | ||
readonly default: import("@typespec/compiler").CallableMessage<["paramName", "paramName", "paramName"]>; | ||
"double-slash": { | ||
readonly default: import("@typespec/compiler").CallableMessage<["paramName"]>; | ||
readonly optionalUnset: import("@typespec/compiler").CallableMessage<["paramName"]>; | ||
readonly optionalSet: import("@typespec/compiler").CallableMessage<["paramName"]>; | ||
}; | ||
@@ -291,3 +297,3 @@ "missing-server-param": { | ||
}; | ||
}, C, M>) => void, createDiagnostic: <C extends "http-verb-duplicate" | "missing-uri-param" | "incompatible-uri-param" | "use-uri-template" | "optional-needs-path-expansion" | "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" | "response-cookie-not-supported" | "no-service-found" | "invalid-type-for-auth" | "shared-inconsistency" | "multipart-invalid-content-type" | "multipart-model" | "no-implicit-multipart" | "multipart-part" | "multipart-nested" | "http-file-extra-property" | "http-file-disallowed-metadata" | "formdata-no-part-name" | "http-file-structured" | "http-file-content-type-not-string" | "http-file-contents-not-scalar", M extends keyof { | ||
}, C, M>) => void, createDiagnostic: <C extends "http-verb-duplicate" | "missing-uri-param" | "incompatible-uri-param" | "use-uri-template" | "double-slash" | "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" | "response-cookie-not-supported" | "no-service-found" | "invalid-type-for-auth" | "shared-inconsistency" | "multipart-invalid-content-type" | "multipart-model" | "no-implicit-multipart" | "multipart-part" | "multipart-nested" | "http-file-extra-property" | "http-file-disallowed-metadata" | "formdata-no-part-name" | "http-file-structured" | "http-file-content-type-not-string" | "http-file-contents-not-scalar", M extends keyof { | ||
"http-verb-duplicate": { | ||
@@ -305,4 +311,6 @@ readonly default: import("@typespec/compiler").CallableMessage<["entityName"]>; | ||
}; | ||
"optional-needs-path-expansion": { | ||
readonly default: import("@typespec/compiler").CallableMessage<["paramName", "paramName", "paramName"]>; | ||
"double-slash": { | ||
readonly default: import("@typespec/compiler").CallableMessage<["paramName"]>; | ||
readonly optionalUnset: import("@typespec/compiler").CallableMessage<["paramName"]>; | ||
readonly optionalSet: import("@typespec/compiler").CallableMessage<["paramName"]>; | ||
}; | ||
@@ -401,4 +409,6 @@ "missing-server-param": { | ||
}; | ||
"optional-needs-path-expansion": { | ||
readonly default: import("@typespec/compiler").CallableMessage<["paramName", "paramName", "paramName"]>; | ||
"double-slash": { | ||
readonly default: import("@typespec/compiler").CallableMessage<["paramName"]>; | ||
readonly optionalUnset: import("@typespec/compiler").CallableMessage<["paramName"]>; | ||
readonly optionalSet: import("@typespec/compiler").CallableMessage<["paramName"]>; | ||
}; | ||
@@ -405,0 +415,0 @@ "missing-server-param": { |
@@ -29,6 +29,8 @@ import { createTypeSpecLibrary, paramMessage } from "@typespec/compiler"; | ||
}, | ||
"optional-needs-path-expansion": { | ||
severity: "error", | ||
"double-slash": { | ||
severity: "warning", | ||
messages: { | ||
default: paramMessage `Optional path parameter '${"paramName"}' must be declared with a leading '/' in the corresponding route path variable. For example, use '{/${"paramName"}}' instead of '{${"paramName"}}'.`, | ||
default: paramMessage `Route will result in duplicate slashes as parameter '${"paramName"}' use path expansion and is prefixed with a /`, | ||
optionalUnset: paramMessage `Route will result in duplicate slashes when optional parameter '${"paramName"}' is not set.`, | ||
optionalSet: paramMessage `Route will result in duplicate slashes when optional parameter '${"paramName"}' is set.`, | ||
}, | ||
@@ -35,0 +37,0 @@ }, |
@@ -7,4 +7,8 @@ import { createDiagnosticCollector, } from "@typespec/compiler"; | ||
const AllowedSegmentSeparators = ["/", ":"]; | ||
function needsSlashPrefix(fragment) { | ||
return !(AllowedSegmentSeparators.indexOf(fragment[0]) !== -1 || | ||
(fragment[0] === "{" && fragment[1] === "/")); | ||
} | ||
function normalizeFragment(fragment, trimLast = false) { | ||
if (fragment.length > 0 && AllowedSegmentSeparators.indexOf(fragment[0]) < 0) { | ||
if (fragment.length > 0 && needsSlashPrefix(fragment)) { | ||
// Insert the default separator | ||
@@ -28,4 +32,4 @@ fragment = `/${fragment}`; | ||
const path = pathFragments.length === 0 ? "/" : joinPathSegments(pathFragments); | ||
// The final path must start with a '/' | ||
return path[0] === "/" ? path : `/${path}`; | ||
// The final path must start with a '/' or {/ (path expansion) | ||
return path[0] === "/" || (path[0] === "{" && path[1] === "/") ? path : `/${path}`; | ||
} | ||
@@ -40,13 +44,6 @@ export function resolvePathAndParameters(program, operation, overloadBase, options) { | ||
.map((x) => x.name)); | ||
validateDoubleSlash(parsedUriTemplate, operation, parameters).forEach((d) => diagnostics.add(d)); | ||
// Ensure that all of the parameters defined in the route are accounted for in | ||
// the operation parameters and are correctly defined when optional | ||
for (const routeParam of parsedUriTemplate.parameters) { | ||
const parameter = parameters.parameters.find((x) => x.name === routeParam.name); | ||
if (parameter?.type === "path" && parameter.param.optional && routeParam.operator !== "/") { | ||
diagnostics.add(createDiagnostic({ | ||
code: "optional-needs-path-expansion", | ||
format: { paramName: parameter.param.name }, | ||
target: parameter.param, | ||
})); | ||
} | ||
const decoded = decodeURIComponent(routeParam.name); | ||
@@ -68,2 +65,26 @@ if (!paramByName.has(routeParam.name) && !paramByName.has(decoded)) { | ||
} | ||
function validateDoubleSlash(parsedUriTemplate, operation, parameters) { | ||
const diagnostics = createDiagnosticCollector(); | ||
if (parsedUriTemplate.segments) { | ||
const [firstSeg, ...rest] = parsedUriTemplate.segments; | ||
let lastSeg = firstSeg; | ||
for (const seg of rest) { | ||
if (typeof seg !== "string") { | ||
const parameter = parameters.parameters.find((x) => x.name === seg.name); | ||
if (seg.operator === "/") { | ||
if (typeof lastSeg === "string" && lastSeg.endsWith("/")) { | ||
diagnostics.add(createDiagnostic({ | ||
code: "double-slash", | ||
messageId: parameter?.param.optional ? "optionalUnset" : "default", | ||
format: { paramName: seg.name }, | ||
target: operation, | ||
})); | ||
} | ||
} | ||
lastSeg = seg; | ||
} | ||
} | ||
} | ||
return diagnostics.diagnostics; | ||
} | ||
function produceLegacyPathFromUriTemplate(uriTemplate) { | ||
@@ -70,0 +91,0 @@ let result = ""; |
{ | ||
"name": "@typespec/http", | ||
"version": "1.0.0-rc.2-dev.1", | ||
"version": "1.0.0-rc.2-dev.2", | ||
"author": "Microsoft Corporation", | ||
@@ -5,0 +5,0 @@ "description": "TypeSpec HTTP protocol binding", |
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
444403
0.62%5586
0.59%