Skip to content

Use OpenAPI model and v2 endpoint for role search#4167

Merged
laevandus merged 3 commits into
developfrom
open-api-role-search
Jul 13, 2026
Merged

Use OpenAPI model and v2 endpoint for role search#4167
laevandus merged 3 commits into
developfrom
open-api-role-search

Conversation

@laevandus

@laevandus laevandus commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

🔗 Issue Links

Resolves: IOS-1832

🎯 Goal

Migrate the roles search endpoint to the OpenAPI-generated model and the v2 endpoint.

📝 Summary

  • Replace the hand-written Role model and RolePayloads with the OpenAPI-generated Role and SearchRolesResponse. OpenAPI model is the public model now.
  • Point searchRoles at the v2 GET /api/v2/roles/search operation.

🛠 Implementation

  • Add SearchRoles/SearchRolesResponse/Role to allowed_endpoints/allowed_models in Scripts/openapi_generate.sh (both lists kept alphabetical) and regenerate.
  • Remove the migrated v1 rolesSearch case from the inject_v1_endpoint_paths heredocs.
  • Adapt the endpoint factory, repository, and callers to the generated types; add Role+Identifiable to keep the existing conformance.
  • Update the affected unit tests.

🎨 Showcase

Not applicable.

Before After
img img

🧪 Manual Testing Notes

Verified in the demo app that role search returns the expected results:

let client = StreamChatWrapper.shared.client!
let controller = client.roleSearchController()
controller.searchRoles(text: "c") { result in
    switch result {
    case .success(let roles):
        print(roles.map(\.name)) // ["channel_member", "channel_moderator"]
    case .failure(let error):
        print(error)
    }

    print(controller.roles)
}

☑️ Contributor Checklist

  • I have signed the Stream CLA (required)
  • This change should be manually QAed
  • Changelog is updated with client-facing changes
  • Changelog is updated with new localization keys
  • New code is covered by unit tests
  • Documentation has been updated in the docs-content repo

Summary by CodeRabbit

  • New Features
    • Role search now targets /api/v2/roles/search and returns SearchRolesResponse.
  • Bug Fixes
    • Offline queuing behavior updated for the renamed role-search endpoint.
    • Role search decoding/mapping updated to use the new generated response models.
  • Breaking/Behavior Changes
    • Role now exposes id derived from name and uses custom (with legacy isCustom deprecated).
  • Tests
    • Updated unit tests for the new endpoint/response types and Role API.
  • Chores
    • Updated the core SDK dependency and regenerated OpenAPI artifacts.

@laevandus
laevandus requested a review from a team as a code owner July 10, 2026 08:18
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e1c5199d-92b7-4218-90ce-75f1e3ef4743

📥 Commits

Reviewing files that changed from the base of the PR and between 015c450 and 2417236.

⛔ Files ignored due to path filters (18)
  • Sources/StreamChat/Generated/OpenAPI/APIs/DefaultEndpoints.swift is excluded by !**/generated/**
  • Sources/StreamChat/Generated/OpenAPI/models/AppSettings.swift is excluded by !**/generated/**
  • Sources/StreamChat/Generated/OpenAPI/models/AttachmentActionPayload.swift is excluded by !**/generated/**
  • Sources/StreamChat/Generated/OpenAPI/models/AttachmentFieldPayload.swift is excluded by !**/generated/**
  • Sources/StreamChat/Generated/OpenAPI/models/BlockUsersResponse.swift is excluded by !**/generated/**
  • Sources/StreamChat/Generated/OpenAPI/models/BlockedUserResponse.swift is excluded by !**/generated/**
  • Sources/StreamChat/Generated/OpenAPI/models/CreateDeviceRequest.swift is excluded by !**/generated/**
  • Sources/StreamChat/Generated/OpenAPI/models/Device.swift is excluded by !**/generated/**
  • Sources/StreamChat/Generated/OpenAPI/models/GetApplicationResponse.swift is excluded by !**/generated/**
  • Sources/StreamChat/Generated/OpenAPI/models/GetBlockedUsersResponse.swift is excluded by !**/generated/**
  • Sources/StreamChat/Generated/OpenAPI/models/GetOGResponse.swift is excluded by !**/generated/**
  • Sources/StreamChat/Generated/OpenAPI/models/GiphyImageData.swift is excluded by !**/generated/**
  • Sources/StreamChat/Generated/OpenAPI/models/GiphyImages.swift is excluded by !**/generated/**
  • Sources/StreamChat/Generated/OpenAPI/models/ListDevicesResponse.swift is excluded by !**/generated/**
  • Sources/StreamChat/Generated/OpenAPI/models/Role.swift is excluded by !**/generated/**
  • Sources/StreamChat/Generated/OpenAPI/models/SearchRolesResponse.swift is excluded by !**/generated/**
  • Sources/StreamChat/Generated/OpenAPI/models/UploadConfig.swift is excluded by !**/generated/**
  • Sources/StreamChat/Generated/OpenAPI/models/UserResponse.swift is excluded by !**/generated/**
📒 Files selected for processing (1)
  • Scripts/openapi_generate.sh

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.


📝 Walkthrough

Walkthrough

Role search now uses generated public Role and SearchRolesResponse models, the .searchRoles endpoint path, direct repository results, updated offline handling, and revised tests and OpenAPI generation rules.

Changes

Role search API migration

Layer / File(s) Summary
Generated role contract
Package.swift, StreamChat.xcodeproj/project.pbxproj, Scripts/openapi_generate.sh, Sources/StreamChat/Models/..., Tests/StreamChatTests/APIClient/Endpoints/Payloads/RolePayloads_Tests.swift, Tests/StreamChatTests/Models/Role_Tests.swift
The generated Role and SearchRolesResponse models are retained and publicized, role timestamps become optional, and legacy role payload/model usage is replaced.
Search roles endpoint flow
Sources/StreamChat/APIClient/Endpoints/..., Sources/StreamChat/Repositories/RolesRepository.swift, Tests/StreamChatTests/APIClient/Endpoints/..., Tests/StreamChatTests/Repositories/RolesRepository_Tests.swift
Role search uses .searchRoles, maps query fields into request parameters, returns SearchRolesResponse.roles directly, and updates offline and repository assertions.
Role search validation
Tests/StreamChatTests/Controllers/RoleSearchController/*, Tests/StreamChatTests/MentionSuggestions/..., Tests/StreamChatTests/StateLayer/...
Role search controller, mention suggestion, and state-layer fixtures use the updated Role construction and response behavior.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Suggested labels: ✅ Feature

Suggested reviewers: martinmitrevski

Sequence Diagram(s)

sequenceDiagram
  participant RoleSearchController
  participant RolesRepository
  participant RoleEndpoints
  participant SearchRolesResponse
  RoleSearchController->>RolesRepository: searchRoles(query)
  RolesRepository->>RoleEndpoints: build searchRoles endpoint
  RoleEndpoints->>SearchRolesResponse: decode response
  RolesRepository-->>RoleSearchController: return response.roles
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: migrating role search to OpenAPI models and the v2 endpoint.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch open-api-role-search

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment on lines +182 to +183
optionalize_property Role createdAt
optionalize_property Role updatedAt

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Avoids API breaking change (cleanup for v6).

Comment on lines -312 to +313
queryItems: [
"id": APIHelper.convertAnyToString(id)
],
queryItems: APIHelper.mapValuesToQueryDictionary([
"id": id
]),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

New function from StreamCore and generated by the OpenAPI generator. Fixes nil handling for optional query items.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
Tests/StreamChatTests/APIClient/Endpoints/Payloads/RolePayloads_Tests.swift (1)

30-42: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider updating test method names to match the migrated types.

Several test method names still reference the old RolePayload/RoleListPayload types and concepts (mapsCustomKeyToIsCustom, asModel_mapsAllFields, roleListPayload_*) but now decode into Role and SearchRolesResponse. Most notably, test_rolePayload_mapsCustomKeyToIsCustom asserts payload.custom rather than the deprecated isCustom, making the name actively misleading.

♻️ Suggested renames
-    func test_rolePayload_mapsCustomKeyToIsCustom() throws {
+    func test_role_decodesCustomKeyCorrectly() throws {
-    func test_rolePayload_asModel_mapsAllFields() {
+    func test_role_init_mapsAllFields() {
-    func test_roleListPayload_isDecodedCorrectly() throws {
+    func test_searchRolesResponse_isDecodedCorrectly() throws {
-    func test_roleListPayload_whenEmpty_isDecodedCorrectly() throws {
+    func test_searchRolesResponse_whenEmpty_isDecodedCorrectly() throws {

Also applies to: 62-78, 80-104, 106-117

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tests/StreamChatTests/APIClient/Endpoints/Payloads/RolePayloads_Tests.swift`
around lines 30 - 42, Rename the affected tests to reference the migrated types
and current behavior: update role payload names to Role, replace
mapsCustomKeyToIsCustom with a name describing the custom field, rename
asModel_mapsAllFields to reflect Role decoding, and rename roleListPayload_*
tests to reference SearchRolesResponse. Keep each test’s assertions unchanged.
Tests/StreamChatTests/Models/Role_Tests.swift (1)

49-55: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Inequality test doesn't isolate the field under test.

Each Role.dummy() call generates unique createdAt/updatedAt via Date.unique, so every assertion passes due to timestamp differences alone — not due to the name, custom, or scopes differences the test claims to verify. If Role.Equatable ignored custom or scopes, these assertions would still pass, masking a potential bug.

♻️ Proposed fix: pass explicit timestamps to isolate each field
 func test_equatable_whenDifferentValues_thenNotEqual() {
-    let role = Role.dummy(name: "admin")
-
-    XCTAssertNotEqual(role, Role.dummy(name: "moderator"))
-    XCTAssertNotEqual(role, Role.dummy(custom: true, name: "admin"))
-    XCTAssertNotEqual(role, Role.dummy(name: "admin", scopes: ["user"]))
+    let createdAt = Date.unique
+    let updatedAt = Date.unique
+    let role = Role.dummy(createdAt: createdAt, custom: false, name: "admin", scopes: [], updatedAt: updatedAt)
+
+    XCTAssertNotEqual(role, Role.dummy(createdAt: createdAt, custom: false, name: "moderator", scopes: [], updatedAt: updatedAt))
+    XCTAssertNotEqual(role, Role.dummy(createdAt: createdAt, custom: true, name: "admin", scopes: [], updatedAt: updatedAt))
+    XCTAssertNotEqual(role, Role.dummy(createdAt: createdAt, custom: false, name: "admin", scopes: ["user"], updatedAt: updatedAt))
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tests/StreamChatTests/Models/Role_Tests.swift` around lines 49 - 55, Update
test_equatable_whenDifferentValues_thenNotEqual to pass identical explicit
createdAt and updatedAt values to every Role.dummy instance, isolating each
assertion to only the differing name, custom, or scopes field. Reuse shared
timestamp constants or variables so timestamp differences cannot cause the
inequality assertions to pass.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/StreamChat/Models/Role`+Identifiable.swift:
- Around line 1-14: Add Sources/StreamChat/Models/Role+Identifiable.swift to the
StreamChat target by registering it in StreamChat.xcodeproj/project.pbxproj,
including the file reference, build-file entry, and target Sources build phase.
Verify the Role extension is compiled by the app target.

---

Nitpick comments:
In `@Tests/StreamChatTests/APIClient/Endpoints/Payloads/RolePayloads_Tests.swift`:
- Around line 30-42: Rename the affected tests to reference the migrated types
and current behavior: update role payload names to Role, replace
mapsCustomKeyToIsCustom with a name describing the custom field, rename
asModel_mapsAllFields to reflect Role decoding, and rename roleListPayload_*
tests to reference SearchRolesResponse. Keep each test’s assertions unchanged.

In `@Tests/StreamChatTests/Models/Role_Tests.swift`:
- Around line 49-55: Update test_equatable_whenDifferentValues_thenNotEqual to
pass identical explicit createdAt and updatedAt values to every Role.dummy
instance, isolating each assertion to only the differing name, custom, or scopes
field. Reuse shared timestamp constants or variables so timestamp differences
cannot cause the inequality assertions to pass.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e34c3837-2532-4787-8c1e-3dbbc63c29d3

📥 Commits

Reviewing files that changed from the base of the PR and between 1a35173 and 908738c.

⛔ Files ignored due to path filters (3)
  • Sources/StreamChat/Generated/OpenAPI/APIs/DefaultEndpoints.swift is excluded by !**/generated/**
  • Sources/StreamChat/Generated/OpenAPI/models/Role.swift is excluded by !**/generated/**
  • Sources/StreamChat/Generated/OpenAPI/models/SearchRolesResponse.swift is excluded by !**/generated/**
📒 Files selected for processing (19)
  • Package.swift
  • Scripts/openapi_generate.sh
  • Sources/StreamChat/APIClient/Endpoints/EndpointPath+OfflineRequest.swift
  • Sources/StreamChat/APIClient/Endpoints/Payloads/RolePayloads.swift
  • Sources/StreamChat/APIClient/Endpoints/RoleEndpoints.swift
  • Sources/StreamChat/Models/Role+Identifiable.swift
  • Sources/StreamChat/Models/Role.swift
  • Sources/StreamChat/Repositories/RolesRepository.swift
  • StreamChat.xcodeproj/project.pbxproj
  • Tests/StreamChatTests/APIClient/Endpoints/EndpointPath_Tests.swift
  • Tests/StreamChatTests/APIClient/Endpoints/Payloads/RolePayloads_Tests.swift
  • Tests/StreamChatTests/APIClient/Endpoints/RoleEndpoints_Tests.swift
  • Tests/StreamChatTests/Controllers/RoleSearchController/RoleSearchController+Combine_Tests.swift
  • Tests/StreamChatTests/Controllers/RoleSearchController/RoleSearchController_Tests.swift
  • Tests/StreamChatTests/MentionSuggestions/EnhancedMentionSuggestionsProvider_Tests.swift
  • Tests/StreamChatTests/Models/Role_Tests.swift
  • Tests/StreamChatTests/Repositories/RolesRepository_Tests.swift
  • Tests/StreamChatTests/StateLayer/RoleSearchState_Tests.swift
  • Tests/StreamChatTests/StateLayer/RoleSearch_Tests.swift
💤 Files with no reviewable changes (2)
  • Sources/StreamChat/APIClient/Endpoints/Payloads/RolePayloads.swift
  • Sources/StreamChat/Models/Role.swift

Comment thread Sources/StreamChat/Models/Role+Identifiable.swift
@github-actions

Copy link
Copy Markdown
1 Message
📖 There seems to be app changes but CHANGELOG wasn't modified.
Please include an entry if the PR includes user-facing changes.
You can find it at CHANGELOG.md.

Generated by 🚫 Danger

@Stream-SDK-Bot

Copy link
Copy Markdown
Collaborator

SDK Performance

target metric benchmark branch performance status
MessageList Hitches total duration 10 ms 8.35 ms 16.5% 🔼 🟢
Duration 2.6 s 2.54 s 2.31% 🔼 🟢
Hitch time ratio 4 ms per s 3.29 ms per s 17.75% 🔼 🟢
Frame rate 75 fps 78.6 fps 4.8% 🔼 🟢
Number of hitches 1 1.0 0.0% 🟰 🟢

@martinmitrevski martinmitrevski 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.

lgtm ✅

)
}

static func searchRoles(query: String, limit: Int?, nameGt: String?, roleType: String?, includeGlobalRoles: Bool?, requiresConnectionId: Bool = false) -> Endpoint<SearchRolesResponse> {

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.

we should align these in new lines, didn't we have a formatter for that?

@laevandus laevandus Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We don't enforce it with linter/formatter at the moment so this stays unchanged when formatter goes over it (there is no maxWidth currently set). I will apply this on the generator level, nicer if less formatting is needed when code is generated.
Checked that the DefaultAPI template does the same so probably better to keep templates as is.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added special instruction only for openAPI output. Fixed.

@github-actions

Copy link
Copy Markdown

Public Interface

+ public final class Role: Sendable, Codable, JSONEncodable  
+ 
+   case createdAt = "created_at"
+   case custom
+   case name
+   case scopes
+   case updatedAt = "updated_at"
+   
+ 
+   public let createdAt: Date?
+   public let custom: Bool
+   public let name: String
+   public let scopes: [String]
+   public let updatedAt: Date?

+ extension Role: Identifiable  
+ 
+   public var id: String
+   @available(*, deprecated, renamed: "custom") public var isCustom: Bool

- public struct Role: Equatable, Identifiable, Sendable  
- 
-   public let name: String
-   public let isCustom: Bool
-   public let scopes: [String]
-   public let createdAt: Date?
-   public let updatedAt: Date?
-   public var id: String

@laevandus
laevandus merged commit b88dc91 into develop Jul 13, 2026
8 of 10 checks passed
@laevandus
laevandus deleted the open-api-role-search branch July 13, 2026 07:31
@Stream-SDK-Bot

Copy link
Copy Markdown
Collaborator

SDK Size

title develop branch diff status
StreamChat 7.53 MB 7.53 MB +3 KB 🟢
StreamChatUI 4.27 MB 4.27 MB 0 KB 🟢
StreamChatCommonUI 0.84 MB 0.84 MB 0 KB 🟢

@Stream-SDK-Bot

Copy link
Copy Markdown
Collaborator

StreamChat XCSize

Object Diff (bytes)
RolePayloads.o -10137
Role.o +7239
SearchRolesResponse.o +5676
StreamCore.o +2772
MentionSuggestion.o -2482
Show 13 more objects
Object Diff (bytes)
StreamCDNStorage.o +1288
UploadConfig.o -516
Role+Identifiable.o +379
ChannelUpdater.o -362
RolesRepository.o -120
AttachmentDownloader.o +114
AnyAttachmentPayload.o +106
Filter.o -88
MessageUpdater.o -80
EnhancedMentionSuggestionsProvider.o +72
AttachmentTypes.o +72
UnknownChannelEvent.o +64
ChannelListPayload.o -60

@sonarqubecloud

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot mentioned this pull request Jul 16, 2026
6 tasks
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.

3 participants