Skip to content

Commit 7fafad6

Browse files
geekbrotherclaude
andauthored
fix: return Ok(None) when project not found in project_data_with (#33)
`project_data_with_impl` mapped a registry 404 (project not found) to `Err(RegistryError::Response(...))` instead of `Ok(None)`. Callers that classify registry errors as transient outages then fail open, letting any well-formed but unregistered project id through. Surface a missing project as `Ok(None)`, matching `project_data_with_limits_impl`. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 4db7b93 commit 7fafad6

1 file changed

Lines changed: 45 additions & 5 deletions

File tree

src/registry/client.rs

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -289,11 +289,16 @@ impl RegistryHttpClient {
289289
if !is_valid_project_id(request.id) {
290290
return Ok(None);
291291
}
292-
// Always fetch the base project data
293-
let data = self
294-
.project_data(request.id)
295-
.await?
296-
.ok_or_else(|| RegistryError::Response("Project not found".to_string()))?;
292+
// Always fetch the base project data. A missing project (registry
293+
// returns 404) must surface as `Ok(None)` so callers can tell
294+
// "project not found" apart from a transient registry error, matching
295+
// the behaviour of `project_data_with_limits_impl`. Returning an `Err`
296+
// here causes callers to treat an unknown project as a registry outage
297+
// and fail open.
298+
let data = match self.project_data(request.id).await? {
299+
Some(data) => data,
300+
None => return Ok(None),
301+
};
297302

298303
let limits = match request.include_limits {
299304
true => Some(
@@ -531,6 +536,41 @@ mod test {
531536
assert!(response.is_none());
532537
}
533538

539+
#[tokio::test]
540+
async fn project_data_with_returns_none_when_project_not_found() {
541+
let project_id = "a".repeat(32);
542+
543+
let mock_server = MockServer::start().await;
544+
545+
// Registry returns 404 for a well-formed but unregistered project id.
546+
Mock::given(method(Method::Get))
547+
.and(path(format!("/internal/project/key/{project_id}")))
548+
.respond_with(ResponseTemplate::new(404))
549+
.mount(&mock_server)
550+
.await;
551+
552+
let request = crate::project::ProjectDataRequest::new(&project_id).include_limits();
553+
554+
let response = RegistryHttpClient::with_config(
555+
mock_server.uri(),
556+
Some(mock_server.uri()),
557+
"auth",
558+
TEST_ORIGIN,
559+
"st",
560+
"sv",
561+
Default::default(),
562+
)
563+
.unwrap()
564+
.project_data_with(request)
565+
.await
566+
.unwrap();
567+
568+
// A missing project must be `Ok(None)`, not an `Err`: an `Err` makes
569+
// callers treat an unknown project as a transient registry outage and
570+
// fail open instead of rejecting the request.
571+
assert!(response.is_none());
572+
}
573+
534574
#[tokio::test]
535575
async fn project_id_invalid_len() {
536576
let project_id = "a".repeat(31);

0 commit comments

Comments
 (0)