Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 33 additions & 24 deletions Scripts/openapi_generate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ rm -rf "$OUTPUT_DIR_CHAT"
./build/chat-manager openapi generate-client --language swift \
--opt immutable_models=true --opt access_modifier=internal \
--opt encodable_filter_conditions=true \
--opt struct_over_enum=true \

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.

That's the option for changing enum generation to RawRepresentable based structs without nested types

--spec ./releases/v2/chat-clientside-api.yaml --output "$OUTPUT_DIR_CHAT" )

# 2. Drop the generated async API client — the SDK ships its own APIClient.
Expand Down Expand Up @@ -398,33 +399,19 @@ rename_generated ReactionResponse MessageReactionPayload
rename_generated_type QueryReactionsResponse MessageReactionsPayload
rename_generated ChannelMemberResponse MemberPayload
rename_generated ChannelMute MutedChannelPayload
rename_generated ChannelOwnCapability ChannelCapability
rename_generated ChannelResponse ChannelDetailPayload
rename_generated MuteChannelResponse MutedChannelPayloadResponse

rename_generated_type CreatePollRequestVotingVisibility VotingVisibility
rename_generated_type PushPreferenceInputChatLevel PushPreferenceLevel

rename_generated_type HideChannelResponse EmptyResponse
rename_generated_type MarkDeliveredResponse EmptyResponse
rename_generated_type Response EmptyResponse
rename_generated_type ShowChannelResponse EmptyResponse
rename_generated_type UnmuteResponse EmptyResponse

# Remove a generated property (declaration, doc comment, init param, assignment,
# CodingKeys case). Runs before publicize, so there are no access modifiers to
# handle. Assumes the single-line init the generator emits (step 7 re-wraps).
remove_property() {
local file="$OUTPUT_DIR_CHAT/models/$1.swift"
awk -v p="$2" '
function flush() { for (i = 1; i <= n; i++) print b[i]; n = 0 }
{ s = $0; sub(/^[[:space:]]+/, "", s) }
s ~ /^(\/\/\/|@available)/ { b[++n] = $0; next }
s ~ "^let " p ": " { n = 0; next }
s ~ "^self\\." p " = " p "$" { next }
s ~ "^case " p "( =|$)" { next }
s ~ /^init\(/ { sub("\\(" p ": [^,)]*, ", "("); sub(", " p ": [^,)]*", ""); sub("\\(" p ": [^,)]*\\)", "()") }
{ flush(); print }
' "$file" > "$file.tmp" && mv "$file.tmp" "$file"
}
remove_property FileUploadResponse duration

Comment on lines -410 to -427

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.

Script cleanup, it was a duplicate

retype_property PushPreference chatLevel String PushPreferenceLevel
rename_property PushPreference chatLevel level
restore_nonoptional_property PushPreference level PushPreferenceLevel .all
Expand Down Expand Up @@ -460,6 +447,7 @@ remove_property() {
perl -0777 -pi -e 's/ &&(\n\s*\})/$1/g' "$file"
}
remove_property CurrentUserUnreads duration
remove_property FileUploadResponse duration
remove_property MessageReactionsPayload duration
remove_property PushPreferenceInput callLevel
remove_property PushPreferenceInput chatPreferences
Expand All @@ -485,17 +473,17 @@ retype_property ChannelDetailPayload config ChannelConfigWithInfo ChannelConfig
# Will be changed on the generation side later
require_property ChannelDetailPayload config

remove_nested_enum() {
remove_type() {
local file="$OUTPUT_DIR_CHAT/models/$1.swift"
awk -v e="$2" '
$0 ~ "^ enum " e ":" { skip = 1; next }
skip && /^ }$/ { skip = 0; next }
skip { next }
$0 ~ "^(enum|struct) " e ":" { skip = 1; next }
skip && /^}$/ { skip = 0; next }
skip { next }
{ print }
' "$file" > "$file.tmp" && mv "$file.tmp" "$file"
}
remove_nested_enum PushPreferenceInput PushPreferenceInputCallLevel
remove_nested_enum PushPreferenceInput PushPreferenceInputFeedsLevel
remove_type PushPreferenceInput PushPreferenceInputCallLevel
remove_type PushPreferenceInput PushPreferenceInputFeedsLevel

# 4c. Expose selected generated models as public API. The class and its stored
# properties become public, along with the generated Hashable conformance
Expand All @@ -522,6 +510,27 @@ publicize_model UploadConfig
publicize_model UserGroup
publicize_model UserGroupMember

# Expose a generated RawRepresentable struct as public API. Unlike publicize_model, the
# init must be public too — it is the RawRepresentable requirement — along with every
# static let holding a known value. The struct is looked up by name, since the file
# named after a model also holds the structs generated for its string properties.
publicize_raw_representable() {
local file="$OUTPUT_DIR_CHAT/models/$1.swift"
awk -v n="${2:-$1}" '
$0 ~ "^struct " n ":" { sub(/^struct /, "public struct "); inside = 1; print; next }
inside && /^}$/ { inside = 0; print; next }
inside {
sub(/^ let /, " public let ")
sub(/^ init\(/, " public init(")
sub(/^ static let /, " public static let ")
}
{ print }
' "$file" > "$file.tmp" && mv "$file.tmp" "$file"
}
publicize_raw_representable ChannelCapability
publicize_raw_representable CreatePollRequestBody VotingVisibility
publicize_raw_representable PushPreferenceInput PushPreferenceLevel

# Drop `final` from a generated model so hand-written payloads can subclass it.
unfinalize_model() {
local file="$OUTPUT_DIR_CHAT/models/$1.swift"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ extension ChannelDetailPayload {
memberCount: memberCount,
members: members,
messageCount: messageCount,
ownCapabilities: ownCapabilities?.compactMap(ChannelOwnCapability.init(rawValue:)),
ownCapabilities: ownCapabilities?.map(ChannelCapability.init(rawValue:)),
team: team,
truncatedAt: truncatedAt,
truncatedBy: truncatedBy,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1146,7 +1146,7 @@ public class ChatChannelController: DataController, DelegateCallable, DataStoreP
description: description,
enforceUniqueVote: enforceUniqueVote,
maxVotesAllowed: maxVotesAllowed,
votingVisibility: votingVisibility?.rawValue,
votingVisibility: votingVisibility,
options: options,
custom: extraData
) { [weak self] result in
Expand Down Expand Up @@ -1877,7 +1877,7 @@ public class ChatChannelController: DataController, DelegateCallable, DataStoreP

let channelPreference = PushPreferenceInput(
channelCid: channelId.rawValue,
chatLevel: PushPreferenceInput.PushPreferenceInputChatLevel(rawValue: level.rawValue),
chatLevel: level,
removeDisable: true
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ public extension CurrentChatUserController {
completion: (@MainActor (Result<PushPreference, Error>) -> Void)? = nil
) {
let userPreference = PushPreferenceInput(
chatLevel: PushPreferenceInput.PushPreferenceInputChatLevel(rawValue: level.rawValue),
chatLevel: level,
removeDisable: true
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,20 +274,6 @@ public class PollController: DataController, DelegateCallable, DataStoreProvider
}
}

/// Represents the visibility of votes in a poll.
public struct VotingVisibility: RawRepresentable, Equatable, Sendable {
public let rawValue: String

public init(rawValue: String) {
self.rawValue = rawValue
}

/// Votes are public and can be seen by everyone.
public static let `public` = Self(rawValue: "public")
/// Votes are anonymous and cannot be attributed to individual users.
public static let anonymous = Self(rawValue: "anonymous")
}

extension PollController {
struct Environment {
var pollObserverBuilder: (
Expand Down
2 changes: 1 addition & 1 deletion Sources/StreamChat/Database/DTOs/ChannelDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -682,7 +682,7 @@ extension ChatChannel {
createdBy: dto.createdBy?.asModel(),
config: dto.config.asModel(),
filterTags: Set(dto.filterTags.map(\.name)),
ownCapabilities: Set(dto.ownCapabilities.compactMap(ChannelCapability.init(rawValue:))),
ownCapabilities: Set(dto.ownCapabilities.map(ChannelCapability.init(rawValue:))),
isFrozen: dto.isFrozen,
isDisabled: dto.isDisabled,
isBlocked: dto.isBlocked,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
import Foundation

final class BlockedUserResponse: Sendable, Codable, JSONEncodable {
/// User response object
let blockedUser: UserPayload
/// ID of the user who got blocked
let blockedUserId: String
let createdAt: Date
/// User response object
let user: UserPayload
/// ID of the user who blocked another user
let userId: String
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// Copyright © 2026 Stream.io Inc. All rights reserved.
//

import Foundation

public struct ChannelCapability: RawRepresentable, Codable, Hashable, Sendable {
public let rawValue: String

public init(rawValue: String) {
self.rawValue = rawValue
}

public static let banChannelMembers = Self(rawValue: "ban-channel-members")
public static let castPollVote = Self(rawValue: "cast-poll-vote")
public static let connectEvents = Self(rawValue: "connect-events")
public static let createAttachment = Self(rawValue: "create-attachment")
public static let createMention = Self(rawValue: "create-mention")
public static let deleteAnyMessage = Self(rawValue: "delete-any-message")
public static let deleteChannel = Self(rawValue: "delete-channel")
public static let deleteOwnMessage = Self(rawValue: "delete-own-message")
public static let deliveryEvents = Self(rawValue: "delivery-events")
public static let flagMessage = Self(rawValue: "flag-message")
public static let freezeChannel = Self(rawValue: "freeze-channel")
public static let joinChannel = Self(rawValue: "join-channel")
public static let leaveChannel = Self(rawValue: "leave-channel")
public static let muteChannel = Self(rawValue: "mute-channel")
public static let notifyChannel = Self(rawValue: "notify-channel")
public static let notifyGroup = Self(rawValue: "notify-group")
public static let notifyHere = Self(rawValue: "notify-here")
public static let notifyRole = Self(rawValue: "notify-role")
public static let pinMessage = Self(rawValue: "pin-message")
public static let queryPollVotes = Self(rawValue: "query-poll-votes")
public static let quoteMessage = Self(rawValue: "quote-message")
public static let readEvents = Self(rawValue: "read-events")
public static let searchMessages = Self(rawValue: "search-messages")
public static let sendCustomEvents = Self(rawValue: "send-custom-events")
public static let sendLinks = Self(rawValue: "send-links")
public static let sendMessage = Self(rawValue: "send-message")
public static let sendPoll = Self(rawValue: "send-poll")
public static let sendReaction = Self(rawValue: "send-reaction")
public static let sendReply = Self(rawValue: "send-reply")
public static let sendRestrictedVisibilityMessage = Self(rawValue: "send-restricted-visibility-message")
public static let sendTypingEvents = Self(rawValue: "send-typing-events")
public static let setChannelCooldown = Self(rawValue: "set-channel-cooldown")
public static let shareLocation = Self(rawValue: "share-location")
public static let skipSlowMode = Self(rawValue: "skip-slow-mode")
public static let slowMode = Self(rawValue: "slow-mode")
public static let typingEvents = Self(rawValue: "typing-events")
public static let updateAnyMessage = Self(rawValue: "update-any-message")
public static let updateChannel = Self(rawValue: "update-channel")
public static let updateChannelMembers = Self(rawValue: "update-channel-members")
public static let updateOwnMessage = Self(rawValue: "update-own-message")
public static let updateThread = Self(rawValue: "update-thread")
public static let uploadFile = Self(rawValue: "upload-file")
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ final class ChannelDetailPayload: Sendable, Codable, JSONEncodable {
let cooldown: Int?
/// Date/time of creation
let createdAt: Date
/// User response object
let createdBy: UserPayload?
/// Custom data for this object
let custom: [String: RawJSON]
Expand Down Expand Up @@ -47,11 +48,12 @@ final class ChannelDetailPayload: Sendable, Codable, JSONEncodable {
/// Whether this channel is muted or not
let muted: Bool?
/// List of channel capabilities of authenticated user
let ownCapabilities: [ChannelOwnCapability]?
let ownCapabilities: [ChannelCapability]?
/// Team the channel belongs to (multi-tenant only)
let team: String?
/// Date of the latest truncation of the channel
let truncatedAt: Date?
/// User response object
let truncatedBy: UserPayload?
/// Type of the channel
let type: String
Expand Down Expand Up @@ -81,7 +83,7 @@ final class ChannelDetailPayload: Sendable, Codable, JSONEncodable {
messageCount: Int? = nil,
muteExpiresAt: Date? = nil,
muted: Bool? = nil,
ownCapabilities: [ChannelOwnCapability]? = nil,
ownCapabilities: [ChannelCapability]? = nil,
team: String? = nil,
truncatedAt: Date? = nil,
truncatedBy: UserPayload? = nil,
Expand Down Expand Up @@ -183,7 +185,7 @@ final class ChannelDetailPayload: Sendable, Codable, JSONEncodable {
messageCount = try container.decodeIfPresent(Int.self, forKey: .messageCount)
muteExpiresAt = try container.decodeIfPresent(Date.self, forKey: .muteExpiresAt)
muted = try container.decodeIfPresent(Bool.self, forKey: .muted)
ownCapabilities = try container.decodeIfPresent([ChannelOwnCapability].self, forKey: .ownCapabilities)
ownCapabilities = try container.decodeIfPresent([ChannelCapability].self, forKey: .ownCapabilities)
team = try container.decodeIfPresent(String.self, forKey: .team)
truncatedAt = try container.decodeIfPresent(Date.self, forKey: .truncatedAt)
truncatedBy = try container.decodeIfPresent(UserPayload.self, forKey: .truncatedBy)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ final class ChannelMemberRequest: Sendable, Codable, JSONEncodable {
/// Role of the member in the channel
let channelRole: String?
let custom: [String: RawJSON]?
/// User response object
let user: UserPayload?
let userId: String

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,20 @@

import Foundation

final class CreateDeviceRequest: Sendable, Codable, JSONEncodable {
enum CreateDeviceRequestPushProvider: String, Sendable, Codable, CaseIterable {
case apn
case firebase
case huawei
case xiaomi
case unknown = "_unknown"
struct CreateDeviceRequestPushProvider: RawRepresentable, Codable, Hashable, Sendable {
let rawValue: String

init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let decodedValue = try? container.decode(String.self),
let value = Self(rawValue: decodedValue) {
self = value
} else {
self = .unknown
}
}
init(rawValue: String) {
self.rawValue = rawValue
}

static let apn = Self(rawValue: "apn")
static let firebase = Self(rawValue: "firebase")
static let huawei = Self(rawValue: "huawei")
static let xiaomi = Self(rawValue: "xiaomi")
}

final class CreateDeviceRequest: Sendable, Codable, JSONEncodable {
/// Stable physical device identifier used to deduplicate pushes across push providers (e.g. APNs VoIP and Firebase on the same iOS device). Distinct from 'id', which is the push token.
let hardwareId: String?
/// Device ID
Expand Down
Loading
Loading