Skip to content

Commit ad93cc1

Browse files
David Brunowclaude
andcommitted
feat: enhance error handling with specific error types and recovery suggestions
- Add GitError, ParseError, and ConfigurationError types with detailed recovery suggestions - Replace generic ParserError with specific ParseError types for better diagnostics - Enhance ConventionalCommit validation to reject empty types and descriptions - Implement smart error suggestion logic that avoids redundant suggestions - Update command error handling with proper stderr output and exit codes - Fix duplicate CHANGELOG.md entries 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent ab652f3 commit ad93cc1

File tree

10 files changed

+217
-147
lines changed

10 files changed

+217
-147
lines changed

CHANGELOG.md

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
* Setup CI for releases (9b70416)
1515
* Add CI (31581ad)
1616

17-
## [0.1.0] - 2024-05-20
18-
19-
### Features
20-
* Initial implementation of parsing (0c04db6)
21-
22-
### Chores
23-
* Setup CI for releases (9b70416)
24-
* Add CI (31581ad)
25-
26-
## [0.1.0] - 2024-05-20
27-
28-
### Features
29-
* Initial implementation of parsing (0c04db6)
30-
31-
### Chores
32-
* Setup CI for releases (9b70416)
33-
* Add CI (31581ad)
34-

Package.resolved

Lines changed: 0 additions & 90 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import Foundation
2+
3+
/// Errors related to configuration and command-line options.
4+
public enum ConfigurationError: LocalizedError {
5+
case invalidStrictMode
6+
case conflictingOptions(String)
7+
case missingRequiredParameter(String)
8+
9+
public var errorDescription: String? {
10+
switch self {
11+
case .invalidStrictMode:
12+
return "Strict mode configuration is invalid"
13+
case .conflictingOptions(let details):
14+
return "Conflicting command line options: \(details)"
15+
case .missingRequiredParameter(let param):
16+
return "Required parameter missing: \(param)"
17+
}
18+
}
19+
20+
public var recoverySuggestion: String? {
21+
switch self {
22+
case .invalidStrictMode:
23+
return "Check strict mode flag usage in documentation"
24+
case .conflictingOptions:
25+
return "Review command line options and remove conflicting flags"
26+
case .missingRequiredParameter(let param):
27+
return "Provide the required parameter: \(param)"
28+
}
29+
}
30+
}

Sources/Model/ConventionalCommit.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ public struct ConventionalCommit: Equatable {
9999

100100
let typeWithoutScope = type.replacingOccurrences(of: scope ?? "", with: "")
101101

102+
guard !typeWithoutScope.isEmpty else {
103+
return nil
104+
}
105+
102106
switch typeWithoutScope {
103107
case "feat":
104108
self.type = .known(.feat)
@@ -124,9 +128,15 @@ public struct ConventionalCommit: Equatable {
124128
self.isBreaking = false
125129
}
126130

127-
self.description = String(
131+
let description = String(
128132
commit.subject.suffix(from: commit.subject.index(after: colonIndex))
129133
).trimmingCharacters(in: .whitespacesAndNewlines)
134+
135+
guard !description.isEmpty else {
136+
return nil
137+
}
138+
139+
self.description = description
130140
self.hash = commit.hash
131141
self.scope = scope?
132142
.replacingOccurrences(of: "(", with: "")

Sources/Model/GitError.swift

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import Foundation
2+
3+
/// Errors related to git operations.
4+
public enum GitError: LocalizedError {
5+
case repositoryNotFound
6+
case invalidRepository
7+
case gitCommandFailed(String, exitCode: Int)
8+
case noCommitsFound
9+
case tagOperationFailed
10+
11+
public var errorDescription: String? {
12+
switch self {
13+
case .repositoryNotFound:
14+
return "No git repository found in current directory"
15+
case .invalidRepository:
16+
return "Invalid git repository structure"
17+
case .gitCommandFailed(let command, let exitCode):
18+
return "Git command '\(command)' failed with exit code \(exitCode)"
19+
case .noCommitsFound:
20+
return "No commits found in repository"
21+
case .tagOperationFailed:
22+
return "Failed to read git tags"
23+
}
24+
}
25+
26+
public var recoverySuggestion: String? {
27+
switch self {
28+
case .repositoryNotFound:
29+
return "Ensure you're running this command from within a git repository"
30+
case .invalidRepository:
31+
return
32+
"Check that the git repository is properly initialized and not corrupted"
33+
case .gitCommandFailed(let command, _):
34+
return "Check git status and ensure '\(command)' can be run manually"
35+
case .noCommitsFound:
36+
return "Ensure the repository has commits and you have access to them"
37+
case .tagOperationFailed:
38+
return "Check that git tags exist and are accessible"
39+
}
40+
}
41+
}

Sources/Model/ParseError.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import Foundation
2+
3+
/// Errors related to parsing conventional commits and semantic versions.
4+
public enum ParseError: LocalizedError {
5+
case invalidConventionalCommit(commit: String, reason: String)
6+
case invalidSemanticVersion(tag: String)
7+
case malformedCommitMessage(hash: String, subject: String)
8+
case unsupportedCommitType(type: String, commit: String)
9+
case noFormattedCommits(String)
10+
11+
public var errorDescription: String? {
12+
switch self {
13+
case .invalidConventionalCommit(let commit, let reason):
14+
return "Invalid conventional commit '\(commit)': \(reason)"
15+
case .invalidSemanticVersion(let tag):
16+
return "Tag '\(tag)' is not a valid semantic version"
17+
case .malformedCommitMessage(let hash, let subject):
18+
return "Commit \(hash) has malformed message: '\(subject)'"
19+
case .unsupportedCommitType(let type, let commit):
20+
return "Unsupported commit type '\(type)' in commit: \(commit)"
21+
case .noFormattedCommits(let message):
22+
return message
23+
}
24+
}
25+
26+
public var recoverySuggestion: String? {
27+
switch self {
28+
case .invalidConventionalCommit, .malformedCommitMessage:
29+
return "Check conventional commit format: https://conventionalcommits.org"
30+
case .invalidSemanticVersion:
31+
return "Ensure tags follow semantic versioning format (e.g., 1.0.0, v2.1.3)"
32+
case .unsupportedCommitType:
33+
return "Use supported commit types: feat, fix, hotfix, or chore"
34+
case .noFormattedCommits:
35+
return "Ensure at least one commit follows conventional commit format"
36+
}
37+
}
38+
}

Sources/SwiftConventionalCommitParser/Parser.swift

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public struct Parser {
3030
}
3131
// print("Conventional commits since last branch: \(conventionalCommitsSinceLastBranch)")
3232
if conventionalCommitsSinceLastBranch.count == 0 {
33-
throw ParserError.noFormattedCommits(noFormattedCommitsErrorMessage)
33+
throw ParseError.noFormattedCommits(noFormattedCommitsErrorMessage)
3434
}
3535
}
3636

@@ -41,7 +41,7 @@ public struct Parser {
4141
}
4242

4343
guard conventionalCommits.count > 0 else {
44-
throw ParserError.noFormattedCommits(noFormattedCommitsErrorMessage)
44+
throw ParseError.noFormattedCommits(noFormattedCommitsErrorMessage)
4545
}
4646

4747
let lastSemanticVersion =
@@ -68,7 +68,7 @@ public struct Parser {
6868
} else if conventionalCommits.isEmpty == false {
6969
bumpType = .none
7070
} else {
71-
throw ParserError.noFormattedCommits(noFormattedCommitsErrorMessage)
71+
throw ParseError.noFormattedCommits(noFormattedCommitsErrorMessage)
7272
}
7373

7474
let nextSemanticVersion = lastSemanticVersion.bump(bumpType)
@@ -81,15 +81,3 @@ public struct Parser {
8181
)
8282
}
8383
}
84-
85-
public enum ParserError: LocalizedError {
86-
case noFormattedCommits(String)
87-
88-
/// No overview available.
89-
public var errorDescription: String? {
90-
switch self {
91-
case let .noFormattedCommits(errorMessage):
92-
return errorMessage
93-
}
94-
}
95-
}

0 commit comments

Comments
 (0)