import { DeepSourceClient, ReportType, ReportStatus } from '../deepsource.js';
import { BaseHandlerDeps } from './base/handler.interface.js';
import {
createBaseHandlerFactory,
wrapInApiResponse,
createDefaultHandlerDeps,
} from './base/handler.factory.js';
import { createLogger, Logger } from '../utils/logging/logger.js';
import { IComplianceReportRepository } from '../domain/aggregates/compliance-report/compliance-report.repository.js';
import { RepositoryFactory } from '../infrastructure/factories/repository.factory.js';
import { asProjectKey } from '../types/branded.js';
// Logger for the compliance reports handler
const logger = createLogger('ComplianceReportsHandler');
/**
* Interface for parameters for getting a compliance report
* @public
*/
export interface DeepsourceComplianceReportParams {
/** DeepSource project key to identify the project */
projectKey: string;
/** Type of compliance report to fetch */
reportType: ReportType;
}
/**
* Extended dependencies interface for compliance report handler
*/
interface ComplianceReportHandlerDeps {
complianceReportRepository: IComplianceReportRepository;
logger: Logger;
}
/**
* Creates a compliance report handler with injected dependencies
* @param deps - The dependencies for the handler
* @returns The configured handler function
*/
export function createComplianceReportHandlerWithRepo(deps: ComplianceReportHandlerDeps) {
return async function handleComplianceReport(params: DeepsourceComplianceReportParams) {
try {
const { projectKey, reportType } = params;
const projectKeyBranded = asProjectKey(projectKey);
deps.logger.info('Fetching compliance report from repository', { projectKey, reportType });
// Get the compliance report from repository
const domainReport = await deps.complianceReportRepository.findByProjectAndType(
projectKeyBranded,
reportType
);
if (!domainReport) {
throw new Error(`Report of type '${reportType}' not found for project '${projectKey}'`);
}
deps.logger.info('Successfully fetched compliance report', {
projectKey,
reportType,
status: domainReport.status,
});
const reportData = {
key: `${domainReport.projectKey}:${domainReport.reportType}`,
title: `${domainReport.reportType} Compliance Report`,
currentValue: domainReport.summary.complianceScore.value,
status:
domainReport.status === 'READY'
? 'PASSING'
: domainReport.status === 'ERROR'
? 'FAILING'
: 'NOT_APPLICABLE',
securityIssueStats: domainReport.categories.map((category) => ({
key: category.name,
title: category.name,
occurrence: {
critical: category.severity === 'CRITICAL' ? category.nonCompliant.count : 0,
major: category.severity === 'MAJOR' ? category.nonCompliant.count : 0,
minor: category.severity === 'INFO' ? category.nonCompliant.count : 0,
total: category.issueCount.count,
},
})),
trends: domainReport.trend ? [domainReport.trend] : [],
// Include helpful analysis of the report
analysis: {
summary: `This report shows compliance with ${domainReport.reportType} security standards.`,
status_explanation:
domainReport.status === 'READY'
? 'Your project is currently meeting all required security standards.'
: domainReport.status === 'ERROR'
? 'Your project has security issues that need to be addressed to meet compliance standards.'
: 'This report is not applicable to your project.',
critical_issues: domainReport.summary.severityDistribution.critical.count,
major_issues: domainReport.summary.severityDistribution.major.count,
minor_issues: domainReport.summary.severityDistribution.info.count,
total_issues: domainReport.summary.totalIssues.count,
},
// Include recommendations based on the report
recommendations: {
actions:
domainReport.status === 'ERROR'
? [
'Fix critical security issues first',
'Use project_issues to view specific issues',
'Implement security best practices for your codebase',
]
: ['Continue monitoring security compliance', 'Run regular security scans'],
resources: [
reportType === ReportType.OWASP_TOP_10
? 'OWASP Top 10: https://owasp.org/www-project-top-ten/'
: reportType === ReportType.SANS_TOP_25
? 'SANS Top 25: https://www.sans.org/top25-software-errors/'
: reportType === ReportType.MISRA_C
? 'MISRA-C: https://www.misra.org.uk/'
: 'Security best practices for your project',
],
},
};
return {
content: [
{
type: 'text' as const,
text: JSON.stringify(reportData),
},
],
};
} catch (error) {
deps.logger.error('Error in handleComplianceReport', {
errorType: typeof error,
errorName: error instanceof Error ? error.name : 'Unknown',
errorMessage: error instanceof Error ? error.message : String(error),
errorStack: error instanceof Error ? error.stack : 'No stack available',
});
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
deps.logger.debug('Returning error response', { errorMessage });
return {
isError: true,
content: [
{
type: 'text' as const,
text: JSON.stringify({
error: errorMessage,
details: 'Failed to retrieve compliance report',
}),
},
],
};
}
};
}
// Keep the old handler for backward compatibility with the base handler pattern
/**
* Creates a compliance report handler with injected dependencies
* @param deps - The dependencies for the handler
* @returns The configured handler factory
*/
export const createComplianceReportHandler = createBaseHandlerFactory(
'compliance_report',
async (deps: BaseHandlerDeps, { projectKey, reportType }: DeepsourceComplianceReportParams) => {
const apiKey = deps.getApiKey();
deps.logger.debug('API key retrieved from config', {
length: apiKey.length,
prefix: `${apiKey.substring(0, 5)}...`,
});
const client = new DeepSourceClient(apiKey);
deps.logger.info('Fetching compliance report', { projectKey, reportType });
const report = await client.getComplianceReport(projectKey, reportType);
if (!report) {
throw new Error(`Report of type '${reportType}' not found for project '${projectKey}'`);
}
deps.logger.info('Successfully fetched compliance report', {
projectKey,
reportType,
status: report.status,
});
const reportData = {
key: report.key,
title: report.title,
currentValue: report.currentValue,
status: report.status,
securityIssueStats: report.securityIssueStats.map((stat) => ({
key: stat.key,
title: stat.title,
occurrence: {
critical: stat.occurrence.critical,
major: stat.occurrence.major,
minor: stat.occurrence.minor,
total: stat.occurrence.total,
},
})),
trends: report.trends,
// Include helpful analysis of the report
analysis: {
summary: `This report shows compliance with ${report.title} security standards.`,
status_explanation:
report.status === ReportStatus.PASSING
? 'Your project is currently meeting all required security standards.'
: report.status === ReportStatus.FAILING
? 'Your project has security issues that need to be addressed to meet compliance standards.'
: 'This report is not applicable to your project.',
critical_issues: report.securityIssueStats.reduce(
(total, stat) => total + (stat.occurrence.critical || 0),
0
),
major_issues: report.securityIssueStats.reduce(
(total, stat) => total + (stat.occurrence.major || 0),
0
),
minor_issues: report.securityIssueStats.reduce(
(total, stat) => total + (stat.occurrence.minor || 0),
0
),
total_issues: report.securityIssueStats.reduce(
(total, stat) => total + (stat.occurrence.total || 0),
0
),
},
// Include recommendations based on the report
recommendations: {
actions:
report.status === ReportStatus.FAILING
? [
'Fix critical security issues first',
'Use project_issues to view specific issues',
'Implement security best practices for your codebase',
]
: ['Continue monitoring security compliance', 'Run regular security scans'],
resources: [
reportType === ReportType.OWASP_TOP_10
? 'OWASP Top 10: https://owasp.org/www-project-top-ten/'
: reportType === ReportType.SANS_TOP_25
? 'SANS Top 25: https://www.sans.org/top25-software-errors/'
: reportType === ReportType.MISRA_C
? 'MISRA-C: https://www.misra.org.uk/'
: 'Security best practices for your project',
],
},
};
return wrapInApiResponse(reportData);
}
);
/**
* Fetches and returns compliance reports from a DeepSource project using domain aggregates
* @param params - Parameters for fetching the compliance report, including project key and report type
* @returns Response containing the compliance report with security issues statistics
* @throws Error if the DEEPSOURCE_API_KEY environment variable is not set or if the report type is unsupported or if API call fails
* @public
*/
export async function handleDeepsourceComplianceReport(params: DeepsourceComplianceReportParams) {
const baseDeps = createDefaultHandlerDeps({ logger });
const apiKey = baseDeps.getApiKey();
const repositoryFactory = new RepositoryFactory({ apiKey });
const complianceReportRepository = repositoryFactory.createComplianceReportRepository();
const deps: ComplianceReportHandlerDeps = {
complianceReportRepository,
logger,
};
const handler = createComplianceReportHandlerWithRepo(deps);
const result = await handler(params);
// If the domain handler returned an error response, throw an error for backward compatibility
if (result.isError) {
const firstContent = result.content[0];
if (firstContent) {
const errorData = JSON.parse(firstContent.text);
throw new Error(errorData.error);
} else {
throw new Error('Unknown compliance report error');
}
}
return result;
}