1pub(crate) type Result<T> = std::result::Result<T, Error>;
2
3#[derive(Debug, thiserror::Error)]
4pub enum Error {
5 #[error("invalid account name: use 1–128 ASCII letters, digits, dots, hyphens or underscores")]
6 InvalidAccountName,
7 #[error("invalid HackerEarth topic: use lowercase URL path segments separated by slashes")]
8 InvalidPracticeTopic,
9 #[error("invalid problem slug: use 1–128 ASCII letters, digits, hyphens or underscores")]
10 InvalidProblemId,
11 #[error("invalid problem identifier: use a positive problem number or title slug")]
12 InvalidProblemSelector,
13 #[error("problem number was not found; search for it and use the returned slug")]
14 ProblemNumberNotFound,
15 #[error("invalid language: use a LeetCode language slug such as rust, cpp or python3")]
16 InvalidLanguage,
17 #[error("this problem does not provide a {0} starter")]
18 LanguageUnavailable(Box<str>),
19 #[error("invalid search query: use 1–100 printable ASCII characters")]
20 InvalidSearchQuery,
21 #[error("invalid problem tag: use 1–64 lowercase letters, digits or hyphens")]
22 InvalidProblemTag,
23 #[error("invalid contest ID: use 1–128 ASCII letters, digits or internal hyphens")]
24 InvalidContestSlug,
25 #[error("invalid discussion identifier: use a positive numeric topic ID")]
26 InvalidDiscussionId,
27 #[error("invalid {platform} problem ID; use {expected}")]
28 InvalidCatalogProblemId {
29 platform: &'static str,
30 expected: &'static str,
31 },
32 #[error("{platform} problem pages start at 1")]
33 InvalidProblemPage { platform: &'static str },
34 #[error("{platform} catalogue page {page} does not exist; the last page is {last}")]
35 ProblemPageNotFound {
36 platform: &'static str,
37 page: u8,
38 last: usize,
39 },
40 #[error("Exercism requires --track <slug>, such as --track rust")]
41 ExercismTrackRequired,
42 #[error("{platform} requires an account name for {capability}")]
43 AccountNameRequired {
44 platform: &'static str,
45 capability: &'static str,
46 },
47 #[error("{platform} does not provide {capability}")]
48 CapabilityUnavailable {
49 platform: &'static str,
50 capability: &'static str,
51 },
52 #[error("contest {id} was not found on {platform}")]
53 ContestNotFound {
54 platform: &'static str,
55 id: Box<str>,
56 },
57 #[error("{0}")]
58 ExercismCli(Box<str>),
59 #[error("{platform} does not expose a callable terminal API for `cp-cli {command}`")]
60 UnsupportedPlatformCommand {
61 platform: &'static str,
62 command: &'static str,
63 },
64 #[cfg(not(feature = "codechef"))]
65 #[error("CodeChef support is disabled; rebuild with --features codechef")]
66 CodeChefDisabled,
67 #[cfg(feature = "codechef")]
68 #[error(transparent)]
69 CodeChef(#[from] platform_codechef::Error),
70 #[cfg(not(feature = "codeforces"))]
71 #[error("Codeforces support is disabled; rebuild with --features codeforces")]
72 CodeforcesDisabled,
73 #[cfg(feature = "codeforces")]
74 #[error(transparent)]
75 Codeforces(#[from] platform_codeforces::Error),
76 #[cfg(not(feature = "hackerrank"))]
77 #[error("HackerRank support is disabled; rebuild with --features hackerrank")]
78 HackerRankDisabled,
79 #[cfg(feature = "hackerrank")]
80 #[error(transparent)]
81 HackerRank(#[from] platform_hackerrank::Error),
82 #[cfg(not(feature = "hackerearth"))]
83 #[error("HackerEarth support is disabled; rebuild with --features hackerearth")]
84 HackerEarthDisabled,
85 #[cfg(feature = "hackerearth")]
86 #[error(transparent)]
87 HackerEarth(#[from] platform_hackerearth::Error),
88 #[cfg(not(feature = "project-euler"))]
89 #[error("Project Euler support is disabled; rebuild with --features project-euler")]
90 ProjectEulerDisabled,
91 #[cfg(feature = "project-euler")]
92 #[error(transparent)]
93 ProjectEuler(#[from] platform_project_euler::Error),
94 #[cfg(not(feature = "leetcode"))]
95 #[error("LeetCode support is disabled; rebuild with --features leetcode")]
96 LeetCodeDisabled,
97 #[cfg(feature = "leetcode")]
98 #[error(transparent)]
99 LeetCode(#[from] platform_leetcode::Error),
100 #[error(
101 "LeetCode authentication is required; set LEETCODE_SESSION and LEETCODE_CSRFTOKEN together"
102 )]
103 AuthenticationRequired,
104 #[error("LEETCODE_SESSION and LEETCODE_CSRFTOKEN must be set together as valid Unicode")]
105 InvalidAuthenticationEnvironment,
106 #[error("CODEFORCES_API_KEY and CODEFORCES_API_SECRET must be set together as valid Unicode")]
107 InvalidCodeforcesAuthenticationEnvironment,
108 #[error("auth login requires an interactive terminal")]
109 InteractiveLoginRequired,
110 #[cfg(any(feature = "codeforces", feature = "leetcode"))]
111 #[error("could not access the operating system's secure credential store")]
112 CredentialStore(#[source] keyring::Error),
113 #[error(
114 "saved LeetCode credentials are unreadable; run `cp-cli auth logout`, then configure a valid environment session"
115 )]
116 InvalidStoredCredentials,
117 #[error(
118 "saved Codeforces API credentials are unreadable; run `cp-cli --platform codeforces auth logout`, then log in again"
119 )]
120 InvalidCodeforcesStoredCredentials,
121 #[error("a solution file or its metadata already exists at {0}")]
122 SolutionExists(std::path::PathBuf),
123 #[error("could not access workspace path {path}: {source}")]
124 WorkspaceIo {
125 path: std::path::PathBuf,
126 #[source]
127 source: std::io::Error,
128 },
129 #[error("invalid solution workspace at {path}: {reason}")]
130 InvalidWorkspace {
131 path: std::path::PathBuf,
132 reason: &'static str,
133 },
134 #[error("choose --lang and --dir when problem pick is not attached to a terminal")]
135 InteractivePickRequired,
136 #[error("problem pick was cancelled")]
137 PickCancelled,
138 #[error("the LeetCode test run {0} did not finish within 60 seconds")]
139 TestJudgeTimeout(Box<str>),
140 #[error("the LeetCode judge did not finish submission {0} within 60 seconds")]
141 JudgeTimeout(u64),
142 #[error("could not write output: {0}")]
143 Io(#[from] std::io::Error),
144 #[error("could not write JSON output: {0}")]
145 Json(#[from] serde_json::Error),
146}
147
148impl From<crate::exercism_cli::ExercismCliError> for Error {
149 fn from(error: crate::exercism_cli::ExercismCliError) -> Self {
150 Self::ExercismCli(error.to_string().into())
151 }
152}
153
154impl Error {
155 pub fn is_broken_pipe(&self) -> bool {
156 match self {
157 Self::Io(error) => error.kind() == std::io::ErrorKind::BrokenPipe,
158 Self::Json(error) => error.io_error_kind() == Some(std::io::ErrorKind::BrokenPipe),
159 _ => false,
160 }
161 }
162}