Extract ProjectMapper class from GitHubProjectDataSource#629
Extract ProjectMapper class from GitHubProjectDataSource#629ulrikandersen wants to merge 2 commits intodevelopfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR extracts the repository-to-project mapping logic from GitHubProjectDataSource into a dedicated, reusable ProjectMapper class. This refactoring introduces generic type support to enable the mapper to work with different Git providers, while moving provider-specific URL generation logic into configurable URL builders. The change improves code organization by separating concerns: the data source now focuses on repository retrieval, while the mapper handles the transformation logic.
Key Changes:
- Created a new
ProjectMapperclass with generic type support for provider-agnostic repository mapping - Introduced
URLBuildersinterface for provider-specific URL generation with optional diff and PR URL support - Simplified
GitHubProjectDataSourceto delegate mapping logic toProjectMapper
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| src/features/projects/domain/ProjectMapper.ts | New file containing extracted mapping logic with generic types (RepositoryRef, RepositoryWithRefs, URLBuilders, and ProjectMapper class) and utility functions |
| src/features/projects/data/GitHubProjectDataSource.ts | Refactored to use ProjectMapper with GitHub-specific URL builders, removing ~260 lines of mapping logic |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
|
|
||
| export function getBlobURL(owner: string, repository: string, path: string, ref: string): string { | ||
| return `/api/blob/${owner}/${repository}/${path}?ref=${ref}` |
There was a problem hiding this comment.
The getBlobURL function is missing path encoding that was present in the original implementation. The old code properly encoded each path segment using path.split('/').map(segment => encodeURIComponent(segment)).join('/'), but the new implementation directly uses the unencoded path. This could cause issues with files containing special characters in their names (e.g., spaces, #, ?, etc.).
Consider updating the function to:
export function getBlobURL(owner: string, repository: string, path: string, ref: string): string {
const encodedPath = path.split('/').map(segment => encodeURIComponent(segment)).join('/')
return `/api/blob/${owner}/${repository}/${encodedPath}?ref=${ref}`
}| return `/api/blob/${owner}/${repository}/${path}?ref=${ref}` | |
| const encodedPath = path.split('/').map(segment => encodeURIComponent(segment)).join('/') | |
| return `/api/blob/${owner}/${repository}/${encodedPath}?ref=${ref}` |
|
|
||
| const gitHubURLBuilders: URLBuilders<RepositoryWithRefs> = { | ||
| getImageRef(repository: RepositoryWithRefs): string { | ||
| return repository.defaultBranchRef.id! |
There was a problem hiding this comment.
Using non-null assertion operator (!) on repository.defaultBranchRef.id is unsafe. The RepositoryWithRefs type defines id as optional (readonly id?: string), but the actual GitHubRepository type from the domain layer requires it to be non-optional (readonly id: string). This mismatch could cause runtime errors if the mapper is used with other repository types that don't guarantee an id.
Consider either:
- Making the id required in
RepositoryRefandRepositoryWithRefstypes if it's always expected, or - Providing a fallback value or throwing a descriptive error when id is missing
| return repository.defaultBranchRef.id! | |
| if (!repository.defaultBranchRef.id) { | |
| throw new Error("Repository defaultBranchRef.id is missing"); | |
| } | |
| return repository.defaultBranchRef.id; |
| return repository.defaultBranchRef.id! | ||
| }, | ||
| getBlobRef(ref: RepositoryRef): string { | ||
| return ref.id! |
There was a problem hiding this comment.
Using non-null assertion operator (!) on ref.id is unsafe. The RepositoryRef type defines id as optional (readonly id?: string), but the actual GitHubRepositoryRef type from the domain layer requires it to be non-optional (readonly id: string). This mismatch could cause runtime errors if the mapper is used with other repository types that don't guarantee an id.
Consider either:
- Making the id required in
RepositoryReftype if it's always expected for GitHub, or - Providing a fallback value or throwing a descriptive error when id is missing
| return ref.id! | |
| if (!ref.id) { | |
| throw new Error("RepositoryRef.id is required for getBlobRef but was undefined"); | |
| } | |
| return ref.id; |
| return `https://github.com/${repository.owner}/${repository.name}/edit/${ref.name}/${encodeURIComponent(fileName)}` | ||
| }, | ||
| getDiffUrl(repository: RepositoryWithRefs, ref: RepositoryRef, fileName: string): string | undefined { | ||
| if (!ref.baseRefOid || !ref.id) { |
There was a problem hiding this comment.
The condition !ref.id is a new addition not present in the original implementation. The old code only checked if (!baseRefOid) before returning undefined. While this additional check may be intentional for safety given the optional nature of ref.id in the new types, it changes the behavior from the original implementation and could cause diff URLs to not be generated in cases where they previously would have been. If this is intentional, it should be documented; otherwise, consider matching the original logic.
| if (!ref.baseRefOid || !ref.id) { | |
| if (!ref.baseRefOid) { |
| .sort((a, b) => a.name.localeCompare(b.name)) | ||
| } | ||
|
|
||
| mapRepositoryToProject(repository: T): Project { |
There was a problem hiding this comment.
[nitpick] The mapRepositoryToProject method is public, but the equivalent method in the original implementation (mapProject) was private. Consider marking this as private unless there's a specific need to expose it as part of the public API, to maintain better encapsulation.
| mapRepositoryToProject(repository: T): Project { | |
| private mapRepositoryToProject(repository: T): Project { |
d90ecdc to
3ce2d6b
Compare
3ce2d6b to
8a37284
Compare
Summary
ProjectMapperclass