Skip to content

feat(exception): add typed UserConflictException and conflict error codes - #374

Merged
aviadl merged 1 commit into
mainfrom
feat/user-conflict-exception
Sep 15, 2026
Merged

aviadl merged 1 commit into
mainfrom
feat/user-conflict-exception

Conversation

@aviadl

@aviadl aviadl commented Sep 15, 2026

Copy link
Copy Markdown
Member

Related issues

Part of https://github.com/descope/etc/issues/18009 and https://github.com/descope/etc/issues/18488
Backend counterpart: https://github.com/descope/backend/pull/2657

Problem

1.5.0 shipped failOnConflict on updateEmail/updatePhone (#360), but a caller who sets it has nothing to catch. A conflict arrives as a generic ServerCommonException, so the only way to detect it is string matching on getCode(), and the relevant constants are not exposed. The default behavior it guards against is severe: without failOnConflict the server merges the colliding user into the one being updated and deletes it, returning 200.

What this adds

  • ErrorCode: USER_UPDATE_CONFLICT (E111127) - the management conflict code introduced in descope/backend#2657, returned with HTTP 409 by update/email and update/phone. AUTH_USER_UPDATE_CONFLICT (E062125) - onetimeservice's UpdateUserConflict, returned from auth flows and already live today. Plus USER_ALREADY_EXISTS (E062107), USER_NOT_FOUND (E112102), BAD_REQUEST (E011001) and VALIDATION_FAILURE (E011003).
  • UserConflictException - new typed exception, same shape as RateLimitExceededException.
  • AbstractProxyImpl - throws it when the response carries either conflict code.
  • README - a "Avoiding silent merges when updating an email or phone" section under Manage Users.

On the four codes named in the issues

They were checked against backend main rather than taken at face value, since the issue text guesses at some of them:

Code What it actually is Handling
E062107 onetimeservice UserAlreadyExists Exposed as a constant. Never returned by the mgmt update endpoints, so not mapped to the conflict exception
E011001 common BadRequest, "Request is malformed" Exposed as a constant only - generic, reused everywhere
E011003 common ValidationFailure (protoc-gen-validate) Exposed as a constant only
E112102 managementservice UserNotFound - the issue describes it as "login ID already exists", which it is not Exposed under its real name

The code that actually means "this identifier belongs to another user" for these endpoints is E111127.

Not in this PR

updateLoginId(loginId, newLoginId, failOnConflict). The backend UpdateUserLoginIDRequest has no such field today (explicitly out of scope of backend#2657), so the argument would be silently dropped and callers would believe they were protected when they are not. It needs the backend change first.

Tests

  • AbstractProxyImplTest: both conflict codes surface as UserConflictException with the right code and message; the four generic codes do not.
  • UserServiceImplTest: two functional tests covering the real conflict against a live project - with failOnConflict the call fails and both users survive, without it the call returns 200 and the other user's userId is gone. Both pass today. The conflict assertion tightens to UserConflictException + E111127 automatically once backend#2657 is deployed; until then it accepts the current generic error (marked with a comment to drop that branch after the deploy).

Full mvn test was run against a stashed baseline as well: identical failure sets, so no new failures. The 5 failures present in both are environmental (project region env-var test, and functional SSO/auth tests failing on E074106 "license does not match requirement" for the test project). mvn checkstyle:check is clean.

All changes are additive and backward compatible.

🤖 Generated with Claude Code

https://claude.ai/code/session_0136Cgn4PC3tt7RomWgvKkEY

…odes

Callers that pass failOnConflict on updateEmail/updatePhone had nothing to catch:
any conflict surfaced as a generic ServerCommonException, so the only option was
string matching on getCode().

- ErrorCode gains USER_UPDATE_CONFLICT (E111127, the management conflict code
  added in descope/backend#2657) and AUTH_USER_UPDATE_CONFLICT (E062125, the
  onetimeservice conflict returned from auth flows), plus USER_ALREADY_EXISTS,
  USER_NOT_FOUND, BAD_REQUEST and VALIDATION_FAILURE for reference.
- Responses carrying either conflict code now throw UserConflictException. The
  generic bad request and validation codes are deliberately not mapped, they are
  reused across unrelated endpoints.
- README documents the silent merge risk and how to guard against it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0136Cgn4PC3tt7RomWgvKkEY
@shuni-bot

shuni-bot Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

🐕 Shuni Review

This PR adds typed user-conflict exceptions, exposes error-code constants, and tests conflict and merge behavior.

Actionable comments posted: 2

  • 2 🟡 Minor: unrelated errors can pass functional assertions; partial test setup can leak live users.

Merge risk: 🟢 Low: the verified issues affect functional-test reliability and cleanup rather than the exception dispatch logic.

See inline comments for details.

🤖 Model: gpt-6-astra · Effort: high


Review scope: Full review

Reviewed files (5)
  • src/main/java/com/descope/exception/ErrorCode.java
  • src/main/java/com/descope/exception/UserConflictException.java
  • src/main/java/com/descope/proxy/impl/AbstractProxyImpl.java
  • src/test/java/com/descope/proxy/impl/AbstractProxyImplTest.java
  • src/test/java/com/descope/sdk/mgmt/impl/UserServiceImplTest.java

🐕 Review complete — View session on Shuni Portal 🐾

🤖 Model: gpt-6-astra · Effort: high

@aviadl
aviadl requested a review from slavikm September 15, 2026 11:12

@shuni-bot shuni-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐕 Shuni review: 2 inline findings. Every changed file in this pull request was reviewed.

The full review summary is in Shuni's sticky review comment on this pull request.

Comment thread src/test/java/com/descope/sdk/mgmt/impl/UserServiceImplTest.java
Comment thread src/test/java/com/descope/sdk/mgmt/impl/UserServiceImplTest.java
@aviadl
aviadl enabled auto-merge (squash) September 15, 2026 12:23
@aviadl
aviadl merged commit 021df4a into main Sep 15, 2026
20 checks passed
@aviadl
aviadl deleted the feat/user-conflict-exception branch September 15, 2026 18:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants