Skip to content

modifyZones silently drops per-zone failures — adopt the RecordResult pattern #431

Description

@leogdion

Summary

modifyZones returns [ZoneInfo] and has no way to report a per-zone failure. CloudKit's zones/modify returns results positionally, and an individual entry may be an error dictionary rather than a zone — so a batch where some zones succeed and others fail is currently reported to the caller as a partial success with no indication that anything went wrong.

This is the gap the project's standing "RecordResult pattern throughout the API" guidance describes: per-item modify failures should be surfaced with the success-or-failure pattern everywhere — subscriptions, zones — not just records.

Surfaced during #386 verification. Deliberately kept out of #427 because fixing it is a breaking return-type change, not a schema addition.

Current behavior

Sources/MistKit/CloudKitService/CloudKitService+ModifyZones.swift:90:

return try (zonesData.zones ?? []).map { try ZoneInfo(fromZoneID: $0.zoneID) }

Every entry is mapped straight through as a success. There is no failure branch, so an errored entry either fails conversion (surfacing as an opaque whole-batch error that names no specific zone) or is dropped.

The schema can't express the alternative either — after #427, ZonesModifyResponse.zones is array of Zone, and Zone models only zoneID / syncToken / atomic. There is no error variant, so a per-zone error dictionary has nowhere to decode into.

Why it matters

zones/modify is a batch endpoint. The realistic failure is partial: creating five zones where one already exists, or deleting zones where one is missing. Today the caller gets a shorter-than-expected array and cannot tell which zone failed or why — the serverErrorCode and reason Apple returns are discarded.

Precedent already exists in the codebase for the right shape. #429 introduces exactly this for the new change-tracking endpoints:

  • ZoneChangeResult / ZoneRecordChangesResult — per-zone success-or-failure
  • ZoneOperationFailure — carries identifier, serverErrorCode, reason
  • DatabaseChangesResult.changedZones / .failures accessors

So ZoneOperationFailure already exists and is already rendered by CloudKitError. This issue is about applying the same treatment to modifyZones.

Proposed change

  1. Spec — give the zones/modify response items a oneOf over the zone shape and a zone error dictionary (serverErrorCode, reason, and the zone identifier), then regenerate. Verify the error shape against Apple's archived reference first — the "Zone Fetch Error Dictionary" is documented, but as with Zone schemas only model { zoneID } — enrich with sync/atomic metadata + create options #386 the payload docs are abbreviated, so confirm before encoding.
  2. Service — change modifyZones to return per-zone results rather than [ZoneInfo], reusing ZoneChangeResult / ZoneOperationFailure from Add changes/database and changes/zone endpoints; deprecate zones/changes (#401, #47, #46) #429 rather than introducing a parallel type. Provide .zones / .failures accessors mirroring DatabaseChangesResult.
  3. Convenience wrapperscreateZone / deleteZone operate on a single zone and can keep throwing on failure; they should surface the specific ZoneOperationFailure instead of a generic error.

Compatibility

This breaks source compatibility for modifyZones callers — the return type changes. Best landed while still in beta. Examples/BushelCloud and Examples/CelestraCloud should be checked for call sites; note that changes isolated to those subrepos belong in their own repos.

Sequencing

Best done after #429 merges, so ZoneOperationFailure and the ZoneChangeResult pattern are already on the branch and this doesn't duplicate them.

Related: #386, #427, #429, #45

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions