1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/// Error types for the context7-cli library.
///
/// [`ErroContext7`] is used for all structured errors within the library.
/// Binary code uses [`anyhow::Result`] for flexible propagation.
use thiserror::Error;
/// Structured errors for the Context7 API client.
#[derive(Debug, Error)]
pub enum ErroContext7 {
/// All available API keys have been exhausted after the given number of attempts.
#[error("No valid API key available after {tentativas} attempts")]
RetryEsgotado {
/// Number of attempts made before exhaustion.
tentativas: u32,
},
/// All keys failed due to authentication errors (401/403).
#[error("All API keys failed due to authentication errors")]
SemChavesApi,
/// The API returned an unexpected HTTP status code.
#[error("Invalid API response: status {status}")]
RespostaInvalida {
/// HTTP status code returned by the API.
status: u16,
},
/// The API returned HTTP 400 with an error message.
#[error("API returned error 400: {mensagem}")]
ApiRetornou400 {
/// Error message returned by the API.
mensagem: String,
},
/// The requested library was not found (HTTP 404).
#[error("Library not found: {library_id}")]
BibliotecaNaoEncontrada {
/// Library identifier that was not found.
library_id: String,
},
/// A keys operation failed (e.g., invalid index, no keys stored).
/// The caller already printed a user-friendly message; this signals exit code 1.
#[error("")]
OperacaoKeysFalhou,
}
impl ErroContext7 {
/// Maps each error variant to a BSD-style exit code (sysexits.h).
///
/// | Code | Constant | Meaning |
/// |------|----------------|----------------------------------|
/// | 1 | generic | Unspecified runtime error |
/// | 65 | EX_DATAERR | Invalid input data |
/// | 66 | EX_NOINPUT | Requested resource not found |
/// | 69 | EX_UNAVAILABLE | Service unavailable after retry |
/// | 74 | EX_IOERR | I/O or network error |
/// | 77 | EX_NOPERM | Permission / authentication denied|
pub fn exit_code(&self) -> i32 {
match self {
Self::RetryEsgotado { .. } => 69,
Self::SemChavesApi => 77,
Self::RespostaInvalida { .. } => 74,
Self::ApiRetornou400 { .. } => 65,
Self::BibliotecaNaoEncontrada { .. } => 66,
Self::OperacaoKeysFalhou => 1,
}
}
}
#[cfg(test)]
mod testes {
use super::*;
#[test]
fn testa_erro_sem_chaves_api_display() {
let erro = ErroContext7::SemChavesApi;
let mensagem = erro.to_string();
assert!(
!mensagem.is_empty(),
"SemChavesApi deve ter mensagem não-vazia"
);
assert!(
mensagem.to_lowercase().contains("key")
|| mensagem.to_lowercase().contains("api")
|| mensagem.to_lowercase().contains("auth"),
"Mensagem deve mencionar key/api/auth, obteve: {mensagem}"
);
}
#[test]
fn testa_erro_retry_esgotado_contem_numero_de_tentativas() {
let erro = ErroContext7::RetryEsgotado { tentativas: 3 };
let mensagem = erro.to_string();
assert!(
mensagem.contains('3'),
"Mensagem deve conter número de tentativas (3), obteve: {mensagem}"
);
}
#[test]
fn testa_erro_resposta_invalida_contem_status() {
let erro = ErroContext7::RespostaInvalida { status: 500 };
let mensagem = erro.to_string();
assert!(
mensagem.contains("500"),
"Mensagem deve conter código de status, obteve: {mensagem}"
);
}
#[test]
fn testa_erro_api_400_contem_texto_do_erro() {
let erro = ErroContext7::ApiRetornou400 {
mensagem: "Parâmetro inválido".to_string(),
};
let mensagem = erro.to_string();
assert!(
mensagem.contains("Parâmetro inválido"),
"Mensagem deve conter texto do erro, obteve: {mensagem}"
);
}
#[test]
fn testa_resultado_alias_propaga_erro_context7() {
fn falha() -> Result<(), ErroContext7> {
Err(ErroContext7::SemChavesApi)
}
let resultado: Result<(), ErroContext7> = falha();
assert!(resultado.is_err(), "Resultado deve ser Err");
let err = resultado.unwrap_err();
assert!(matches!(err, ErroContext7::SemChavesApi));
}
}