/**
* @fileoverview AnalysisRun mapper
*
* Maps between DeepSource API models and domain AnalysisRun aggregates.
*/
import { AnalysisRun } from '../../domain/aggregates/analysis-run/analysis-run.aggregate.js';
import { DeepSourceRun } from '../../models/runs.js';
import {
CommitInfo,
RunSummary,
RunStatus,
RunTimestamps,
AnalyzerDistribution,
CategoryDistribution,
IssueCategory,
} from '../../domain/aggregates/analysis-run/analysis-run.types.js';
import {
asRunId,
asProjectKey,
asCommitOid,
asBranchName,
asAnalyzerShortcode,
asGraphQLNodeId,
} from '../../types/branded.js';
import { IssueCount } from '../../domain/value-objects/issue-count.js';
/**
* Maps API status to domain status
*
* @param apiStatus - The API status
* @returns The domain status
*/
function mapApiStatusToDomain(apiStatus: string): RunStatus {
// API uses different status names than domain
const statusMap: Record<string, RunStatus> = {
PENDING: 'PENDING',
READY: 'RUNNING',
SUCCESS: 'SUCCESS',
FAILURE: 'FAILURE',
TIMEOUT: 'TIMEOUT',
CANCEL: 'CANCELLED',
CANCELLED: 'CANCELLED',
SKIPPED: 'SKIPPED',
};
return statusMap[apiStatus] || 'FAILURE';
}
/**
* Maps API category to domain category
*
* @param apiCategory - The API category
* @returns The domain category
*/
function mapApiCategoryToDomain(apiCategory: string): IssueCategory {
// Map API categories to domain categories
const categoryMap: Record<string, IssueCategory> = {
'anti-pattern': 'ANTI_PATTERN',
'bug-risk': 'BUG_RISK',
bug_risk: 'BUG_RISK', // Support both hyphen and underscore
coverage: 'COVERAGE',
documentation: 'DOCUMENTATION',
performance: 'PERFORMANCE',
security: 'SECURITY',
style: 'STYLE',
typecheck: 'TYPE_CHECK',
'type-check': 'TYPE_CHECK', // Support both forms
};
return categoryMap[apiCategory.toLowerCase()] || 'OTHER';
}
/**
* Maps a DeepSource API run to a domain AnalysisRun aggregate
*
* @param apiRun - The API run model
* @param projectKey - The project key (not available in API response)
* @returns The domain AnalysisRun aggregate
*/
export function mapAnalysisRunToDomain(apiRun: DeepSourceRun, projectKey: string): AnalysisRun {
const commitInfo: CommitInfo = {
oid: asCommitOid(apiRun.commitOid),
branch: asBranchName(apiRun.branchName),
baseOid: asCommitOid(apiRun.baseOid),
};
// Map API status to domain status
const status = mapApiStatusToDomain(apiRun.status);
// Map issue distributions
const analyzerDistributions: AnalyzerDistribution[] =
apiRun.summary.occurrenceDistributionByAnalyzer?.map((dist) => ({
analyzerShortcode: asAnalyzerShortcode(dist.analyzerShortcode),
introduced: IssueCount.create(dist.introduced),
resolved: IssueCount.create(0), // API doesn't provide resolved by analyzer
suppressed: IssueCount.create(0), // API doesn't provide suppressed by analyzer
})) || [];
const categoryDistributions: CategoryDistribution[] =
apiRun.summary.occurrenceDistributionByCategory?.map((dist) => ({
category: mapApiCategoryToDomain(dist.category) as IssueCategory,
introduced: IssueCount.create(dist.introduced),
resolved: IssueCount.create(0), // API doesn't provide resolved by category
suppressed: IssueCount.create(0), // API doesn't provide suppressed by category
})) || [];
const summary: RunSummary = {
totalIntroduced: IssueCount.create(apiRun.summary.occurrencesIntroduced),
totalResolved: IssueCount.create(apiRun.summary.occurrencesResolved),
totalSuppressed: IssueCount.create(apiRun.summary.occurrencesSuppressed),
byAnalyzer: analyzerDistributions,
byCategory: categoryDistributions,
};
const run = AnalysisRun.fromPersistence({
runId: asRunId(apiRun.runUid),
projectKey: asProjectKey(projectKey),
repositoryId: asGraphQLNodeId(apiRun.repository.id),
commitInfo,
status,
timestamps: (() => {
const ts: RunTimestamps = {
createdAt: new Date(apiRun.createdAt),
};
if (status !== 'PENDING') {
ts.startedAt = new Date(apiRun.updatedAt);
}
if (apiRun.finishedAt) {
ts.finishedAt = new Date(apiRun.finishedAt);
}
return ts;
})(),
summary,
issues: [], // Issues are fetched separately if needed
});
return run;
}
/**
* Maps a domain AnalysisRun aggregate to persistence format
*
* @param run - The domain AnalysisRun aggregate
* @returns The persistence model
*/
export function mapAnalysisRunToPersistence(run: AnalysisRun): {
runId: string;
projectKey: string;
repositoryId: string;
commitInfo: CommitInfo;
status: RunStatus;
timestamps: {
createdAt: Date;
startedAt?: Date;
finishedAt?: Date;
};
summary?: RunSummary;
issues: Array<{
id: string;
issueCode: string;
analyzerShortcode: string;
category: string;
severity: string;
message: string;
path: string;
line?: number;
column?: number;
}>;
} {
return run.toPersistence();
}
/**
* Maps multiple API runs to domain aggregates
*
* @param apiRuns - Array of API run models
* @param projectKey - The project key for all runs
* @returns Array of domain AnalysisRun aggregates
*/
export function mapAnalysisRunsToDomainList(
apiRuns: DeepSourceRun[],
projectKey: string
): AnalysisRun[] {
return apiRuns.map((run) => mapAnalysisRunToDomain(run, projectKey));
}
// For backward compatibility, export a namespace with the old static methods
export const AnalysisRunMapper = {
toDomain: mapAnalysisRunToDomain,
toPersistence: mapAnalysisRunToPersistence,
toDomainList: mapAnalysisRunsToDomainList,
};