Preserve source compat and document retry-after field change

Add a non-canonical 8-arg SendMessageResult constructor that delegates
to the canonical form with null retry-after. This keeps source
compatibility for any downstream code that constructs the record
directly (tests, mocks) without changing the canonical shape. Records
permit additional constructors alongside the canonical one.

Document the retryAfterSeconds meaning change in the CHANGELOG. The
field was previously populated only for proof-required failures; it
is now populated whenever the server sends a Retry-After header. The
canonical proof-required discriminator is still token != null.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tony Cebzanov 2026-04-12 23:41:42 -04:00
parent d4c57c9270
commit 0b1261c549
3 changed files with 44 additions and 0 deletions

View File

@ -2,6 +2,10 @@
## [Unreleased]
### Changed
- Send message results now surface server-advised retry time for plain rate-limit (HTTP 413) failures, not only for proof-required challenges. The `retryAfterSeconds` field in JSON-RPC `SendMessageResult` is populated whenever the server sends a `Retry-After` header. The canonical way to distinguish proof-required failures remains `token != null`. Text output includes "retry after N seconds" when known.
## [0.14.2] - 2026-04-04
### Added

View File

@ -15,6 +15,32 @@ public record SendMessageResult(
Long rateLimitRetryAfterSeconds
) {
/**
* Source-compatible constructor for callers built against the pre-retry-after record shape.
* Delegates to the canonical constructor with a null retry-after, which is the correct value
* for any result not produced by {@link #from}.
*/
public SendMessageResult(
RecipientAddress address,
boolean isSuccess,
boolean isNetworkFailure,
boolean isUnregisteredFailure,
boolean isIdentityFailure,
boolean isRateLimitFailure,
ProofRequiredException proofRequiredFailure,
boolean isInvalidPreKeyFailure
) {
this(address,
isSuccess,
isNetworkFailure,
isUnregisteredFailure,
isIdentityFailure,
isRateLimitFailure,
proofRequiredFailure,
isInvalidPreKeyFailure,
null);
}
public static SendMessageResult unregisteredFailure(RecipientAddress address) {
return new SendMessageResult(address, false, false, true, false, false, null, false, null);
}

View File

@ -3,6 +3,7 @@ package org.asamk.signal.manager.api;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
class SendMessageResultTest {
@ -21,4 +22,17 @@ class SendMessageResultTest {
assertEquals(2L, SendMessageResult.millisToCeilingSeconds(1500L));
assertEquals(60L, SendMessageResult.millisToCeilingSeconds(60_000L));
}
/**
* Source-compat: callers built against the pre-retry-after record shape use the 8-arg
* constructor. It must continue to compile and produce a record with a null retry-after.
*/
@Test
@SuppressWarnings("deprecation")
void legacyEightArgConstructorPreservesSourceCompat() {
var result = new SendMessageResult(new RecipientAddress(null, null, "+15551234567", null),
true, false, false, false, false, null, false);
assertNull(result.rateLimitRetryAfterSeconds());
}
}