import { CompassComponentType, CreateLinkInput, } from '@atlassian/forge-graphql-types'; import { invalidLinkKeyErrorMessage, invalidLinkNameErrorMessage, invalidLinkTypeErrorMessage, invalidUrlErrorMessage, invalidUrlSchemeErrorMessage, linksCountOfTypeExceededErrorMessage, maxLinkNameLengthErrorMessage, missingLinkKeyErrorMessage, } from './models/error-messages'; import { LINK_KEYS, LinksInputValidator, MANDATORY_LINK_KEYS, VALID_LINK_TYPES, } from './models/links-input-validator-types'; export class AdditionalLinksValidator implements LinksInputValidator { errors: string[]; constructor() { this.errors = []; } private addError(message: string): void { this.errors.push(message); } private validateLinkUrl(url: any): void { try { const parsedUrl = new URL(url); const scheme = parsedUrl.protocol; // eslint-disable-next-line no-script-url if (scheme === 'javascript:') { this.addError(invalidUrlSchemeErrorMessage(scheme)); } } catch (e) { this.addError(invalidUrlErrorMessage()); } } private validateLinkType( type: any, componentType: CompassComponentType | string, ): void { if ( typeof type !== 'string' || !VALID_LINK_TYPES.includes(type.toUpperCase()) ) { this.addError(invalidLinkTypeErrorMessage(type, VALID_LINK_TYPES)); } } private validateLinkName(maxLinksNameLength: number, name?: any): void { if (name === undefined) { return; } if (typeof name !== 'string') { this.addError(invalidLinkNameErrorMessage()); } if (name.length > maxLinksNameLength) { this.addError(maxLinkNameLengthErrorMessage(maxLinksNameLength)); } } validateLinkKeys( link: CreateLinkInput, componentType: CompassComponentType | string, maxLinksNameLength: number, ): void { const actualKeys = Object.keys(link); const missingMandatoryKeys: string[] = []; MANDATORY_LINK_KEYS.forEach((mandatoryKey) => { if (!actualKeys.includes(mandatoryKey)) { missingMandatoryKeys.push(mandatoryKey); } }); if (missingMandatoryKeys.length > 0) { this.addError(missingLinkKeyErrorMessage(missingMandatoryKeys)); return; } actualKeys.forEach((actualKey) => { if (!LINK_KEYS.includes(actualKey)) { this.addError(invalidLinkKeyErrorMessage(actualKey, LINK_KEYS)); } }); this.validateLinkUrl(link.url); this.validateLinkType(link.type, componentType); this.validateLinkName(maxLinksNameLength, link.name); } validateMaxLinksByType( links: CreateLinkInput[], maxLinksOfType: number, ): void { const linksGroupedByType = links.reduce<{ [key: string]: CreateLinkInput[]; }>((groupedLinks, currentlink) => { if (groupedLinks[currentlink.type]) { groupedLinks[currentlink.type].push(currentlink); return groupedLinks; } // eslint-disable-next-line no-param-reassign groupedLinks[currentlink.type] = [currentlink]; return groupedLinks; }, {}); for (const type in linksGroupedByType) { if (linksGroupedByType[type].length > maxLinksOfType) { this.addError( linksCountOfTypeExceededErrorMessage(type, maxLinksOfType), ); } } } }