fix(caldav+carddav): raise 400 error on param issue

rather than a 500
This commit is contained in:
Edouard Vanbelle
2026-07-14 14:45:04 +02:00
parent 54b5b3bf4f
commit a7a45b3383
4 changed files with 250 additions and 10 deletions
+26 -5
View File
@@ -587,10 +587,12 @@ async fn handle_mkcalendar(
is_public: Some(false),
};
// See the comment above create_event_from_ical for why this uses
// `AppError::from` (kind-aware mapping) instead of `internal_error`.
calendar_service
.create_calendar(create_dto, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to create calendar: {}", e)))?;
.map_err(AppError::from)?;
Ok(Response::builder()
.status(StatusCode::CREATED)
@@ -639,11 +641,15 @@ async fn handle_put(
};
if let Some(existing_event) = existing {
// Update existing event — re-create from iCal for full fidelity
// Update existing event — re-create from iCal for full fidelity.
// Both calls use `AppError::from` — the delete propagates
// NotFound/AccessDenied as 404/403, and the recreate propagates
// InvalidInput on malformed iCalendar as 400 (see comment on
// create_event_from_ical below).
calendar_service
.delete_event(&existing_event.id, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to update event: {}", e)))?;
.map_err(AppError::from)?;
let create_dto = CreateEventICalDto {
calendar_id: calendar_id.to_string(),
@@ -652,7 +658,7 @@ async fn handle_put(
let event = calendar_service
.create_event_from_ical(create_dto, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to recreate event: {}", e)))?;
.map_err(AppError::from)?;
Ok(Response::builder()
.status(StatusCode::NO_CONTENT)
@@ -665,10 +671,25 @@ async fn handle_put(
ical_data,
};
// `AppError::from(DomainError)` (via the `From` impl in
// `interfaces/errors.rs`) maps the ErrorKind onto the correct
// HTTP status:
// * `InvalidInput` → 400 (e.g. "Missing DTSTART in iCalendar
// data" from `CalendarEvent::from_ical`) — this is the fix
// for AtalayaLabs/OxiCloud#545 comment from `funboytwo`.
// * `NotFound` → 404 (parent calendar doesn't exist)
// * `AccessDenied` → 403 (caller lacks Write on the calendar)
// * `DatabaseError`/`InternalError` → 500 (genuine server bug)
//
// The old `map_err(|e| AppError::internal_error(...))` was
// blanket-wrapping every case as 500, hiding client-input bugs
// as opaque server errors. Downstream monitoring (500 rate,
// pager alerts) took the false hit; users saw an unhelpful
// "Internal Server Error" for their own bad iCalendar.
let event = calendar_service
.create_event_from_ical(create_dto, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to create event: {}", e)))?;
.map_err(AppError::from)?;
Ok(Response::builder()
.status(StatusCode::CREATED)
+16 -5
View File
@@ -511,10 +511,13 @@ async fn handle_mkcol(
is_public: Some(false),
};
// See the comment on the vCard PUT path — kind-aware error mapping
// so a client MKCOL body with a bad name / duplicate returns
// 400 / 409 instead of an opaque 500.
addressbook_service
.create_address_book(create_dto)
.await
.map_err(|e| AppError::internal_error(format!("Failed to create address book: {}", e)))?;
.map_err(AppError::from)?;
Ok(Response::builder()
.status(StatusCode::CREATED)
@@ -564,11 +567,17 @@ async fn handle_put(
};
if let Some(existing_contact) = existing {
// Update: delete + recreate from vCard
// Update: delete + recreate from vCard. `AppError::from` maps
// the domain-error ErrorKind onto the right status code:
// NotFound → 404 (contact/address-book gone), AccessDenied →
// 403, InvalidInput → 400 (malformed vCard PUT from the
// client). Naive `internal_error(...)` wrapping used to hide
// all client-input bugs as 500 — same class of bug as the
// CalDAV `create_event_from_ical` path (see #545).
contact_svc
.delete_contact(&existing_contact.id, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to update contact: {}", e)))?;
.map_err(AppError::from)?;
let create_dto = CreateContactVCardDto {
address_book_id: address_book_id.to_string(),
@@ -578,7 +587,7 @@ async fn handle_put(
let contact = contact_svc
.create_contact_from_vcard(create_dto)
.await
.map_err(|e| AppError::internal_error(format!("Failed to recreate contact: {}", e)))?;
.map_err(AppError::from)?;
Ok(Response::builder()
.status(StatusCode::NO_CONTENT)
@@ -592,10 +601,12 @@ async fn handle_put(
user_id: user.id.to_string(),
};
// See the comment above the update branch — same rationale for
// preferring `AppError::from` over blanket 500.
let contact = contact_svc
.create_contact_from_vcard(create_dto)
.await
.map_err(|e| AppError::internal_error(format!("Failed to create contact: {}", e)))?;
.map_err(AppError::from)?;
Ok(Response::builder()
.status(StatusCode::CREATED)