URL-correctness and credential-scope fixes, plus the PaginatedRESTClient 0.2.0 bump. Each fix carries a regression test verified to fail without it.
Fixed
- Path segments are no longer double percent-encoded (#10).
pathSegmentescaped an id andURL.appending(path:)escaped it again, soada+test@example.comwas requested asada%252Btest%2540example.com— the server saw a literalada%2Btest%40example.comand 404'd, or addressed a different record. SCIM/Users/{id}was the most likely trigger, since many deployments use the email as the resource id. Every URL now goes through one funnel that never re-encodes. The old suite used only bare ids liket1, which is why this was invisible. - A single malformed SCIM row no longer empties the directory (#11). The
try?wrapped the array rather than the element, so one numericidwhere aString?was expected madescimUsers()/scimGroups()return an empty list — with no error and no log line — for a fully populated directory. Now decodes leniently per element, like the other list endpoints. +is now escaped in search queries (#12).URLComponentsleaves+literal, and the Rack/Rails backend decodes it as a space, soC++and plus-addressed emails were searched for as something else entirely.allUsersno longer duplicates a user when a row is dropped (#13). Fan-out offsets were derived from the decoded count, but the server pages by requested limit — so one malformed record shifted every subsequent offset and returned a user twice, feeding duplicate keys to a SwiftUIForEach. Offsets now come from the page size.- The Bearer token is no longer sent to a foreign host (#14).
cursorURLreturned any absolute URL from a servernextfield unvalidated, andauthorizedGETattaches the credential unconditionally — so a response naming another host received the user's personal access token. Cursors are now constrained tobaseURL's origin (scheme, host, port), and anything else is rejected.
Upgrading
PaginatedRESTClient is now 0.2.0. HackerRankPage's Item is constrained to Identifiable so each page can supply a stable identity and keep the parallel fetch path — source-breaking in principle, though all ten item types already conformed. pageSize is declared as the API's documented maximum of 100, sourced from the same constant every request already sends as its limit, so the two cannot drift.
Full changelog: 0.5.0...0.6.0