deepsource-mcp-server
Version:
Model Context Protocol server for DeepSource
631 lines (630 loc) • 19.2 kB
TypeScript
/**
* @fileoverview Discriminated union types for complex state management
*
* This module defines type-safe discriminated union types for various complex state
* management scenarios in the codebase. Discriminated unions help ensure that code
* handling different states properly accounts for all possible variants.
*/
import { ErrorCategory } from '../utils/errors/categories.js';
import { AnalysisRunStatus } from '../models/runs.js';
import { MetricDirection, MetricShortcode, MetricThresholdStatus } from './metrics.js';
import { BranchName, CommitOid, RunId } from './branded.js';
import { ReportType, ReportStatus } from '../deepsource.js';
/**
* -----------------------------------------------------------------------------
* Error Handling Discriminated Unions
* -----------------------------------------------------------------------------
*/
/**
* Base interface for all error types
*/
interface ErrorBase {
/** Message describing the error */
message: string;
/** Error category discriminant */
category: ErrorCategory;
/** Additional metadata for the error */
metadata?: Record<string, unknown>;
}
/**
* Authentication error (invalid API key, expired token, etc.)
*/
export interface AuthError extends ErrorBase {
category: ErrorCategory.AUTH;
/** Token information if available */
token?: {
/** Whether the token was present */
present: boolean;
/** Whether the token was expired */
expired?: boolean;
};
}
/**
* Network error (connection issues, DNS failures, etc.)
*/
export interface NetworkError extends ErrorBase {
category: ErrorCategory.NETWORK;
/** The URL that failed */
url?: string;
/** Information about the connection */
connection?: {
/** Whether a connection was established */
established: boolean;
/** Time spent trying to connect */
timeSpent?: number;
};
}
/**
* Server error (500 errors, backend failures, etc.)
*/
export interface ServerError extends ErrorBase {
category: ErrorCategory.SERVER;
/** HTTP status code if available */
statusCode?: number;
/** Server response if available */
response?: unknown;
}
/**
* Client error (400 errors, invalid input, etc.)
*/
export interface ClientError extends ErrorBase {
category: ErrorCategory.CLIENT;
/** HTTP status code if available */
statusCode?: number;
/** Validation errors if available */
validationErrors?: Record<string, string>;
}
/**
* Timeout error (request took too long)
*/
export interface TimeoutError extends ErrorBase {
category: ErrorCategory.TIMEOUT;
/** The timeout limit in milliseconds */
timeoutLimit?: number;
/** Time spent before timeout */
timeSpent?: number;
}
/**
* Rate limit error (too many requests)
*/
export interface RateLimitError extends ErrorBase {
category: ErrorCategory.RATE_LIMIT;
/** When the rate limit resets */
resetAt?: string;
/** Request limit information */
limit?: {
/** Max requests allowed */
max: number;
/** Remaining requests */
remaining: number;
};
}
/**
* Schema error (GraphQL schema issues)
*/
export interface SchemaError extends ErrorBase {
category: ErrorCategory.SCHEMA;
/** The problematic field or type */
field?: string;
/** Original GraphQL error */
graphqlErrors?: Array<{
message: string;
path?: string[];
}>;
}
/**
* Not found error (resource doesn't exist)
*/
export interface NotFoundError extends ErrorBase {
category: ErrorCategory.NOT_FOUND;
/** Resource that wasn't found */
resource?: string;
/** ID or key that was looked up */
identifier?: string;
}
/**
* Format error (data in unexpected format)
*/
export interface FormatError extends ErrorBase {
category: ErrorCategory.FORMAT;
/** Expected format */
expected?: string;
/** Received format */
received?: string;
}
/**
* Other/unknown errors
*/
export interface OtherError extends ErrorBase {
category: ErrorCategory.OTHER;
}
/**
* Union type for all possible error types
*/
export type ApiError = AuthError | NetworkError | ServerError | ClientError | TimeoutError | RateLimitError | SchemaError | NotFoundError | FormatError | OtherError;
/**
* Type guard to check if an API error belongs to a specific error category
*
* This function performs a runtime check on the error's category discriminant
* to determine if it matches the requested category. Use this with type narrowing
* to access category-specific properties safely.
*
* @example
* ```typescript
* if (isErrorOfCategory(error, ErrorCategory.NOT_FOUND)) {
* // TypeScript knows this is a NotFoundError now
* console.log(error.resource); // Safely access NotFoundError property
* }
* ```
*
* @param error - The API error object to check
* @param category - The error category to test against
* @returns True if the error is of the specified category, false otherwise
* @public
*/
export declare function isErrorOfCategory(error: ApiError, category: ErrorCategory): boolean;
/**
* -----------------------------------------------------------------------------
* API Response Discriminated Unions
* -----------------------------------------------------------------------------
*/
/**
* Base interface for API responses
*/
interface ApiResponseBase {
/** Whether the request was successful */
success: boolean;
}
/**
* Successful API response
*/
export interface SuccessResponse<T> extends ApiResponseBase {
success: true;
/** Response data */
data: T;
}
/**
* Failed API response
*/
export interface ErrorResponse extends ApiResponseBase {
success: false;
/** Error information */
error: ApiError;
}
/**
* Union type for API responses
*/
export type ApiResponse<T> = SuccessResponse<T> | ErrorResponse;
/**
* Type guard to check if an API response was successful
*
* This function enables safe handling of API responses by distinguishing between
* success and error cases in a type-safe manner. It acts as a TypeScript type predicate,
* allowing the compiler to narrow the type when used in conditions.
*
* @example
* ```typescript
* const response = await fetchData();
*
* if (isSuccessResponse(response)) {
* // TypeScript knows this is a SuccessResponse<T> now
* console.log(response.data); // Safely access response.data
* } else {
* // TypeScript knows this is an ErrorResponse now
* console.error(response.error.message); // Safely access error properties
* }
* ```
*
* @param response - The API response to check
* @returns Type predicate indicating whether the response is a successful response
* @typeParam T - The type of data contained in a successful response
* @public
*/
export declare function isSuccessResponse<T>(response: ApiResponse<T>): response is SuccessResponse<T>;
/**
* -----------------------------------------------------------------------------
* Run Status Discriminated Unions
* -----------------------------------------------------------------------------
*/
/**
* Base interface for run states
*/
interface RunStateBase {
/** Run status discriminant */
status: AnalysisRunStatus;
/** Run ID */
runId: RunId;
/** Commit hash */
commitOid: CommitOid;
/** Branch name */
branchName: BranchName;
/** Base commit hash */
baseOid: CommitOid;
/** Timestamp when the run was created */
createdAt: string;
/** Timestamp when the run was last updated */
updatedAt: string;
}
/**
* Run in pending state
*/
export interface PendingRun extends RunStateBase {
status: 'PENDING';
/** Position in queue if available */
queuePosition?: number;
/** Estimated start time if available */
estimatedStartTime?: string;
}
/**
* Run in progress state
*/
export interface RunningRun extends RunStateBase {
status: 'READY';
/** Current progress percentage if available */
progress?: number;
/** Current stage of analysis if available */
currentStage?: string;
/** Estimated completion time if available */
estimatedCompletionTime?: string;
}
/**
* Successful run
*/
export interface SuccessfulRun extends RunStateBase {
status: 'SUCCESS';
/** When the run finished */
finishedAt: string;
/** Summary of results */
summary: {
/** Number of new issues introduced */
occurrencesIntroduced: number;
/** Number of issues resolved */
occurrencesResolved: number;
/** Number of issues suppressed */
occurrencesSuppressed: number;
/** Analyzer distribution */
analyzerDistribution?: Array<{
analyzer: string;
count: number;
}>;
/** Category distribution */
categoryDistribution?: Array<{
category: string;
count: number;
}>;
};
}
/**
* Failed run (error occurred)
*/
export interface FailedRun extends RunStateBase {
status: 'FAILURE';
/** When the run failed */
finishedAt: string;
/** Error information if available */
error?: {
/** Error message */
message: string;
/** Error code if available */
code?: string;
};
}
/**
* Timed out run
*/
export interface TimedOutRun extends RunStateBase {
status: 'TIMEOUT';
/** When the run timed out */
finishedAt: string;
/** Timeout information */
timeout?: {
/** Time limit in seconds */
limitSeconds: number;
/** Partial results if available */
partialResultsAvailable: boolean;
};
}
/**
* Cancelled run
*/
export interface CancelledRun extends RunStateBase {
status: 'CANCEL';
/** When the run was cancelled */
finishedAt: string;
/** Who cancelled the run if available */
cancelledBy?: string;
/** Reason for cancellation if available */
cancellationReason?: string;
}
/**
* Skipped run
*/
export interface SkippedRun extends RunStateBase {
status: 'SKIPPED';
/** When the run was skipped */
finishedAt: string;
/** Reason for skipping */
skipReason?: string;
}
/**
* Union type for run states
*/
export type RunState = PendingRun | RunningRun | SuccessfulRun | FailedRun | TimedOutRun | CancelledRun | SkippedRun;
/**
* Type guard to check if a run is in a specific state
* @param run Run to check
* @param status Status to check for
* @returns Whether the run is in the specified state
*/
/**
* Type guard to check if a run is in a specific state
*
* This generic type guard determines if a run is in a particular state based on
* its status discriminant. It enables type-safe access to state-specific properties
* after TypeScript narrows the type.
*
* @example
* ```typescript
* // Check if run is successful and access success-specific properties
* if (isRunInState<SuccessfulRun>(run, 'SUCCESS')) {
* // TypeScript knows this is a SuccessfulRun now
* console.log(run.finishedAt);
* console.log(run.summary.occurrencesIntroduced);
* }
*
* // Check if run is in progress and access progress-specific properties
* if (isRunInState<RunningRun>(run, 'READY')) {
* // TypeScript knows this is a RunningRun now
* console.log(run.progress);
* console.log(run.currentStage);
* }
* ```
*
* @param run - The run object to check
* @param status - The status to test against
* @returns Type predicate indicating whether the run is in the specified state
* @typeParam T - The specific run state type to check for (must extend RunState)
* @public
*/
export declare function isRunInState<T extends RunState>(run: RunState, status: AnalysisRunStatus): run is T;
/**
* -----------------------------------------------------------------------------
* Report Type Discriminated Unions
* -----------------------------------------------------------------------------
*/
/**
* Base interface for compliance reports
*/
interface ComplianceReportBase {
/** Report type discriminant */
type: ReportType;
/** Report title */
title: string;
/** Current compliance value (percentage or score) */
currentValue: number;
/** Compliance status */
status: ReportStatus;
/** Trend data if available */
trends?: Record<string, unknown>;
}
/**
* OWASP Top 10 report
*/
export interface OwaspTop10Report extends ComplianceReportBase {
type: ReportType.OWASP_TOP_10;
/** OWASP Top 10 categories with issues */
categories: Array<{
/** OWASP category key */
key: string;
/** Category title */
title: string;
/** Issue counts by severity */
issues: {
critical: number;
major: number;
minor: number;
total: number;
};
}>;
}
/**
* SANS Top 25 report
*/
export interface SansTop25Report extends ComplianceReportBase {
type: ReportType.SANS_TOP_25;
/** SANS Top 25 categories with issues */
categories: Array<{
/** SANS category key */
key: string;
/** Category title */
title: string;
/** Issue counts by severity */
issues: {
critical: number;
major: number;
minor: number;
total: number;
};
}>;
}
/**
* MISRA-C report
*/
export interface MisraCReport extends ComplianceReportBase {
type: ReportType.MISRA_C;
/** MISRA violation categories */
categories: Array<{
/** MISRA rule key */
key: string;
/** Rule title */
title: string;
/** Issue counts by severity */
issues: {
critical: number;
major: number;
minor: number;
total: number;
};
}>;
}
/**
* Code coverage report
*/
export interface CodeCoverageReport extends ComplianceReportBase {
type: ReportType.CODE_COVERAGE;
/** Coverage by type */
coverage: {
/** Line coverage percentage */
line?: number;
/** Branch coverage percentage */
branch?: number;
/** Statement coverage percentage */
statement?: number;
/** Function coverage percentage */
function?: number;
};
/** Coverage trend over time */
trend?: Array<{
/** Date of the data point */
date: string;
/** Coverage value */
value: number;
}>;
}
/**
* Issues prevented report
*/
export interface IssuesPreventedReport extends ComplianceReportBase {
type: ReportType.ISSUES_PREVENTED;
/** Issues prevented by severity */
prevented: {
/** Critical issues prevented */
critical: number;
/** Major issues prevented */
major: number;
/** Minor issues prevented */
minor: number;
/** Total issues prevented */
total: number;
};
/** Percentage of issues that were prevented */
preventionRate: number;
}
/**
* Union type for all report types
*/
export type ComplianceReport = OwaspTop10Report | SansTop25Report | MisraCReport | CodeCoverageReport | IssuesPreventedReport;
/**
* Type guard to check if a compliance report is of a specific type
*
* This generic type guard determines if a compliance report is of a particular type
* based on its type discriminant. It enables type-safe access to report-type-specific
* properties after TypeScript narrows the type.
*
* @example
* ```typescript
* // Check if report is an OWASP Top 10 report
* if (isReportOfType<OwaspTop10Report>(report, ReportType.OWASP_TOP_10)) {
* // TypeScript knows this is an OwaspTop10Report now
* console.log(report.owaspCategories);
* console.log(report.vulnerabilityCount);
* }
* ```
*
* @param report - The compliance report to check
* @param type - The report type to test against
* @returns Type predicate indicating whether the report is of the specified type
* @typeParam T - The specific report type to check for (must extend ComplianceReport)
* @public
*/
export declare function isReportOfType<T extends ComplianceReport>(report: ComplianceReport, type: ReportType): report is T;
/**
* -----------------------------------------------------------------------------
* Metric Status Discriminated Unions
* -----------------------------------------------------------------------------
*/
/**
* Base interface for metric states
*/
interface MetricStateBase {
/** Metric shortcode */
shortcode: MetricShortcode;
/** Metric name */
name: string;
/** Unit of measurement */
unit: string;
/** Direction in which higher values are considered positive */
positiveDirection: MetricDirection;
/** Current metric value */
value: number;
/** Status discriminant */
status: MetricThresholdStatus;
}
/**
* Passing metric
*/
export interface PassingMetric extends MetricStateBase {
status: MetricThresholdStatus.PASSING;
/** The threshold that was met or exceeded */
threshold: number;
/** By how much the metric exceeds the threshold */
margin?: number;
}
/**
* Failing metric
*/
export interface FailingMetric extends MetricStateBase {
status: MetricThresholdStatus.FAILING;
/** The threshold that wasn't met */
threshold: number;
/** By how much the metric falls short of the threshold */
gap?: number;
/** Recommendations for improving the metric */
recommendations?: string[];
}
/**
* Metric with unknown status (no threshold set)
*/
export interface UnknownMetric extends MetricStateBase {
status: MetricThresholdStatus.UNKNOWN;
/** Whether a threshold is recommended */
thresholdRecommended?: boolean;
/** Recommended threshold value if available */
recommendedThreshold?: number;
}
/**
* Union type for metric states
*/
export type MetricState = PassingMetric | FailingMetric | UnknownMetric;
/**
* Type guard to check if a metric is in a specific threshold state
*
* This generic type guard determines if a quality metric is in a particular threshold
* state based on its status discriminant. It enables type-safe access to state-specific
* properties after TypeScript narrows the type.
*
* @example
* ```typescript
* // Check if metric is passing its threshold
* if (isMetricInState<PassingMetric>(metric, MetricThresholdStatus.PASS)) {
* // TypeScript knows this is a PassingMetric now
* console.log(metric.passPercentage);
* console.log(metric.margin);
* }
*
* // Check if metric is failing its threshold
* if (isMetricInState<FailingMetric>(metric, MetricThresholdStatus.FAIL)) {
* // TypeScript knows this is a FailingMetric now
* console.log(metric.thresholdDelta);
* console.log(metric.recommendedThreshold);
* }
* ```
*
* @param metric - The metric to check
* @param status - The threshold status to test against
* @returns Type predicate indicating whether the metric is in the specified state
* @typeParam T - The specific metric state to check for (must extend MetricState)
* @public
*/
export declare function isMetricInState<T extends MetricState>(metric: MetricState, status: MetricThresholdStatus): metric is T;
export {};