Skip to content
Merged
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
2 changes: 1 addition & 1 deletion Sources/StreamAttachments/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>0.7.0</string>
<string>0.8.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
Expand Down
2 changes: 1 addition & 1 deletion Sources/StreamCore/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>0.7.0</string>
<string>0.8.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
Expand Down
9 changes: 9 additions & 0 deletions Sources/StreamCore/OpenAPI/Utils/APIHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,15 @@ public enum APIHelper {
return source
}

/// Maps a value to a single path component and percent-encodes it for use in a URL path.
/// Folds the previous two-step `mapValueToPathItem` + `addingPercentEncoding` into one call
/// so generated endpoint paths stay compact. A `nil` value maps to an empty component.
public static func escapedPathItem(_ value: Any?) -> String {
guard let value else { return "" }
let item = "\(mapValueToPathItem(value))"
return item.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? ""
}

/// maps all values from source to query parameters
///
/// explode attribute is respected: collection values might be either joined or split up into separate key value pairs
Expand Down
2 changes: 1 addition & 1 deletion Sources/StreamCore/Utils/SystemEnvironment+Version.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ import Foundation

enum SystemEnvironment {
/// A Stream Core version.
public static let version: String = "0.7.0"
public static let version: String = "0.8.0"
}
2 changes: 1 addition & 1 deletion Sources/StreamCoreUI/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>0.7.0</string>
<string>0.8.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
Expand Down
57 changes: 57 additions & 0 deletions Tests/StreamCoreTests/OpenAPI/Utils/APIHelper_Tests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// Copyright © 2026 Stream.io Inc. All rights reserved.
//

import Foundation
@testable import StreamCore
import Testing

struct APIHelper_Tests {
// MARK: - escapedPathItem

@Test("Plain string with only allowed characters passes through unchanged")
func escapedPathItem_plainString() {
#expect(APIHelper.escapedPathItem("simple-id_123.~") == "simple-id_123.~")
}

@Test("Characters disallowed in a path are percent-encoded")
func escapedPathItem_encodesDisallowedCharacters() {
#expect(APIHelper.escapedPathItem("a b") == "a%20b")
#expect(APIHelper.escapedPathItem("a#b") == "a%23b")
#expect(APIHelper.escapedPathItem("a?b") == "a%3Fb")
#expect(APIHelper.escapedPathItem("100%") == "100%25")
// ":" is not in .urlPathAllowed, so a channel CID's separator is encoded.
#expect(APIHelper.escapedPathItem("messaging:general") == "messaging%3Ageneral")
}

@Test("Characters allowed in a URL path are not encoded")
func escapedPathItem_keepsPathAllowedCharacters() {
// .urlPathAllowed permits "/", "@", ",", ";", matching the legacy
// per-parameter escaping the generated EndpointPath relied on.
#expect(APIHelper.escapedPathItem("a/b@c,d;e") == "a/b@c,d;e")
}

@Test("Array value is comma-joined then escaped")
func escapedPathItem_arrayValue() {
#expect(APIHelper.escapedPathItem(["a", "b c"]) == "a,b%20c")
}

@Test("nil value maps to an empty component")
func escapedPathItem_nil() {
#expect(APIHelper.escapedPathItem(nil) == "")
}

@Test("Matches the previous two-step mapValueToPathItem + addingPercentEncoding")
func escapedPathItem_matchesLegacyTwoStep() {
for value in ["foo bar/baz", "100% done", "messaging:general"] as [Any] {
let preEscape = "\(APIHelper.mapValueToPathItem(value))"
let postEscape = preEscape.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? ""
#expect(APIHelper.escapedPathItem(value) == postEscape)
}

let array: [String] = ["a", "b c", "d/e"]
let preEscape = "\(APIHelper.mapValueToPathItem(array))"
let postEscape = preEscape.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? ""
#expect(APIHelper.escapedPathItem(array) == postEscape)
}
}
Loading