Skip to main content

cp_cli/
error.rs

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}