Skip to content

OpenAPI: Reuse generated AppSettings model in the public API layer#4159

Merged
laevandus merged 1 commit into
developfrom
open-api-app-settings
Jul 7, 2026
Merged

OpenAPI: Reuse generated AppSettings model in the public API layer#4159
laevandus merged 1 commit into
developfrom
open-api-app-settings

Conversation

@laevandus

@laevandus laevandus commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

🔗 Issue Links

Related: IOS-1620

🎯 Goal

Reuse the OpenAPI-generated model for the public API layer instead of maintaining a duplicated hand-written AppSettings model with a mapping layer.

📝 Summary

  • Rename the generated AppResponseFields to AppSettings and FileUploadConfig to UploadConfig, and generate both as public via a new publicize_model post-processing step (the memberwise init and CodingKeys stay internal).
  • Delete the hand-written AppSettings struct and its asModel() mappings; ChatClient.loadAppSettings now returns the generated model directly.
  • Keep the public API source-compatible: the AppSettings.UploadConfig typealias and the existing UTI/URL validation helpers are preserved in AppSettings+Extensions.swift; sizeLimitInBytes is kept as a computed property and deprecated in favor of the generated sizeLimit.
  • AppSettings is now a final class (previously a struct) and exposes new id and placement properties.

🧪 Manual Testing Notes

Connect a user and confirm attachment upload validation in the composer still respects the dashboard upload configuration (allowed/blocked file types and size limits fetched from /api/v2/app).

☑️ 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
    • App and upload settings are now exposed more consistently in the SDK, making attachment-related configuration available to apps.
  • Bug Fixes
    • Improved attachment size handling so limits are calculated more reliably from app settings.
    • Fixed app settings loading to use the returned app data directly, reducing mismatches in configuration.
  • Chores
    • Updated generated SDK models and test mocks to match the latest settings structure.

@laevandus
laevandus requested a review from a team as a code owner July 6, 2026 12:30
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR renames and publicizes generated OpenAPI models (AppSettings, UploadConfig), switches ChatClient to consume the generated app payload directly, refactors AppSettings+Extensions.swift to extend the generated types with a deprecated sizeLimitInBytes property, and updates ComposerVC and test mocks for the new non-optional sizeLimit field.

Changes

AppSettings/UploadConfig public model migration

Layer / File(s) Summary
Generator renaming and public exposure
Scripts/openapi_generate.sh
Renames generated AppResponseFieldsAppSettings and FileUploadConfigUploadConfig, and adds a publicize_model function that makes the model class and stored properties public.
ChatClient and AppSettings extension refactor
Sources/StreamChat/ChatClient.swift, Sources/StreamChat/Models/AppSettings+Extensions.swift
loadAppSettings now uses payload.app instead of asModel(); AppSettings+Extensions.swift removes manual struct declarations, adding a UploadConfig typealias and a deprecated computed sizeLimitInBytes property.
Consumer updates for sizeLimit shape
Sources/StreamChatUI/Composer/ComposerVC.swift, TestTools/StreamChatTestTools/Mocks/StreamChat/ChatClient_Mock.swift
maxAttachmentSize(for:) uses non-optional sizeLimit values; test mocks build AppSettings/UploadConfig with the new fields and derive sizeLimit from sizeLimitInBytes.

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

Possibly related PRs

  • GetStream/stream-chat-swift#4133: Both PRs modify ChatClient.swift’s loadAppSettings(completion:) to switch to the /api/v2/app response/model flow.
  • GetStream/stream-chat-swift#4148: Both PRs modify Scripts/openapi_generate.sh’s model generation flow, one for generator options and this one for post-generation renaming/publicizing.

Suggested labels: 🟢 QAed

Suggested reviewers: martinmitrevski, nuno-vieira

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: switching the public API to the generated AppSettings model.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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-app-settings

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.

@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: 2

🧹 Nitpick comments (1)
Sources/StreamChat/ChatClient.swift (1)

28-28: 🗄️ Data Integrity & Integration | 🔵 Trivial

Confirm the struct→class change for AppSettings is documented as a breaking API change.

AppSettings moves from a struct to a final class (per PR objectives). Even though it's immutable (let properties), consumers that relied on value semantics (implicit copy-on-assign, potential Equatable/Hashable usage, or @State/diffing based on struct equality) may behave differently now that this is a reference type. Recommend calling this out explicitly in release notes / SemVer bump.

🤖 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 `@Sources/StreamChat/ChatClient.swift` at line 28, Document the AppSettings
type change as a breaking API update in the release notes and SemVer bump
guidance, since ChatClient.appSettings now exposes a final class instead of a
struct. Call out the loss of value semantics and any behavior changes for
consumers that relied on implicit copying, equality/hashability, or
SwiftUI/state diffing, and reference AppSettings and ChatClient.appSettings
explicitly so the change is easy to find.
🤖 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/AppSettings`+Extensions.swift:
- Around line 13-18: The deprecated AppSettings.UploadConfig.sizeLimitInBytes
property currently always returns a value because sizeLimit is non-optional,
which breaks the previous “no limit configured” nil semantics. Update
sizeLimitInBytes to preserve the old behavior by returning nil when sizeLimit is
unset or non-positive (matching the existing sizeLimit <= 0 convention used
elsewhere, such as ComposerVC.maxAttachmentSize), and only return
Int64(sizeLimit) for real configured limits. Keep the change localized to
AppSettings.UploadConfig.sizeLimitInBytes so existing callers continue to branch
correctly on nil versus a valid limit.
- Around line 39-41: The early return in
AppSettings+Extensions.isAllowed(localURL:) lets extensionless files bypass the
configured upload validation entirely. Update this method so a missing
pathExtension does not immediately return true; instead, let the existing
extension and MIME-type allow/block logic run for extensionless URLs, or only
skip validation if the settings explicitly permit it. Use the
isAllowed(localURL:) flow and its related upload restriction checks to keep
dashboard-configured rules enforced.

---

Nitpick comments:
In `@Sources/StreamChat/ChatClient.swift`:
- Line 28: Document the AppSettings type change as a breaking API update in the
release notes and SemVer bump guidance, since ChatClient.appSettings now exposes
a final class instead of a struct. Call out the loss of value semantics and any
behavior changes for consumers that relied on implicit copying,
equality/hashability, or SwiftUI/state diffing, and reference AppSettings and
ChatClient.appSettings explicitly so the change is easy to find.
🪄 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: 298443d9-1e6f-4086-b667-8b5ffb7c6e06

📥 Commits

Reviewing files that changed from the base of the PR and between 5ae5be1 and 57f7ab0.

⛔ Files ignored due to path filters (3)
  • Sources/StreamChat/Generated/OpenAPI/models/AppSettings.swift is excluded by !**/generated/**
  • Sources/StreamChat/Generated/OpenAPI/models/GetApplicationResponse.swift is excluded by !**/generated/**
  • Sources/StreamChat/Generated/OpenAPI/models/UploadConfig.swift is excluded by !**/generated/**
📒 Files selected for processing (5)
  • Scripts/openapi_generate.sh
  • Sources/StreamChat/ChatClient.swift
  • Sources/StreamChat/Models/AppSettings+Extensions.swift
  • Sources/StreamChatUI/Composer/ComposerVC.swift
  • TestTools/StreamChatTestTools/Mocks/StreamChat/ChatClient_Mock.swift

Comment on lines +13 to 18
extension AppSettings.UploadConfig {
/// The file size limit allowed in Bytes.
/// This value is configurable from Stream's Dashboard App Settings.
@available(*, deprecated, renamed: "sizeLimit")
public var sizeLimitInBytes: Int64? { Int64(sizeLimit) }
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

sizeLimitInBytes can no longer represent "no limit configured".

sizeLimit (generated) is a non-optional Int, so Int64(sizeLimit) never returns nil. Given the codebase's own convention elsewhere (ComposerVC.maxAttachmentSize treats sizeLimit <= 0 as "use default"), the deprecated sizeLimitInBytes previously likely returned nil to signal "no limit configured." Any remaining caller (internal or third-party via this public deprecated API) branching on nil vs a value will now always take the "configured" branch, silently changing behavior for the 0/unset case.

🐛 Proposed fix to preserve nil semantics
     `@available`(*, deprecated, renamed: "sizeLimit")
-    public var sizeLimitInBytes: Int64? { Int64(sizeLimit) }
+    public var sizeLimitInBytes: Int64? { sizeLimit > 0 ? Int64(sizeLimit) : nil }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
extension AppSettings.UploadConfig {
/// The file size limit allowed in Bytes.
/// This value is configurable from Stream's Dashboard App Settings.
@available(*, deprecated, renamed: "sizeLimit")
public var sizeLimitInBytes: Int64? { Int64(sizeLimit) }
}
extension AppSettings.UploadConfig {
/// The file size limit allowed in Bytes.
/// This value is configurable from Stream's Dashboard App Settings.
`@available`(*, deprecated, renamed: "sizeLimit")
public var sizeLimitInBytes: Int64? { sizeLimit > 0 ? Int64(sizeLimit) : nil }
}
🤖 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 `@Sources/StreamChat/Models/AppSettings`+Extensions.swift around lines 13 - 18,
The deprecated AppSettings.UploadConfig.sizeLimitInBytes property currently
always returns a value because sizeLimit is non-optional, which breaks the
previous “no limit configured” nil semantics. Update sizeLimitInBytes to
preserve the old behavior by returning nil when sizeLimit is unset or
non-positive (matching the existing sizeLimit <= 0 convention used elsewhere,
such as ComposerVC.maxAttachmentSize), and only return Int64(sizeLimit) for real
configured limits. Keep the change localized to
AppSettings.UploadConfig.sizeLimitInBytes so existing callers continue to branch
correctly on nil versus a valid limit.

Comment on lines 39 to +41
func isAllowed(localURL: URL) -> Bool {
guard !localURL.pathExtension.isEmpty else { return true }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== target file outline =="
ast-grep outline Sources/StreamChat/Models/AppSettings+Extensions.swift --view expanded || true

echo
echo "== target file excerpt =="
nl -ba Sources/StreamChat/Models/AppSettings+Extensions.swift | sed -n '1,220p'

echo
echo "== usages of isAllowed(localURL:) =="
rg -n "isAllowed\\(localURL:" -S Sources || true

echo
echo "== references to AttachmentFileType(ext: or mimeType checks =="
rg -n "AttachmentFileType\\(ext:|mimeType|allowedFileExtensions|allowedMimeTypes|upload validation|isAllowed\\(" Sources -S || true

Repository: GetStream/stream-chat-swift

Length of output: 514


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== target file excerpt =="
sed -n '1,180p' Sources/StreamChat/Models/AppSettings+Extensions.swift | cat -n

echo
echo "== usages of isAllowed(localURL:) =="
rg -n "isAllowed\\(localURL:" Sources -S || true

echo
echo "== nearby upload validation references =="
rg -n "allowedFileExtensions|allowedMimeTypes|isAllowed\\(|AttachmentFileType\\(|mimeType" Sources/StreamChat -S || true

Repository: GetStream/stream-chat-swift

Length of output: 11946


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== AttachmentQueueUploader excerpt around validation =="
sed -n '150,210p' Sources/StreamChat/Workers/Background/AttachmentQueueUploader.swift | cat -n

echo
echo "== AttachmentFileType initializer and mimeType =="
sed -n '260,330p' Sources/StreamChat/Models/Attachments/AttachmentTypes.swift | cat -n

echo
echo "== AttachmentFileType ext-based usage =="
rg -n "init\\(ext:|AttachmentFileType\\(ext:" Sources/StreamChat/Models/Attachments/AttachmentTypes.swift Sources/StreamChat -S || true

Repository: GetStream/stream-chat-swift

Length of output: 6781


Extensionless uploads bypass configured upload validation Sources/StreamChat/Models/AppSettings+Extensions.swift:39

guard !localURL.pathExtension.isEmpty else { return true } skips both the extension and MIME-type allow/block checks, so a local file with no extension can bypass dashboard upload restrictions. If extensionless files should still honor those rules, remove this early return or gate it behind the config checks.

🤖 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 `@Sources/StreamChat/Models/AppSettings`+Extensions.swift around lines 39 - 41,
The early return in AppSettings+Extensions.isAllowed(localURL:) lets
extensionless files bypass the configured upload validation entirely. Update
this method so a missing pathExtension does not immediately return true;
instead, let the existing extension and MIME-type allow/block logic run for
extensionless URLs, or only skip validation if the settings explicitly permit
it. Use the isAllowed(localURL:) flow and its related upload restriction checks
to keep dashboard-configured rules enforced.

@Stream-SDK-Bot

Copy link
Copy Markdown
Collaborator

SDK Size

title develop branch diff status
StreamChat 7.54 MB 7.53 MB -16 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

SDK Performance

target metric benchmark branch performance status
MessageList Hitches total duration 10 ms 1.67 ms 83.3% 🔼 🟢
Duration 2.6 s 2.53 s 2.69% 🔼 🟢
Hitch time ratio 4 ms per s 0.66 ms per s 83.5% 🔼 🟢
Frame rate 75 fps 79.14 fps 5.52% 🔼 🟢
Number of hitches 1 0.2 80.0% 🔼 🟢

@Stream-SDK-Bot

Copy link
Copy Markdown
Collaborator

StreamChat XCSize

Object Diff (bytes)
ChannelListUpdater.o -8384
AppSettings+Extensions.o +8300
ChatClient.o +7146
AppResponseFields.o -6740
FileUploadConfig.o -6509
UploadConfig.o +6502
AppSettings.o -4307
AttachmentQueueUploader.o -744
ChannelUpdater.o +144
MessageUpdater.o +76

@Stream-SDK-Bot

Copy link
Copy Markdown
Collaborator

StreamChatUI XCSize

Object Diff (bytes)
ComposerVC.o -264

@sonarqubecloud

sonarqubecloud Bot commented Jul 6, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Jul 6, 2026

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

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

Public Interface

+ public final class AppSettings: Sendable, Codable, JSONEncodable  
+ 
+   case asyncUrlEnrichEnabled = "async_url_enrich_enabled"
+   case autoTranslationEnabled = "auto_translation_enabled"
+   case fileUploadConfig = "file_upload_config"
+   case id
+   case imageUploadConfig = "image_upload_config"
+   case name
+   case placement
+   
+ 
+   public let asyncUrlEnrichEnabled: Bool
+   public let autoTranslationEnabled: Bool
+   public let fileUploadConfig: UploadConfig
+   public let id: Int
+   public let imageUploadConfig: UploadConfig
+   public let name: String
+   public let placement: String

+ public final class UploadConfig: Sendable, Codable, JSONEncodable  
+ 
+   case allowedFileExtensions = "allowed_file_extensions"
+   case allowedMimeTypes = "allowed_mime_types"
+   case blockedFileExtensions = "blocked_file_extensions"
+   case blockedMimeTypes = "blocked_mime_types"
+   case sizeLimit = "size_limit"
+   
+ 
+   public let allowedFileExtensions: [String]
+   public let allowedMimeTypes: [String]
+   public let blockedFileExtensions: [String]
+   public let blockedMimeTypes: [String]
+   public let sizeLimit: Int

- public struct AppSettings: Sendable  
- 
-   public let name: String
-   public let fileUploadConfig: UploadConfig
-   public let imageUploadConfig: UploadConfig
-   public let autoTranslationEnabled: Bool
-   public let asyncUrlEnrichEnabled: Bool
-   
- 
-   public struct UploadConfig: Sendable  
-   
-     public let allowedFileExtensions: [String]
-     public let blockedFileExtensions: [String]
-     public let allowedMimeTypes: [String]
-     public let blockedMimeTypes: [String]
-     public let sizeLimitInBytes: Int64?

@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

@laevandus
laevandus merged commit e1929ab into develop Jul 7, 2026
22 checks passed
@laevandus
laevandus deleted the open-api-app-settings branch July 7, 2026 09:57
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