Outlook_Addin_LLM/node_modules/@microsoft/m365-spec-parser/dist/index.esm2017.js

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