Files
puaros/packages/guardian/src/domain/value-objects/EntityExposure.ts
imfozilbek a34ca85241 chore: refactor hardcoded values to constants (v0.5.1)
Major internal refactoring to eliminate hardcoded values and improve
maintainability. Guardian now fully passes its own quality checks!

Changes:
- Extract all RepositoryViolation messages to domain constants
- Extract all framework leak template strings to centralized constants
- Extract all layer paths to infrastructure constants
- Extract all regex patterns to IMPORT_PATTERNS constant
- Add 30+ new constants for better maintainability

New files:
- src/infrastructure/constants/paths.ts (layer paths, patterns)
- src/domain/constants/Messages.ts (25+ repository messages)
- src/domain/constants/FrameworkCategories.ts (framework categories)
- src/shared/constants/layers.ts (layer names)

Impact:
- Reduced hardcoded values from 37 to 1 (97% improvement)
- Guardian passes its own src/ directory checks with 0 violations
- All 292 tests still passing (100% pass rate)
- No breaking changes - fully backwards compatible

Test results:
- 292 tests passing (100% pass rate)
- 96.77% statement coverage
- 83.82% branch coverage
2025-11-24 20:12:08 +05:00

113 lines
3.2 KiB
TypeScript

import { ValueObject } from "./ValueObject"
import { ENTITY_EXPOSURE_MESSAGES } from "../constants/Messages"
interface EntityExposureProps {
readonly entityName: string
readonly returnType: string
readonly filePath: string
readonly layer: string
readonly line?: number
readonly methodName?: string
}
/**
* Represents an entity exposure violation in the codebase
*
* Entity exposure occurs when a domain entity is directly exposed in API responses
* instead of using DTOs (Data Transfer Objects). This violates the separation of concerns
* and can lead to exposing internal domain logic to external clients.
*
* @example
* ```typescript
* // Bad: Controller returning domain entity
* const exposure = EntityExposure.create(
* 'User',
* 'User',
* 'src/infrastructure/controllers/UserController.ts',
* 'infrastructure',
* 25,
* 'getUser'
* )
*
* console.log(exposure.getMessage())
* // "Method 'getUser' returns domain entity 'User' instead of DTO"
* ```
*/
export class EntityExposure extends ValueObject<EntityExposureProps> {
private constructor(props: EntityExposureProps) {
super(props)
}
public static create(
entityName: string,
returnType: string,
filePath: string,
layer: string,
line?: number,
methodName?: string,
): EntityExposure {
return new EntityExposure({
entityName,
returnType,
filePath,
layer,
line,
methodName,
})
}
public get entityName(): string {
return this.props.entityName
}
public get returnType(): string {
return this.props.returnType
}
public get filePath(): string {
return this.props.filePath
}
public get layer(): string {
return this.props.layer
}
public get line(): number | undefined {
return this.props.line
}
public get methodName(): string | undefined {
return this.props.methodName
}
public getMessage(): string {
const method = this.props.methodName
? `Method '${this.props.methodName}'`
: ENTITY_EXPOSURE_MESSAGES.METHOD_DEFAULT
return `${method} returns domain entity '${this.props.entityName}' instead of DTO`
}
public getSuggestion(): string {
const suggestions = [
`Create a DTO class (e.g., ${this.props.entityName}ResponseDto) in the application layer`,
`Create a mapper to convert ${this.props.entityName} to ${this.props.entityName}ResponseDto`,
`Update the method to return ${this.props.entityName}ResponseDto instead of ${this.props.entityName}`,
]
return suggestions.join("\n")
}
public getExampleFix(): string {
return `
// ❌ Bad: Exposing domain entity
async ${this.props.methodName || ENTITY_EXPOSURE_MESSAGES.METHOD_DEFAULT_NAME}(): Promise<${this.props.entityName}> {
return await this.service.find()
}
// ✅ Good: Using DTO
async ${this.props.methodName || ENTITY_EXPOSURE_MESSAGES.METHOD_DEFAULT_NAME}(): Promise<${this.props.entityName}ResponseDto> {
const entity = await this.service.find()
return ${this.props.entityName}Mapper.toDto(entity)
}`
}
}