feat: [SDK-4988] add OneSignalResult, OneSignalError, and the per-method payloads - #2710
feat: [SDK-4988] add OneSignalResult, OneSignalError, and the per-method payloads#2710abdulraqeeb33 wants to merge 1 commit into
Conversation
…payloads Pure addition, wired to nothing, so the type design can be reviewed before any API changes shape around it. OneSignalResult<T> is the envelope: isSuccess, the payload, the error, plus toMap/fromMap projections onto the cross-SDK wire schema. OneSignalError carries a list of Detail — one request can fail for several reasons at once — plus the originating Throwable. ErrorCode is an enum rather than a sealed hierarchy so Java gets a native `switch` and the wrapper bridges get a `name()` marshal; each constant carries an ErrorSource saying whether the SDK produced it locally or the backend returned it. Backend catalog codes stay a raw Int on Detail so the backend can add them without an SDK release gating recognition. Detail is nested to keep a top-level `Error` from shadowing kotlin.Error. Unrecognized codes degrade to UNKNOWN with the original text preserved, so a wrapper built against an older SDK survives a newer producer. Co-authored-by: Cursor <cursoragent@cursor.com>
📊 Diff Coverage ReportDiff Coverage Report (Changed Lines Only)Gate: aggregate coverage on changed executable lines must be ≥ 80% (JaCoCo line data for lines touched in the diff). Changed Files Coverage
Overall (aggregate gate)54/66 touched executable lines covered (81.8% — requires ≥ 80%) Per-file detail (informational; gate is aggregate above):
|
|
|
||
| override fun toString(): String = "LoginData(onesignalId=$onesignalId, externalId=$externalId)" | ||
|
|
||
| internal companion object { |
There was a problem hiding this comment.
i have added this an example, i will get rid of it when i integrate it with the login api
| */ | ||
| class Detail internal constructor( | ||
| /** A stable code, safe to branch on. Never localized. */ | ||
| val code: ErrorCode, |
There was a problem hiding this comment.
i thought were avoiding error codes
|
Ran this through a couple of adversarial passes. Design reads well, but a few things in the parsing half are worth fixing before the wire contract is locked in.
Same cast is erased at the element level, so
On the open question about Two smaller ones: |
Adds the public result model as a pure addition, wired to nothing. Nothing in the SDK returns these types yet — that starts in SDK-4990 — so the type design can be reviewed on its own before any API changes shape around it.
Part of SDK-4783. Ticket: SDK-4988.
Base
Targets
ar/sdk-4986, which introduces the.apidump this PR extends. GitHub will retarget tofeature/async-first-public-apionce 4986 merges. The whole migration lands on that feature branch and merges tomainonce.What's here
OneSignalResult<T>— the envelope:isSuccess, the payload, the error, plustoMap/fromMapprojections onto the cross-SDK wire schema.OneSignalError— a list ofDetailplus the originatingThrowable.ErrorCode/ErrorSource— the shared code catalog.InitData,LoginData,LogoutData,UpdateUserJwtData— the per-method payloads.Decisions worth your attention
An enum, not a sealed hierarchy. A sealed
ErrorCode.Client.StorageLockedreads well in Kotlin and badly everywhere else — from Java it'sErrorCode.Client.StorageLocked.INSTANCEand can't beswitched on, only chained throughinstanceof. An enum compiles to a realjava.lang.Enum, so Java gets a nativeswitchand the wrapper bridges get aname()marshal. The exhaustiveness argument for sealed doesn't apply: adding an enum constant breaks a Kotlinwhenin exactly the same way.Backend codes stay a raw
Int. The backend adds catalog codes on its own schedule, and an SDK release must not be what unblocks recognizing one.BACKEND_ERRORplusDetail.backendCodekeeps that half open-ended.Detailis nested rather than a top-levelError. A top-levelcom.onesignal.Errorwould shadowkotlin.Error, which is auto-imported into every Kotlin file, andjava.lang.Errorin any Java file that imports it.No
retryableorhttpStatus. Both would be guesses the SDK can't currently back — nothing below the entry points reports a status code yet (SDK-4989), and retryability would be hardcoded per call site rather than derived from anything. A field customers branch on, populated by a guess, is worse than no field.A list, not one reason. One request can fail for several reasons at once. Everything the SDK raises locally has exactly one, which
firstreads without the indexing ceremony.causeis off the wire. A stack trace can't cross a wrapper bridge and the schema has to stay identical across SDKs, socauseexists only for native Kotlin and Java callers.Wire shape
{ "success": false, "data": null, "error": [ { "code": "STORAGE_LOCKED", "source": "CLIENT", "backendCode": null, "message": "..." } ] }fromMapnever throws on unexpected input:UNKNOWN, original text kept onmessageUNKNOWN, message preservedUNKNOWNreason sofirststays safesuccess: truealongside an errorsuccessis derived, never trustedThat matters because the enum is closed, so a wrapper built against an older SDK will eventually meet a code it can't name — a strict
valueOfwould throw at exactly that moment.Self-review
Five defects found on a fresh adversarial pass, all fixed here:
OneSignalResult's class doc still showed the nestederror.errorenvelope from before the list was flattened. This file is what a wrapper author reads to build their parser, so a wrong doc is a defect in the deliverable.erroris documented "Never empty" andfirstas always safe, but only the factories guarded it — a direct constructor call did not.MutableListcould clear it afterrequirepassed. Now a defensivetoList().null. ADetailwith no message rendered as"STORAGE_LOCKED: null"ingetOrThrow's stack trace.OneSignalResultDatalived inOneSignalResult.ktwhileOneSignalResultData.ktheld only the payloads.One open question
Every constructor here is
internal. That correctly stops customers fabricating SDK results in production, but it also stops them unit-testing their own error-handling branches — there's no supported way to build a failedOneSignalResultin a customer's test suite, and wrapper SDKs hit the same wall unless they live in this Gradle module. Worth deciding now, since a testing entry point is additive and cheap to add later but awkward to discover after adoption.Not in this PR
Nothing populates
BACKEND_ERRORorbackendCode. That detail isn't reachable from the entry points yet; SDK-4989 is what carries it up. This PR only defines where it will go.Test plan
:OneSignal:core:testDebugUnitTest— full core suite green:OneSignal:core:apiCheck—.apidiff showsErrorCode/ErrorSourceasjava/lang/Enum:OneSignal:core:detektandspotlessApplyMade with Cursor