1388 lines
58 KiB
JavaScript
1388 lines
58 KiB
JavaScript
import SwaggerParser from '@apidevtools/swagger-parser';
|
|
|
|
// Copyright (c) Microsoft Corporation.
|
|
/**
|
|
* An enum that represents the types of errors that can occur during validation.
|
|
*/
|
|
var ErrorType;
|
|
(function (ErrorType) {
|
|
ErrorType["SpecNotValid"] = "spec-not-valid";
|
|
ErrorType["RemoteRefNotSupported"] = "remote-ref-not-supported";
|
|
ErrorType["NoServerInformation"] = "no-server-information";
|
|
ErrorType["UrlProtocolNotSupported"] = "url-protocol-not-supported";
|
|
ErrorType["RelativeServerUrlNotSupported"] = "relative-server-url-not-supported";
|
|
ErrorType["NoSupportedApi"] = "no-supported-api";
|
|
ErrorType["NoExtraAPICanBeAdded"] = "no-extra-api-can-be-added";
|
|
ErrorType["ResolveServerUrlFailed"] = "resolve-server-url-failed";
|
|
ErrorType["SwaggerNotSupported"] = "swagger-not-supported";
|
|
ErrorType["MultipleAuthNotSupported"] = "multiple-auth-not-supported";
|
|
ErrorType["SpecVersionNotSupported"] = "spec-version-not-supported";
|
|
ErrorType["ListFailed"] = "list-failed";
|
|
ErrorType["listSupportedAPIInfoFailed"] = "list-supported-api-info-failed";
|
|
ErrorType["FilterSpecFailed"] = "filter-spec-failed";
|
|
ErrorType["UpdateManifestFailed"] = "update-manifest-failed";
|
|
ErrorType["GenerateAdaptiveCardFailed"] = "generate-adaptive-card-failed";
|
|
ErrorType["GenerateFailed"] = "generate-failed";
|
|
ErrorType["ValidateFailed"] = "validate-failed";
|
|
ErrorType["GetSpecFailed"] = "get-spec-failed";
|
|
ErrorType["AuthTypeIsNotSupported"] = "auth-type-is-not-supported";
|
|
ErrorType["MissingOperationId"] = "missing-operation-id";
|
|
ErrorType["PostBodyContainMultipleMediaTypes"] = "post-body-contain-multiple-media-types";
|
|
ErrorType["ResponseContainMultipleMediaTypes"] = "response-contain-multiple-media-types";
|
|
ErrorType["ResponseJsonIsEmpty"] = "response-json-is-empty";
|
|
ErrorType["PostBodySchemaIsNotJson"] = "post-body-schema-is-not-json";
|
|
ErrorType["PostBodyContainsRequiredUnsupportedSchema"] = "post-body-contains-required-unsupported-schema";
|
|
ErrorType["ParamsContainRequiredUnsupportedSchema"] = "params-contain-required-unsupported-schema";
|
|
ErrorType["ParamsContainsNestedObject"] = "params-contains-nested-object";
|
|
ErrorType["RequestBodyContainsNestedObject"] = "request-body-contains-nested-object";
|
|
ErrorType["ExceededRequiredParamsLimit"] = "exceeded-required-params-limit";
|
|
ErrorType["NoParameter"] = "no-parameter";
|
|
ErrorType["NoAPIInfo"] = "no-api-info";
|
|
ErrorType["MethodNotAllowed"] = "method-not-allowed";
|
|
ErrorType["UrlPathNotExist"] = "url-path-not-exist";
|
|
ErrorType["Cancelled"] = "cancelled";
|
|
ErrorType["Unknown"] = "unknown";
|
|
})(ErrorType || (ErrorType = {}));
|
|
/**
|
|
* An enum that represents the types of warnings that can occur during validation.
|
|
*/
|
|
var WarningType;
|
|
(function (WarningType) {
|
|
WarningType["OperationIdMissing"] = "operationid-missing";
|
|
WarningType["GenerateCardFailed"] = "generate-card-failed";
|
|
WarningType["OperationOnlyContainsOptionalParam"] = "operation-only-contains-optional-param";
|
|
WarningType["ConvertSwaggerToOpenAPI"] = "convert-swagger-to-openapi";
|
|
WarningType["Unknown"] = "unknown";
|
|
})(WarningType || (WarningType = {}));
|
|
/**
|
|
* An enum that represents the validation status of an OpenAPI specification file.
|
|
*/
|
|
var ValidationStatus;
|
|
(function (ValidationStatus) {
|
|
ValidationStatus[ValidationStatus["Valid"] = 0] = "Valid";
|
|
ValidationStatus[ValidationStatus["Warning"] = 1] = "Warning";
|
|
ValidationStatus[ValidationStatus["Error"] = 2] = "Error";
|
|
})(ValidationStatus || (ValidationStatus = {}));
|
|
var ProjectType;
|
|
(function (ProjectType) {
|
|
ProjectType[ProjectType["Copilot"] = 0] = "Copilot";
|
|
ProjectType[ProjectType["SME"] = 1] = "SME";
|
|
ProjectType[ProjectType["TeamsAi"] = 2] = "TeamsAi";
|
|
})(ProjectType || (ProjectType = {}));
|
|
|
|
// Copyright (c) Microsoft Corporation.
|
|
class SpecParserError extends Error {
|
|
constructor(message, errorType) {
|
|
super(message);
|
|
this.errorType = errorType;
|
|
}
|
|
}
|
|
|
|
// Copyright (c) Microsoft Corporation.
|
|
class ConstantString {
|
|
}
|
|
ConstantString.CancelledMessage = "Operation is cancelled.";
|
|
ConstantString.NoServerInformation = "No server information is found in the OpenAPI description document.";
|
|
ConstantString.RemoteRefNotSupported = "Remote reference is not supported: %s.";
|
|
ConstantString.MissingOperationId = "Missing operationIds: %s.";
|
|
ConstantString.NoSupportedApi = "No supported API is found in the OpenAPI description document: only GET and POST methods are supported, additionally, there can be at most one required parameter, and no auth is allowed.";
|
|
ConstantString.AdditionalPropertiesNotSupported = "'additionalProperties' is not supported, and will be ignored.";
|
|
ConstantString.SchemaNotSupported = "'oneOf', 'allOf', 'anyOf', and 'not' schema are not supported: %s.";
|
|
ConstantString.UnknownSchema = "Unknown schema: %s.";
|
|
ConstantString.UrlProtocolNotSupported = "Server url is not correct: protocol %s is not supported, you should use https protocol instead.";
|
|
ConstantString.RelativeServerUrlNotSupported = "Server url is not correct: relative server url is not supported.";
|
|
ConstantString.ResolveServerUrlFailed = "Unable to resolve the server URL: please make sure that the environment variable %s is defined.";
|
|
ConstantString.OperationOnlyContainsOptionalParam = "Operation %s contains multiple optional parameters. The first optional parameter is used for this command.";
|
|
ConstantString.ConvertSwaggerToOpenAPI = "The Swagger 2.0 file has been converted to OpenAPI 3.0.";
|
|
ConstantString.SwaggerNotSupported = "Swagger 2.0 is not supported. Please convert to OpenAPI 3.0 manually before proceeding.";
|
|
ConstantString.SpecVersionNotSupported = "Unsupported OpenAPI version %s. Please use version 3.0.x.";
|
|
ConstantString.MultipleAuthNotSupported = "Multiple authentication methods are unsupported. Ensure all selected APIs use identical authentication.";
|
|
ConstantString.UnsupportedSchema = "Unsupported schema in %s %s: %s";
|
|
ConstantString.WrappedCardVersion = "devPreview";
|
|
ConstantString.WrappedCardSchema = "https://developer.microsoft.com/json-schemas/teams/vDevPreview/MicrosoftTeams.ResponseRenderingTemplate.schema.json";
|
|
ConstantString.WrappedCardResponseLayout = "list";
|
|
ConstantString.GetMethod = "get";
|
|
ConstantString.PostMethod = "post";
|
|
ConstantString.AdaptiveCardVersion = "1.5";
|
|
ConstantString.AdaptiveCardSchema = "http://adaptivecards.io/schemas/adaptive-card.json";
|
|
ConstantString.AdaptiveCardType = "AdaptiveCard";
|
|
ConstantString.TextBlockType = "TextBlock";
|
|
ConstantString.ImageType = "Image";
|
|
ConstantString.ContainerType = "Container";
|
|
ConstantString.RegistrationIdPostfix = {
|
|
apiKey: "REGISTRATION_ID",
|
|
oauth2: "CONFIGURATION_ID",
|
|
http: "REGISTRATION_ID",
|
|
openIdConnect: "REGISTRATION_ID",
|
|
};
|
|
ConstantString.ResponseCodeFor20X = [
|
|
"200",
|
|
"201",
|
|
"202",
|
|
"203",
|
|
"204",
|
|
"205",
|
|
"206",
|
|
"207",
|
|
"208",
|
|
"226",
|
|
"default",
|
|
];
|
|
ConstantString.AllOperationMethods = [
|
|
"get",
|
|
"post",
|
|
"put",
|
|
"delete",
|
|
"patch",
|
|
"head",
|
|
"options",
|
|
"trace",
|
|
];
|
|
// TODO: update after investigating the usage of these constants.
|
|
ConstantString.WellknownResultNames = [
|
|
"result",
|
|
"data",
|
|
"items",
|
|
"root",
|
|
"matches",
|
|
"queries",
|
|
"list",
|
|
"output",
|
|
];
|
|
ConstantString.WellknownTitleName = ["title", "name", "summary", "caption", "subject", "label"];
|
|
ConstantString.WellknownSubtitleName = [
|
|
"subtitle",
|
|
"id",
|
|
"uid",
|
|
"description",
|
|
"desc",
|
|
"detail",
|
|
];
|
|
ConstantString.WellknownImageName = [
|
|
"image",
|
|
"icon",
|
|
"avatar",
|
|
"picture",
|
|
"photo",
|
|
"logo",
|
|
"pic",
|
|
"thumbnail",
|
|
"img",
|
|
];
|
|
ConstantString.ShortDescriptionMaxLens = 80;
|
|
ConstantString.FullDescriptionMaxLens = 4000;
|
|
ConstantString.CommandDescriptionMaxLens = 128;
|
|
ConstantString.ParameterDescriptionMaxLens = 128;
|
|
ConstantString.ConversationStarterMaxLens = 50;
|
|
ConstantString.CommandTitleMaxLens = 32;
|
|
ConstantString.ParameterTitleMaxLens = 32;
|
|
ConstantString.SMERequiredParamsMaxNum = 5;
|
|
ConstantString.DefaultPluginId = "plugin_1";
|
|
ConstantString.PluginManifestSchema = "https://aka.ms/json-schemas/copilot-extensions/v2.1/plugin.schema.json";
|
|
|
|
// Copyright (c) Microsoft Corporation.
|
|
class Utils {
|
|
static hasNestedObjectInSchema(schema) {
|
|
if (schema.type === "object") {
|
|
for (const property in schema.properties) {
|
|
const nestedSchema = schema.properties[property];
|
|
if (nestedSchema.type === "object") {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
static containMultipleMediaTypes(bodyObject) {
|
|
return Object.keys((bodyObject === null || bodyObject === void 0 ? void 0 : bodyObject.content) || {}).length > 1;
|
|
}
|
|
static isBearerTokenAuth(authScheme) {
|
|
return authScheme.type === "http" && authScheme.scheme === "bearer";
|
|
}
|
|
static isAPIKeyAuth(authScheme) {
|
|
return authScheme.type === "apiKey";
|
|
}
|
|
static isOAuthWithAuthCodeFlow(authScheme) {
|
|
return !!(authScheme.type === "oauth2" &&
|
|
authScheme.flows &&
|
|
authScheme.flows.authorizationCode);
|
|
}
|
|
static getAuthArray(securities, spec) {
|
|
var _a;
|
|
const result = [];
|
|
const securitySchemas = (_a = spec.components) === null || _a === void 0 ? void 0 : _a.securitySchemes;
|
|
const securitiesArr = securities !== null && securities !== void 0 ? securities : spec.security;
|
|
if (securitiesArr && securitySchemas) {
|
|
for (let i = 0; i < securitiesArr.length; i++) {
|
|
const security = securitiesArr[i];
|
|
const authArray = [];
|
|
for (const name in security) {
|
|
const auth = securitySchemas[name];
|
|
authArray.push({
|
|
authScheme: auth,
|
|
name: name,
|
|
});
|
|
}
|
|
if (authArray.length > 0) {
|
|
result.push(authArray);
|
|
}
|
|
}
|
|
}
|
|
result.sort((a, b) => a[0].name.localeCompare(b[0].name));
|
|
return result;
|
|
}
|
|
static getAuthInfo(spec) {
|
|
let authInfo = undefined;
|
|
for (const url in spec.paths) {
|
|
for (const method in spec.paths[url]) {
|
|
const operation = spec.paths[url][method];
|
|
const authArray = Utils.getAuthArray(operation.security, spec);
|
|
if (authArray && authArray.length > 0) {
|
|
const currentAuth = authArray[0][0];
|
|
if (!authInfo) {
|
|
authInfo = authArray[0][0];
|
|
}
|
|
else if (authInfo.name !== currentAuth.name) {
|
|
throw new SpecParserError(ConstantString.MultipleAuthNotSupported, ErrorType.MultipleAuthNotSupported);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return authInfo;
|
|
}
|
|
static updateFirstLetter(str) {
|
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
}
|
|
static getResponseJson(operationObject) {
|
|
var _a, _b;
|
|
let json = {};
|
|
let multipleMediaType = false;
|
|
for (const code of ConstantString.ResponseCodeFor20X) {
|
|
const responseObject = (_a = operationObject === null || operationObject === void 0 ? void 0 : operationObject.responses) === null || _a === void 0 ? void 0 : _a[code];
|
|
if ((_b = responseObject === null || responseObject === void 0 ? void 0 : responseObject.content) === null || _b === void 0 ? void 0 : _b["application/json"]) {
|
|
multipleMediaType = false;
|
|
json = responseObject.content["application/json"];
|
|
if (Utils.containMultipleMediaTypes(responseObject)) {
|
|
multipleMediaType = true;
|
|
json = {};
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return { json, multipleMediaType };
|
|
}
|
|
static convertPathToCamelCase(path) {
|
|
const pathSegments = path.split(/[./{]/);
|
|
const camelCaseSegments = pathSegments.map((segment) => {
|
|
segment = segment.replace(/}/g, "");
|
|
return segment.charAt(0).toUpperCase() + segment.slice(1);
|
|
});
|
|
const camelCasePath = camelCaseSegments.join("");
|
|
return camelCasePath;
|
|
}
|
|
static getUrlProtocol(urlString) {
|
|
try {
|
|
const url = new URL(urlString);
|
|
return url.protocol;
|
|
}
|
|
catch (err) {
|
|
return undefined;
|
|
}
|
|
}
|
|
static resolveEnv(str) {
|
|
const placeHolderReg = /\${{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*}}/g;
|
|
let matches = placeHolderReg.exec(str);
|
|
let newStr = str;
|
|
while (matches != null) {
|
|
const envVar = matches[1];
|
|
const envVal = process.env[envVar];
|
|
if (!envVal) {
|
|
throw new Error(Utils.format(ConstantString.ResolveServerUrlFailed, envVar));
|
|
}
|
|
else {
|
|
newStr = newStr.replace(matches[0], envVal);
|
|
}
|
|
matches = placeHolderReg.exec(str);
|
|
}
|
|
return newStr;
|
|
}
|
|
static checkServerUrl(servers) {
|
|
const errors = [];
|
|
let serverUrl;
|
|
try {
|
|
serverUrl = Utils.resolveEnv(servers[0].url);
|
|
}
|
|
catch (err) {
|
|
errors.push({
|
|
type: ErrorType.ResolveServerUrlFailed,
|
|
content: err.message,
|
|
data: servers,
|
|
});
|
|
return errors;
|
|
}
|
|
const protocol = Utils.getUrlProtocol(serverUrl);
|
|
if (!protocol) {
|
|
// Relative server url is not supported
|
|
errors.push({
|
|
type: ErrorType.RelativeServerUrlNotSupported,
|
|
content: ConstantString.RelativeServerUrlNotSupported,
|
|
data: servers,
|
|
});
|
|
}
|
|
else if (protocol !== "https:") {
|
|
// Http server url is not supported
|
|
const protocolString = protocol.slice(0, -1);
|
|
errors.push({
|
|
type: ErrorType.UrlProtocolNotSupported,
|
|
content: Utils.format(ConstantString.UrlProtocolNotSupported, protocol.slice(0, -1)),
|
|
data: protocolString,
|
|
});
|
|
}
|
|
return errors;
|
|
}
|
|
static validateServer(spec, options) {
|
|
var _a;
|
|
const errors = [];
|
|
let hasTopLevelServers = false;
|
|
let hasPathLevelServers = false;
|
|
let hasOperationLevelServers = false;
|
|
if (spec.servers && spec.servers.length >= 1) {
|
|
hasTopLevelServers = true;
|
|
// for multiple server, we only use the first url
|
|
const serverErrors = Utils.checkServerUrl(spec.servers);
|
|
errors.push(...serverErrors);
|
|
}
|
|
const paths = spec.paths;
|
|
for (const path in paths) {
|
|
const methods = paths[path];
|
|
if ((methods === null || methods === void 0 ? void 0 : methods.servers) && methods.servers.length >= 1) {
|
|
hasPathLevelServers = true;
|
|
const serverErrors = Utils.checkServerUrl(methods.servers);
|
|
errors.push(...serverErrors);
|
|
}
|
|
for (const method in methods) {
|
|
const operationObject = methods[method];
|
|
if (((_a = options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
if ((operationObject === null || operationObject === void 0 ? void 0 : operationObject.servers) && operationObject.servers.length >= 1) {
|
|
hasOperationLevelServers = true;
|
|
const serverErrors = Utils.checkServerUrl(operationObject.servers);
|
|
errors.push(...serverErrors);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!hasTopLevelServers && !hasPathLevelServers && !hasOperationLevelServers) {
|
|
errors.push({
|
|
type: ErrorType.NoServerInformation,
|
|
content: ConstantString.NoServerInformation,
|
|
});
|
|
}
|
|
return errors;
|
|
}
|
|
static isWellKnownName(name, wellknownNameList) {
|
|
for (let i = 0; i < wellknownNameList.length; i++) {
|
|
name = name.replace(/_/g, "").replace(/-/g, "");
|
|
if (name.toLowerCase().includes(wellknownNameList[i])) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
static generateParametersFromSchema(schema, name, allowMultipleParameters, isRequired = false) {
|
|
var _a, _b;
|
|
const requiredParams = [];
|
|
const optionalParams = [];
|
|
if (schema.type === "string" ||
|
|
schema.type === "integer" ||
|
|
schema.type === "boolean" ||
|
|
schema.type === "number") {
|
|
const parameter = {
|
|
name: name,
|
|
title: Utils.updateFirstLetter(name).slice(0, ConstantString.ParameterTitleMaxLens),
|
|
description: ((_a = schema.description) !== null && _a !== void 0 ? _a : "").slice(0, ConstantString.ParameterDescriptionMaxLens),
|
|
};
|
|
if (allowMultipleParameters) {
|
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
}
|
|
if (isRequired && schema.default === undefined) {
|
|
parameter.isRequired = true;
|
|
requiredParams.push(parameter);
|
|
}
|
|
else {
|
|
optionalParams.push(parameter);
|
|
}
|
|
}
|
|
else if (schema.type === "object") {
|
|
const { properties } = schema;
|
|
for (const property in properties) {
|
|
let isRequired = false;
|
|
if (schema.required && ((_b = schema.required) === null || _b === void 0 ? void 0 : _b.indexOf(property)) >= 0) {
|
|
isRequired = true;
|
|
}
|
|
const [requiredP, optionalP] = Utils.generateParametersFromSchema(properties[property], property, allowMultipleParameters, isRequired);
|
|
requiredParams.push(...requiredP);
|
|
optionalParams.push(...optionalP);
|
|
}
|
|
}
|
|
return [requiredParams, optionalParams];
|
|
}
|
|
static updateParameterWithInputType(schema, param) {
|
|
if (schema.enum) {
|
|
param.inputType = "choiceset";
|
|
param.choices = [];
|
|
for (let i = 0; i < schema.enum.length; i++) {
|
|
param.choices.push({
|
|
title: schema.enum[i],
|
|
value: schema.enum[i],
|
|
});
|
|
}
|
|
}
|
|
else if (schema.type === "string") {
|
|
param.inputType = "text";
|
|
}
|
|
else if (schema.type === "integer" || schema.type === "number") {
|
|
param.inputType = "number";
|
|
}
|
|
else if (schema.type === "boolean") {
|
|
param.inputType = "toggle";
|
|
}
|
|
if (schema.default) {
|
|
param.value = schema.default;
|
|
}
|
|
}
|
|
static parseApiInfo(operationItem, options) {
|
|
var _a, _b;
|
|
const requiredParams = [];
|
|
const optionalParams = [];
|
|
const paramObject = operationItem.parameters;
|
|
if (paramObject) {
|
|
paramObject.forEach((param) => {
|
|
var _a;
|
|
const parameter = {
|
|
name: param.name,
|
|
title: Utils.updateFirstLetter(param.name).slice(0, ConstantString.ParameterTitleMaxLens),
|
|
description: ((_a = param.description) !== null && _a !== void 0 ? _a : "").slice(0, ConstantString.ParameterDescriptionMaxLens),
|
|
};
|
|
const schema = param.schema;
|
|
if (options.allowMultipleParameters && schema) {
|
|
Utils.updateParameterWithInputType(schema, parameter);
|
|
}
|
|
if (param.in !== "header" && param.in !== "cookie") {
|
|
if (param.required && (schema === null || schema === void 0 ? void 0 : schema.default) === undefined) {
|
|
parameter.isRequired = true;
|
|
requiredParams.push(parameter);
|
|
}
|
|
else {
|
|
optionalParams.push(parameter);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
if (operationItem.requestBody) {
|
|
const requestBody = operationItem.requestBody;
|
|
const requestJson = requestBody.content["application/json"];
|
|
if (Object.keys(requestJson).length !== 0) {
|
|
const schema = requestJson.schema;
|
|
const [requiredP, optionalP] = Utils.generateParametersFromSchema(schema, "requestBody", !!options.allowMultipleParameters, requestBody.required);
|
|
requiredParams.push(...requiredP);
|
|
optionalParams.push(...optionalP);
|
|
}
|
|
}
|
|
const operationId = operationItem.operationId;
|
|
const parameters = [...requiredParams, ...optionalParams];
|
|
const command = {
|
|
context: ["compose"],
|
|
type: "query",
|
|
title: ((_a = operationItem.summary) !== null && _a !== void 0 ? _a : "").slice(0, ConstantString.CommandTitleMaxLens),
|
|
id: operationId,
|
|
parameters: parameters,
|
|
description: ((_b = operationItem.description) !== null && _b !== void 0 ? _b : "").slice(0, ConstantString.CommandDescriptionMaxLens),
|
|
};
|
|
return command;
|
|
}
|
|
static format(str, ...args) {
|
|
let index = 0;
|
|
return str.replace(/%s/g, () => {
|
|
const arg = args[index++];
|
|
return arg !== undefined ? arg : "";
|
|
});
|
|
}
|
|
static getSafeRegistrationIdEnvName(authName) {
|
|
if (!authName) {
|
|
return "";
|
|
}
|
|
let safeRegistrationIdEnvName = authName.toUpperCase().replace(/[^A-Z0-9_]/g, "_");
|
|
if (!safeRegistrationIdEnvName.match(/^[A-Z]/)) {
|
|
safeRegistrationIdEnvName = "PREFIX_" + safeRegistrationIdEnvName;
|
|
}
|
|
return safeRegistrationIdEnvName;
|
|
}
|
|
static getServerObject(spec, method, path) {
|
|
const pathObj = spec.paths[path];
|
|
const operationObject = pathObj[method];
|
|
const rootServer = spec.servers && spec.servers[0];
|
|
const methodServer = spec.paths[path].servers && spec.paths[path].servers[0];
|
|
const operationServer = operationObject.servers && operationObject.servers[0];
|
|
const serverUrl = operationServer || methodServer || rootServer;
|
|
return serverUrl;
|
|
}
|
|
}
|
|
|
|
// Copyright (c) Microsoft Corporation.
|
|
class Validator {
|
|
listAPIs() {
|
|
var _a;
|
|
if (this.apiMap) {
|
|
return this.apiMap;
|
|
}
|
|
const paths = this.spec.paths;
|
|
const result = {};
|
|
for (const path in paths) {
|
|
const methods = paths[path];
|
|
for (const method in methods) {
|
|
const operationObject = methods[method];
|
|
if (((_a = this.options.allowMethods) === null || _a === void 0 ? void 0 : _a.includes(method)) && operationObject) {
|
|
const validateResult = this.validateAPI(method, path);
|
|
result[`${method.toUpperCase()} ${path}`] = {
|
|
operation: operationObject,
|
|
isValid: validateResult.isValid,
|
|
reason: validateResult.reason,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
this.apiMap = result;
|
|
return result;
|
|
}
|
|
validateSpecVersion() {
|
|
const result = { errors: [], warnings: [] };
|
|
if (this.spec.openapi >= "3.1.0") {
|
|
result.errors.push({
|
|
type: ErrorType.SpecVersionNotSupported,
|
|
content: Utils.format(ConstantString.SpecVersionNotSupported, this.spec.openapi),
|
|
data: this.spec.openapi,
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
validateSpecServer() {
|
|
const result = { errors: [], warnings: [] };
|
|
const serverErrors = Utils.validateServer(this.spec, this.options);
|
|
result.errors.push(...serverErrors);
|
|
return result;
|
|
}
|
|
validateSpecNoSupportAPI() {
|
|
const result = { errors: [], warnings: [] };
|
|
const apiMap = this.listAPIs();
|
|
const validAPIs = Object.entries(apiMap).filter(([, value]) => value.isValid);
|
|
if (validAPIs.length === 0) {
|
|
const data = [];
|
|
for (const key in apiMap) {
|
|
const { reason } = apiMap[key];
|
|
const apiInvalidReason = { api: key, reason: reason };
|
|
data.push(apiInvalidReason);
|
|
}
|
|
result.errors.push({
|
|
type: ErrorType.NoSupportedApi,
|
|
content: ConstantString.NoSupportedApi,
|
|
data,
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
validateSpecOperationId() {
|
|
const result = { errors: [], warnings: [] };
|
|
const apiMap = this.listAPIs();
|
|
// OperationId missing
|
|
const apisMissingOperationId = [];
|
|
for (const key in apiMap) {
|
|
const { operation } = apiMap[key];
|
|
if (!operation.operationId) {
|
|
apisMissingOperationId.push(key);
|
|
}
|
|
}
|
|
if (apisMissingOperationId.length > 0) {
|
|
result.warnings.push({
|
|
type: WarningType.OperationIdMissing,
|
|
content: Utils.format(ConstantString.MissingOperationId, apisMissingOperationId.join(", ")),
|
|
data: apisMissingOperationId,
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
validateMethodAndPath(method, path) {
|
|
const result = { isValid: true, reason: [] };
|
|
if (this.options.allowMethods && !this.options.allowMethods.includes(method)) {
|
|
result.isValid = false;
|
|
result.reason.push(ErrorType.MethodNotAllowed);
|
|
return result;
|
|
}
|
|
const pathObj = this.spec.paths[path];
|
|
if (!pathObj || !pathObj[method]) {
|
|
result.isValid = false;
|
|
result.reason.push(ErrorType.UrlPathNotExist);
|
|
return result;
|
|
}
|
|
return result;
|
|
}
|
|
validateResponse(method, path) {
|
|
const result = { isValid: true, reason: [] };
|
|
const operationObject = this.spec.paths[path][method];
|
|
const { json, multipleMediaType } = Utils.getResponseJson(operationObject);
|
|
if (this.options.projectType === ProjectType.SME) {
|
|
// only support response body only contains “application/json” content type
|
|
if (multipleMediaType) {
|
|
result.reason.push(ErrorType.ResponseContainMultipleMediaTypes);
|
|
}
|
|
else if (Object.keys(json).length === 0) {
|
|
// response body should not be empty
|
|
result.reason.push(ErrorType.ResponseJsonIsEmpty);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
validateServer(method, path) {
|
|
const result = { isValid: true, reason: [] };
|
|
const serverObj = Utils.getServerObject(this.spec, method, path);
|
|
if (!serverObj) {
|
|
// should contain server URL
|
|
result.reason.push(ErrorType.NoServerInformation);
|
|
}
|
|
else {
|
|
// server url should be absolute url with https protocol
|
|
const serverValidateResult = Utils.checkServerUrl([serverObj]);
|
|
result.reason.push(...serverValidateResult.map((item) => item.type));
|
|
}
|
|
return result;
|
|
}
|
|
validateAuth(method, path) {
|
|
const pathObj = this.spec.paths[path];
|
|
const operationObject = pathObj[method];
|
|
const securities = operationObject.security;
|
|
const authSchemeArray = Utils.getAuthArray(securities, this.spec);
|
|
if (authSchemeArray.length === 0) {
|
|
return { isValid: true, reason: [] };
|
|
}
|
|
if (this.options.allowAPIKeyAuth ||
|
|
this.options.allowOauth2 ||
|
|
this.options.allowBearerTokenAuth) {
|
|
// Currently we don't support multiple auth in one operation
|
|
if (authSchemeArray.length > 0 && authSchemeArray.every((auths) => auths.length > 1)) {
|
|
return {
|
|
isValid: false,
|
|
reason: [ErrorType.MultipleAuthNotSupported],
|
|
};
|
|
}
|
|
for (const auths of authSchemeArray) {
|
|
if (auths.length === 1) {
|
|
if ((this.options.allowAPIKeyAuth && Utils.isAPIKeyAuth(auths[0].authScheme)) ||
|
|
(this.options.allowOauth2 && Utils.isOAuthWithAuthCodeFlow(auths[0].authScheme)) ||
|
|
(this.options.allowBearerTokenAuth && Utils.isBearerTokenAuth(auths[0].authScheme))) {
|
|
return { isValid: true, reason: [] };
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return { isValid: false, reason: [ErrorType.AuthTypeIsNotSupported] };
|
|
}
|
|
checkPostBodySchema(schema, isRequired = false) {
|
|
var _a;
|
|
const paramResult = {
|
|
requiredNum: 0,
|
|
optionalNum: 0,
|
|
isValid: true,
|
|
reason: [],
|
|
};
|
|
if (Object.keys(schema).length === 0) {
|
|
return paramResult;
|
|
}
|
|
const isRequiredWithoutDefault = isRequired && schema.default === undefined;
|
|
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
paramResult.isValid = false;
|
|
paramResult.reason = [ErrorType.RequestBodyContainsNestedObject];
|
|
return paramResult;
|
|
}
|
|
if (schema.type === "string" ||
|
|
schema.type === "integer" ||
|
|
schema.type === "boolean" ||
|
|
schema.type === "number") {
|
|
if (isRequiredWithoutDefault) {
|
|
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
}
|
|
else {
|
|
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
}
|
|
}
|
|
else if (schema.type === "object") {
|
|
const { properties } = schema;
|
|
for (const property in properties) {
|
|
let isRequired = false;
|
|
if (schema.required && ((_a = schema.required) === null || _a === void 0 ? void 0 : _a.indexOf(property)) >= 0) {
|
|
isRequired = true;
|
|
}
|
|
const result = this.checkPostBodySchema(properties[property], isRequired);
|
|
paramResult.requiredNum += result.requiredNum;
|
|
paramResult.optionalNum += result.optionalNum;
|
|
paramResult.isValid = paramResult.isValid && result.isValid;
|
|
paramResult.reason.push(...result.reason);
|
|
}
|
|
}
|
|
else {
|
|
if (isRequiredWithoutDefault && !isCopilot) {
|
|
paramResult.isValid = false;
|
|
paramResult.reason.push(ErrorType.PostBodyContainsRequiredUnsupportedSchema);
|
|
}
|
|
}
|
|
return paramResult;
|
|
}
|
|
checkParamSchema(paramObject) {
|
|
const paramResult = {
|
|
requiredNum: 0,
|
|
optionalNum: 0,
|
|
isValid: true,
|
|
reason: [],
|
|
};
|
|
if (!paramObject) {
|
|
return paramResult;
|
|
}
|
|
const isCopilot = this.projectType === ProjectType.Copilot;
|
|
for (let i = 0; i < paramObject.length; i++) {
|
|
const param = paramObject[i];
|
|
const schema = param.schema;
|
|
if (isCopilot && this.hasNestedObjectInSchema(schema)) {
|
|
paramResult.isValid = false;
|
|
paramResult.reason.push(ErrorType.ParamsContainsNestedObject);
|
|
continue;
|
|
}
|
|
const isRequiredWithoutDefault = param.required && schema.default === undefined;
|
|
if (isCopilot) {
|
|
if (isRequiredWithoutDefault) {
|
|
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
}
|
|
else {
|
|
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
}
|
|
continue;
|
|
}
|
|
if (param.in === "header" || param.in === "cookie") {
|
|
if (isRequiredWithoutDefault) {
|
|
paramResult.isValid = false;
|
|
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
}
|
|
continue;
|
|
}
|
|
if (schema.type !== "boolean" &&
|
|
schema.type !== "string" &&
|
|
schema.type !== "number" &&
|
|
schema.type !== "integer") {
|
|
if (isRequiredWithoutDefault) {
|
|
paramResult.isValid = false;
|
|
paramResult.reason.push(ErrorType.ParamsContainRequiredUnsupportedSchema);
|
|
}
|
|
continue;
|
|
}
|
|
if (param.in === "query" || param.in === "path") {
|
|
if (isRequiredWithoutDefault) {
|
|
paramResult.requiredNum = paramResult.requiredNum + 1;
|
|
}
|
|
else {
|
|
paramResult.optionalNum = paramResult.optionalNum + 1;
|
|
}
|
|
}
|
|
}
|
|
return paramResult;
|
|
}
|
|
hasNestedObjectInSchema(schema) {
|
|
if (schema.type === "object") {
|
|
for (const property in schema.properties) {
|
|
const nestedSchema = schema.properties[property];
|
|
if (nestedSchema.type === "object") {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Copyright (c) Microsoft Corporation.
|
|
class CopilotValidator extends Validator {
|
|
constructor(spec, options) {
|
|
super();
|
|
this.projectType = ProjectType.Copilot;
|
|
this.options = options;
|
|
this.spec = spec;
|
|
}
|
|
validateSpec() {
|
|
const result = { errors: [], warnings: [] };
|
|
// validate spec version
|
|
let validationResult = this.validateSpecVersion();
|
|
result.errors.push(...validationResult.errors);
|
|
// validate spec server
|
|
validationResult = this.validateSpecServer();
|
|
result.errors.push(...validationResult.errors);
|
|
// validate no supported API
|
|
validationResult = this.validateSpecNoSupportAPI();
|
|
result.errors.push(...validationResult.errors);
|
|
// validate operationId missing
|
|
validationResult = this.validateSpecOperationId();
|
|
result.warnings.push(...validationResult.warnings);
|
|
return result;
|
|
}
|
|
validateAPI(method, path) {
|
|
const result = { isValid: true, reason: [] };
|
|
method = method.toLocaleLowerCase();
|
|
// validate method and path
|
|
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
if (!methodAndPathResult.isValid) {
|
|
return methodAndPathResult;
|
|
}
|
|
const operationObject = this.spec.paths[path][method];
|
|
// validate auth
|
|
const authCheckResult = this.validateAuth(method, path);
|
|
result.reason.push(...authCheckResult.reason);
|
|
// validate operationId
|
|
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
result.reason.push(ErrorType.MissingOperationId);
|
|
}
|
|
// validate server
|
|
const validateServerResult = this.validateServer(method, path);
|
|
result.reason.push(...validateServerResult.reason);
|
|
// validate response
|
|
const validateResponseResult = this.validateResponse(method, path);
|
|
result.reason.push(...validateResponseResult.reason);
|
|
// validate requestBody
|
|
const requestBody = operationObject.requestBody;
|
|
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
if (requestJsonBody) {
|
|
const requestBodySchema = requestJsonBody.schema;
|
|
if (requestBodySchema.type !== "object") {
|
|
result.reason.push(ErrorType.PostBodySchemaIsNotJson);
|
|
}
|
|
const requestBodyParamResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
result.reason.push(...requestBodyParamResult.reason);
|
|
}
|
|
// validate parameters
|
|
const paramObject = operationObject.parameters;
|
|
const paramResult = this.checkParamSchema(paramObject);
|
|
result.reason.push(...paramResult.reason);
|
|
if (result.reason.length > 0) {
|
|
result.isValid = false;
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
// Copyright (c) Microsoft Corporation.
|
|
class SMEValidator extends Validator {
|
|
constructor(spec, options) {
|
|
super();
|
|
this.projectType = ProjectType.SME;
|
|
this.options = options;
|
|
this.spec = spec;
|
|
}
|
|
validateSpec() {
|
|
const result = { errors: [], warnings: [] };
|
|
// validate spec version
|
|
let validationResult = this.validateSpecVersion();
|
|
result.errors.push(...validationResult.errors);
|
|
// validate spec server
|
|
validationResult = this.validateSpecServer();
|
|
result.errors.push(...validationResult.errors);
|
|
// validate no supported API
|
|
validationResult = this.validateSpecNoSupportAPI();
|
|
result.errors.push(...validationResult.errors);
|
|
// validate operationId missing
|
|
if (this.options.allowMissingId) {
|
|
validationResult = this.validateSpecOperationId();
|
|
result.warnings.push(...validationResult.warnings);
|
|
}
|
|
return result;
|
|
}
|
|
validateAPI(method, path) {
|
|
const result = { isValid: true, reason: [] };
|
|
method = method.toLocaleLowerCase();
|
|
// validate method and path
|
|
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
if (!methodAndPathResult.isValid) {
|
|
return methodAndPathResult;
|
|
}
|
|
const operationObject = this.spec.paths[path][method];
|
|
// validate auth
|
|
const authCheckResult = this.validateAuth(method, path);
|
|
result.reason.push(...authCheckResult.reason);
|
|
// validate operationId
|
|
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
result.reason.push(ErrorType.MissingOperationId);
|
|
}
|
|
// validate server
|
|
const validateServerResult = this.validateServer(method, path);
|
|
result.reason.push(...validateServerResult.reason);
|
|
// validate response
|
|
const validateResponseResult = this.validateResponse(method, path);
|
|
result.reason.push(...validateResponseResult.reason);
|
|
let postBodyResult = {
|
|
requiredNum: 0,
|
|
optionalNum: 0,
|
|
isValid: true,
|
|
reason: [],
|
|
};
|
|
// validate requestBody
|
|
const requestBody = operationObject.requestBody;
|
|
const requestJsonBody = requestBody === null || requestBody === void 0 ? void 0 : requestBody.content["application/json"];
|
|
if (Utils.containMultipleMediaTypes(requestBody)) {
|
|
result.reason.push(ErrorType.PostBodyContainMultipleMediaTypes);
|
|
}
|
|
if (requestJsonBody) {
|
|
const requestBodySchema = requestJsonBody.schema;
|
|
postBodyResult = this.checkPostBodySchema(requestBodySchema, requestBody.required);
|
|
result.reason.push(...postBodyResult.reason);
|
|
}
|
|
// validate parameters
|
|
const paramObject = operationObject.parameters;
|
|
const paramResult = this.checkParamSchema(paramObject);
|
|
result.reason.push(...paramResult.reason);
|
|
// validate total parameters count
|
|
if (paramResult.isValid && postBodyResult.isValid) {
|
|
const paramCountResult = this.validateParamCount(postBodyResult, paramResult);
|
|
result.reason.push(...paramCountResult.reason);
|
|
}
|
|
if (result.reason.length > 0) {
|
|
result.isValid = false;
|
|
}
|
|
return result;
|
|
}
|
|
validateParamCount(postBodyResult, paramResult) {
|
|
const result = { isValid: true, reason: [] };
|
|
const totalRequiredParams = postBodyResult.requiredNum + paramResult.requiredNum;
|
|
const totalParams = totalRequiredParams + postBodyResult.optionalNum + paramResult.optionalNum;
|
|
if (totalRequiredParams > 1) {
|
|
if (!this.options.allowMultipleParameters ||
|
|
totalRequiredParams > SMEValidator.SMERequiredParamsMaxNum) {
|
|
result.reason.push(ErrorType.ExceededRequiredParamsLimit);
|
|
}
|
|
}
|
|
else if (totalParams === 0) {
|
|
result.reason.push(ErrorType.NoParameter);
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
SMEValidator.SMERequiredParamsMaxNum = 5;
|
|
|
|
// Copyright (c) Microsoft Corporation.
|
|
class TeamsAIValidator extends Validator {
|
|
constructor(spec, options) {
|
|
super();
|
|
this.projectType = ProjectType.TeamsAi;
|
|
this.options = options;
|
|
this.spec = spec;
|
|
}
|
|
validateSpec() {
|
|
const result = { errors: [], warnings: [] };
|
|
// validate spec server
|
|
let validationResult = this.validateSpecServer();
|
|
result.errors.push(...validationResult.errors);
|
|
// validate no supported API
|
|
validationResult = this.validateSpecNoSupportAPI();
|
|
result.errors.push(...validationResult.errors);
|
|
return result;
|
|
}
|
|
validateAPI(method, path) {
|
|
const result = { isValid: true, reason: [] };
|
|
method = method.toLocaleLowerCase();
|
|
// validate method and path
|
|
const methodAndPathResult = this.validateMethodAndPath(method, path);
|
|
if (!methodAndPathResult.isValid) {
|
|
return methodAndPathResult;
|
|
}
|
|
const operationObject = this.spec.paths[path][method];
|
|
// validate operationId
|
|
if (!this.options.allowMissingId && !operationObject.operationId) {
|
|
result.reason.push(ErrorType.MissingOperationId);
|
|
}
|
|
// validate server
|
|
const validateServerResult = this.validateServer(method, path);
|
|
result.reason.push(...validateServerResult.reason);
|
|
if (result.reason.length > 0) {
|
|
result.isValid = false;
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
class ValidatorFactory {
|
|
static create(spec, options) {
|
|
var _a;
|
|
const type = (_a = options.projectType) !== null && _a !== void 0 ? _a : ProjectType.SME;
|
|
switch (type) {
|
|
case ProjectType.SME:
|
|
return new SMEValidator(spec, options);
|
|
case ProjectType.Copilot:
|
|
return new CopilotValidator(spec, options);
|
|
case ProjectType.TeamsAi:
|
|
return new TeamsAIValidator(spec, options);
|
|
default:
|
|
throw new Error(`Invalid project type: ${type}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Copyright (c) Microsoft Corporation.
|
|
/**
|
|
* A class that parses an OpenAPI specification file and provides methods to validate, list, and generate artifacts.
|
|
*/
|
|
class SpecParser {
|
|
/**
|
|
* Creates a new instance of the SpecParser class.
|
|
* @param pathOrDoc The path to the OpenAPI specification file or the OpenAPI specification object.
|
|
* @param options The options for parsing the OpenAPI specification file.
|
|
*/
|
|
constructor(pathOrDoc, options) {
|
|
this.defaultOptions = {
|
|
allowMissingId: false,
|
|
allowSwagger: false,
|
|
allowAPIKeyAuth: false,
|
|
allowMultipleParameters: false,
|
|
allowBearerTokenAuth: false,
|
|
allowOauth2: false,
|
|
allowMethods: ["get", "post"],
|
|
allowConversationStarters: false,
|
|
allowResponseSemantics: false,
|
|
allowConfirmation: false,
|
|
projectType: ProjectType.SME,
|
|
isGptPlugin: false,
|
|
};
|
|
this.pathOrSpec = pathOrDoc;
|
|
this.parser = new SwaggerParser();
|
|
this.options = Object.assign(Object.assign({}, this.defaultOptions), (options !== null && options !== void 0 ? options : {}));
|
|
}
|
|
/**
|
|
* Validates the OpenAPI specification file and returns a validation result.
|
|
*
|
|
* @returns A validation result object that contains information about any errors or warnings in the specification file.
|
|
*/
|
|
async validate() {
|
|
try {
|
|
try {
|
|
await this.loadSpec();
|
|
await this.parser.validate(this.spec, {
|
|
validate: {
|
|
schema: false,
|
|
},
|
|
});
|
|
}
|
|
catch (e) {
|
|
return {
|
|
status: ValidationStatus.Error,
|
|
warnings: [],
|
|
errors: [{ type: ErrorType.SpecNotValid, content: e.toString() }],
|
|
};
|
|
}
|
|
const errors = [];
|
|
const warnings = [];
|
|
if (!this.options.allowSwagger && this.isSwaggerFile) {
|
|
return {
|
|
status: ValidationStatus.Error,
|
|
warnings: [],
|
|
errors: [
|
|
{
|
|
type: ErrorType.SwaggerNotSupported,
|
|
content: ConstantString.SwaggerNotSupported,
|
|
},
|
|
],
|
|
};
|
|
}
|
|
// Remote reference not supported
|
|
const refPaths = this.parser.$refs.paths();
|
|
// refPaths [0] is the current spec file path
|
|
if (refPaths.length > 1) {
|
|
errors.push({
|
|
type: ErrorType.RemoteRefNotSupported,
|
|
content: Utils.format(ConstantString.RemoteRefNotSupported, refPaths.join(", ")),
|
|
data: refPaths,
|
|
});
|
|
}
|
|
const validator = this.getValidator(this.spec);
|
|
const validationResult = validator.validateSpec();
|
|
warnings.push(...validationResult.warnings);
|
|
errors.push(...validationResult.errors);
|
|
let status = ValidationStatus.Valid;
|
|
if (warnings.length > 0 && errors.length === 0) {
|
|
status = ValidationStatus.Warning;
|
|
}
|
|
else if (errors.length > 0) {
|
|
status = ValidationStatus.Error;
|
|
}
|
|
return {
|
|
status: status,
|
|
warnings: warnings,
|
|
errors: errors,
|
|
};
|
|
}
|
|
catch (err) {
|
|
throw new SpecParserError(err.toString(), ErrorType.ValidateFailed);
|
|
}
|
|
}
|
|
async listSupportedAPIInfo() {
|
|
try {
|
|
await this.loadSpec();
|
|
const apiMap = this.getAPIs(this.spec);
|
|
const apiInfos = [];
|
|
for (const key in apiMap) {
|
|
const { operation, isValid } = apiMap[key];
|
|
if (!isValid) {
|
|
continue;
|
|
}
|
|
const [method, path] = key.split(" ");
|
|
const operationId = operation.operationId;
|
|
// In Browser environment, this api is by default not support api without operationId
|
|
if (!operationId) {
|
|
continue;
|
|
}
|
|
const command = Utils.parseApiInfo(operation, this.options);
|
|
const apiInfo = {
|
|
method: method,
|
|
path: path,
|
|
title: command.title,
|
|
id: operationId,
|
|
parameters: command.parameters,
|
|
description: command.description,
|
|
};
|
|
apiInfos.push(apiInfo);
|
|
}
|
|
return apiInfos;
|
|
}
|
|
catch (err) {
|
|
throw new SpecParserError(err.toString(), ErrorType.listSupportedAPIInfoFailed);
|
|
}
|
|
}
|
|
/**
|
|
* Lists all the OpenAPI operations in the specification file.
|
|
* @returns A string array that represents the HTTP method and path of each operation, such as ['GET /pets/{petId}', 'GET /user/{userId}']
|
|
* according to copilot plugin spec, only list get and post method without auth
|
|
*/
|
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
async list() {
|
|
throw new Error("Method not implemented.");
|
|
}
|
|
/**
|
|
* Generate specs according to the filters.
|
|
* @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
|
|
*/
|
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
async getFilteredSpecs(filter, signal) {
|
|
throw new Error("Method not implemented.");
|
|
}
|
|
/**
|
|
* Generates and update artifacts from the OpenAPI specification file. Generate Adaptive Cards, update Teams app manifest, and generate a new OpenAPI specification file.
|
|
* @param manifestPath A file path of the Teams app manifest file to update.
|
|
* @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
|
|
* @param outputSpecPath File path of the new OpenAPI specification file to generate. If not specified or empty, no spec file will be generated.
|
|
* @param pluginFilePath File path of the api plugin file to generate.
|
|
*/
|
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
async generateForCopilot(manifestPath, filter, outputSpecPath, pluginFilePath, signal) {
|
|
throw new Error("Method not implemented.");
|
|
}
|
|
/**
|
|
* Generates and update artifacts from the OpenAPI specification file. Generate Adaptive Cards, update Teams app manifest, and generate a new OpenAPI specification file.
|
|
* @param manifestPath A file path of the Teams app manifest file to update.
|
|
* @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
|
|
* @param outputSpecPath File path of the new OpenAPI specification file to generate. If not specified or empty, no spec file will be generated.
|
|
* @param adaptiveCardFolder Folder path where the Adaptive Card files will be generated. If not specified or empty, Adaptive Card files will not be generated.
|
|
* @param isMe Boolean that indicates whether the project is an Messaging Extension. For Messaging Extension, composeExtensions will be added in Teams app manifest.
|
|
*/
|
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
async generate(manifestPath, filter, outputSpecPath, adaptiveCardFolder, signal) {
|
|
throw new Error("Method not implemented.");
|
|
}
|
|
async loadSpec() {
|
|
if (!this.spec) {
|
|
this.unResolveSpec = (await this.parser.parse(this.pathOrSpec));
|
|
if (!this.unResolveSpec.openapi && this.unResolveSpec.swagger === "2.0") {
|
|
this.isSwaggerFile = true;
|
|
}
|
|
const clonedUnResolveSpec = JSON.parse(JSON.stringify(this.unResolveSpec));
|
|
this.spec = (await this.parser.dereference(clonedUnResolveSpec));
|
|
}
|
|
}
|
|
getAPIs(spec) {
|
|
const validator = this.getValidator(spec);
|
|
const apiMap = validator.listAPIs();
|
|
return apiMap;
|
|
}
|
|
getValidator(spec) {
|
|
if (this.validator) {
|
|
return this.validator;
|
|
}
|
|
const validator = ValidatorFactory.create(spec, this.options);
|
|
this.validator = validator;
|
|
return validator;
|
|
}
|
|
}
|
|
|
|
// Copyright (c) Microsoft Corporation.
|
|
class AdaptiveCardGenerator {
|
|
static generateAdaptiveCard(operationItem) {
|
|
try {
|
|
const { json } = Utils.getResponseJson(operationItem);
|
|
let cardBody = [];
|
|
let schema = json.schema;
|
|
let jsonPath = "$";
|
|
if (schema && Object.keys(schema).length > 0) {
|
|
jsonPath = AdaptiveCardGenerator.getResponseJsonPathFromSchema(schema);
|
|
if (jsonPath !== "$") {
|
|
schema = schema.properties[jsonPath];
|
|
}
|
|
cardBody = AdaptiveCardGenerator.generateCardFromResponse(schema, "");
|
|
}
|
|
// if no schema, try to use example value
|
|
if (cardBody.length === 0 && (json.examples || json.example)) {
|
|
cardBody = [
|
|
{
|
|
type: ConstantString.TextBlockType,
|
|
text: "${jsonStringify($root)}",
|
|
wrap: true,
|
|
},
|
|
];
|
|
}
|
|
// if no example value, use default success response
|
|
if (cardBody.length === 0) {
|
|
cardBody = [
|
|
{
|
|
type: ConstantString.TextBlockType,
|
|
text: "success",
|
|
wrap: true,
|
|
},
|
|
];
|
|
}
|
|
const fullCard = {
|
|
type: ConstantString.AdaptiveCardType,
|
|
$schema: ConstantString.AdaptiveCardSchema,
|
|
version: ConstantString.AdaptiveCardVersion,
|
|
body: cardBody,
|
|
};
|
|
return [fullCard, jsonPath];
|
|
}
|
|
catch (err) {
|
|
throw new SpecParserError(err.toString(), ErrorType.GenerateAdaptiveCardFailed);
|
|
}
|
|
}
|
|
static generateCardFromResponse(schema, name, parentArrayName = "") {
|
|
if (schema.type === "array") {
|
|
// schema.items can be arbitrary object: schema { type: array, items: {} }
|
|
if (Object.keys(schema.items).length === 0) {
|
|
return [
|
|
{
|
|
type: ConstantString.TextBlockType,
|
|
text: name ? `${name}: \${jsonStringify(${name})}` : "result: ${jsonStringify($root)}",
|
|
wrap: true,
|
|
},
|
|
];
|
|
}
|
|
const obj = AdaptiveCardGenerator.generateCardFromResponse(schema.items, "", name);
|
|
const template = {
|
|
type: ConstantString.ContainerType,
|
|
$data: name ? `\${${name}}` : "${$root}",
|
|
items: Array(),
|
|
};
|
|
template.items.push(...obj);
|
|
return [template];
|
|
}
|
|
// some schema may not contain type but contain properties
|
|
if (schema.type === "object" || (!schema.type && schema.properties)) {
|
|
const { properties } = schema;
|
|
const result = [];
|
|
for (const property in properties) {
|
|
const obj = AdaptiveCardGenerator.generateCardFromResponse(properties[property], name ? `${name}.${property}` : property, parentArrayName);
|
|
result.push(...obj);
|
|
}
|
|
if (schema.additionalProperties) {
|
|
// TODO: better ways to handler warnings.
|
|
console.warn(ConstantString.AdditionalPropertiesNotSupported);
|
|
}
|
|
return result;
|
|
}
|
|
if (schema.type === "string" ||
|
|
schema.type === "integer" ||
|
|
schema.type === "boolean" ||
|
|
schema.type === "number") {
|
|
if (!AdaptiveCardGenerator.isImageUrlProperty(schema, name, parentArrayName)) {
|
|
// string in root: "ddd"
|
|
let text = "result: ${$root}";
|
|
if (name) {
|
|
// object { id: "1" }
|
|
text = `${name}: \${if(${name}, ${name}, 'N/A')}`;
|
|
if (parentArrayName) {
|
|
// object types inside array: { tags: ["id": 1, "name": "name"] }
|
|
text = `${parentArrayName}.${text}`;
|
|
}
|
|
}
|
|
else if (parentArrayName) {
|
|
// string array: photoUrls: ["1", "2"]
|
|
text = `${parentArrayName}: ` + "${$data}";
|
|
}
|
|
return [
|
|
{
|
|
type: ConstantString.TextBlockType,
|
|
text,
|
|
wrap: true,
|
|
},
|
|
];
|
|
}
|
|
else {
|
|
if (name) {
|
|
return [
|
|
{
|
|
type: "Image",
|
|
url: `\${${name}}`,
|
|
$when: `\${${name} != null}`,
|
|
},
|
|
];
|
|
}
|
|
else {
|
|
return [
|
|
{
|
|
type: "Image",
|
|
url: "${$data}",
|
|
$when: "${$data != null}",
|
|
},
|
|
];
|
|
}
|
|
}
|
|
}
|
|
if (schema.oneOf || schema.anyOf || schema.not || schema.allOf) {
|
|
throw new Error(Utils.format(ConstantString.SchemaNotSupported, JSON.stringify(schema)));
|
|
}
|
|
throw new Error(Utils.format(ConstantString.UnknownSchema, JSON.stringify(schema)));
|
|
}
|
|
// Find the first array property in the response schema object with the well-known name
|
|
static getResponseJsonPathFromSchema(schema) {
|
|
if (schema.type === "object" || (!schema.type && schema.properties)) {
|
|
const { properties } = schema;
|
|
for (const property in properties) {
|
|
const schema = properties[property];
|
|
if (schema.type === "array" &&
|
|
Utils.isWellKnownName(property, ConstantString.WellknownResultNames)) {
|
|
return property;
|
|
}
|
|
}
|
|
}
|
|
return "$";
|
|
}
|
|
static isImageUrlProperty(schema, name, parentArrayName) {
|
|
const propertyName = name ? name : parentArrayName;
|
|
return (!!propertyName &&
|
|
schema.type === "string" &&
|
|
Utils.isWellKnownName(propertyName, ConstantString.WellknownImageName) &&
|
|
(propertyName.toLocaleLowerCase().indexOf("url") >= 0 || schema.format === "uri"));
|
|
}
|
|
}
|
|
|
|
export { AdaptiveCardGenerator, ConstantString, ErrorType, SpecParser, SpecParserError, Utils, ValidationStatus, WarningType };
|
|
//# sourceMappingURL=index.esm2017.js.map
|