-
Notifications
You must be signed in to change notification settings - Fork 225
Added a new upload endpoint #3788
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
dc691a0
5f001eb
28236bf
89e48a2
3f16dee
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -4,6 +4,39 @@ | |||||||
|
||||||||
import Foundation | ||||||||
|
||||||||
public struct StreamAttachment<Payload> { | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Stream prefix could be a bit generic, and it does not give enough context on how this is used. What about this:
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another option is to try to just reuse the API from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. About the naming, I would actually prefer to keep |
||||||||
/// The attachment type. | ||||||||
public let type: AttachmentType | ||||||||
|
||||||||
/// The attachment payload. | ||||||||
public var payload: Payload | ||||||||
|
||||||||
/// The downloading state of the attachment. | ||||||||
/// | ||||||||
/// Reflects the downloading progress for attachments. | ||||||||
public let downloadingState: AttachmentDownloadingState? | ||||||||
|
||||||||
/// The uploading state of the attachment. | ||||||||
/// | ||||||||
/// Reflects uploading progress for local attachments that require file uploading. | ||||||||
/// Is `nil` for local attachments that don't need to be uploaded. | ||||||||
/// | ||||||||
/// Becomes `nil` when the message with the current attachment is sent. | ||||||||
public let uploadingState: AttachmentUploadingState? | ||||||||
|
||||||||
public init( | ||||||||
type: AttachmentType, | ||||||||
payload: Payload, | ||||||||
downloadingState: AttachmentDownloadingState?, | ||||||||
uploadingState: AttachmentUploadingState? | ||||||||
) { | ||||||||
self.type = type | ||||||||
self.payload = payload | ||||||||
self.downloadingState = downloadingState | ||||||||
self.uploadingState = uploadingState | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
/// A type representing a chat message attachment. | ||||||||
/// `ChatMessageAttachment<Payload>` is an immutable snapshot of message attachment at the given time. | ||||||||
@dynamicMemberLookup | ||||||||
|
@@ -81,6 +114,13 @@ public struct AttachmentUploadingState: Hashable { | |||||||
|
||||||||
/// The information about file size/mimeType. | ||||||||
public let file: AttachmentFile | ||||||||
|
||||||||
/// Public init. | ||||||||
public init(localFileURL: URL, state: LocalAttachmentState, file: AttachmentFile) { | ||||||||
self.localFileURL = localFileURL | ||||||||
self.state = state | ||||||||
self.file = file | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
// MARK: - Type erasure/recovery | ||||||||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -28,7 +28,7 @@ extension EndpointPath: Equatable { | |||||||||||||||||
case let (.channelEvent(string1), .channelEvent(string2)): return string1 == string2 | ||||||||||||||||||
case let (.stopWatchingChannel(string1), .stopWatchingChannel(string2)): return string1 == string2 | ||||||||||||||||||
case let (.pinnedMessages(string1), .pinnedMessages(string2)): return string1 == string2 | ||||||||||||||||||
case let (.uploadAttachment(channelId1, type1), .uploadAttachment(channelId2, type2)): return channelId1 == channelId2 && | ||||||||||||||||||
case let (.uploadChannelAttachment(channelId1, type1), .uploadChannelAttachment(channelId2, type2)): return channelId1 == channelId2 && | ||||||||||||||||||
type1 == | ||||||||||||||||||
type2 | ||||||||||||||||||
Comment on lines
+31
to
33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainAdd Equatable branch for the new You updated the renamed channel-scoped case, but the new standalone path ( Apply this diff near the existing upload case: case let (.uploadChannelAttachment(channelId1, type1), .uploadChannelAttachment(channelId2, type2)): return channelId1 == channelId2 &&
type1 ==
type2
+case let (.uploadAttachment(type1), .uploadAttachment(type2)): return type1 == type2 Run this to confirm we have tests that rely on path equality for the new case (and to ensure the new branch is exercised): 🏁 Script executed: #!/bin/bash
# Find usages of the standalone upload path in tests
rg -nP --type=swift -C2 '\.uploadAttachment\s*\(\s*type:' Tests TestTools Sources \
| sed -n '1,200p'
# Check Equatable extension covers standalone upload
rg -nP --type=swift 'extension\s+EndpointPath\s*:\s*Equatable' -A120 TestTools \
| rg -nP 'uploadAttachment' Length of output: 1143 🏁 Script executed: #!/bin/bash
# Locate the Equatable extension file under TestTools
find TestTools -type f -name "*Equatable.swift"
# Display its first 200 lines if found
equiv_file=$(find TestTools -type f -name "*Equatable.swift" | head -n1)
if [ -n "$equiv_file" ]; then
echo "== Showing first 200 lines of $equiv_file =="
sed -n '1,200p' "$equiv_file"
echo "== Searching for uploadAttachment in $equiv_file =="
grep -n "uploadAttachment" "$equiv_file" || echo "No matches found in $equiv_file"
else
echo "No Equatable extension file found under TestTools"
fi
# Search across TestTools for any mentions of uploadAttachment
echo "== Searching TestTools for uploadAttachment usage =="
rg -n --type swift 'uploadAttachment' TestTools || echo "No uploadAttachment references in TestTools" Length of output: 5363 🏁 Script executed: #!/bin/bash
# Inspect the EndpointPath Equatable extension for uploadAttachment handling
equiv="TestTools/StreamChatTestTools/Extensions/EndpoinPath+Equatable.swift"
if [ -f "$equiv" ]; then
echo "== File exists: $equiv =="
sed -n '1,200p' "$equiv"
echo "== Searching for uploadChannelAttachment =="
grep -n "uploadChannelAttachment" "$equiv" || echo "None"
echo "== Searching for uploadAttachment =="
grep -n "uploadAttachment" "$equiv" || echo "None"
else
echo "$equiv not found"
fi Length of output: 4316 Add missing Equatable branch for Our inspection of Please add the following case immediately after the existing case let (.uploadChannelAttachment(channelId1, type1), .uploadChannelAttachment(channelId2, type2)): return channelId1 == channelId2 &&
type1 ==
type2
+case let (.uploadAttachment(type1), .uploadAttachment(type2)): return type1 == type2 This will ensure that equality between two 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||
case let (.sendMessage(channelId1), .sendMessage(channelId2)): return channelId1 == channelId2 | ||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
// | ||
// Copyright © 2025 Stream.io Inc. All rights reserved. | ||
// | ||
|
||
@testable import StreamChat | ||
import Foundation | ||
|
||
public extension StreamAttachment { | ||
/// Creates a new `ChatMessageFileAttachment` object from the provided data. | ||
static func mock( | ||
payload: Payload, | ||
title: String = "Sample.png", | ||
assetURL: URL = URL(string: "http://asset.url")!, | ||
file: AttachmentFile = AttachmentFile(type: .png, size: 120, mimeType: "image/png"), | ||
localState: LocalAttachmentState? = .uploaded, | ||
localDownloadState: LocalAttachmentDownloadState? = nil, | ||
uploadingState: AttachmentUploadingState? = nil, | ||
extraData: [String: RawJSON]? = nil | ||
) -> Self { | ||
.init( | ||
type: .image, | ||
payload: payload, | ||
downloadingState: localDownloadState.map { | ||
.init( | ||
localFileURL: $0 == .downloaded ? .newTemporaryFileURL() : nil, | ||
state: $0, | ||
file: file | ||
) | ||
}, | ||
uploadingState: uploadingState ?? localState.map { | ||
.init( | ||
localFileURL: assetURL, | ||
state: $0, | ||
file: file | ||
) | ||
} | ||
) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Renaming the former
.uploadAttachment(channelId:type:)
breaks Codable compatibility; consider a backward-compatible strategyBecause
EndpointPath
isCodable
, changing the case name and its payload shape (from two associated values to one) will fail to decode any previously persisted values (e.g., offline request queue, cached requests) that used the legacy case. This can silently drop queued work or crash decode paths after app upgrade.Two viable approaches:
case uploadAttachment(channelId: String, type: String)
(legacy semantics, unchanged)case uploadStandaloneAttachment(String)
(new uploads/{type})Update call-sites accordingly.
init(from:)
that attempts to decode the legacy representation (uploadAttachment
with a keyed object{channelId,type}
) and map it to the newuploadChannelAttachment
, while also supporting the new single-valueuploadAttachment(String)
shape.If you do intend this to be a breaking change, please call it out in the changelog and migration docs. Otherwise, consider the diff below to avoid breaking persisted payloads:
Follow-ups outside this file: switch call-sites/tests from
.uploadChannelAttachment
to.uploadAttachment(channelId:type:)
and rename references to the new standalone case as needed. I can generate a PR-wide patch if helpful.Run this repo scan to see if
EndpointPath
values are persisted (to gauge the impact) and where to update call-sites:Also applies to: 129-131
🏁 Script executed:
Length of output: 54439
Preserve Codable compatibility for queued endpoints in EndpointPath
The
EndpointPath
enum is serialized and deserialized by the offline requests queue (see OfflineRequestsRepository.swift:198–199 forJSONDecoder.stream.decode
and 233–235 forJSONEncoder.stream.encode
). Renaming the channel-scoped upload case and changing its associated values will break decoding of any endpoints persisted before this change, leading to silent failure or data loss in the offline queue.Locations needing attention:
Ensure the legacy
.uploadAttachment(channelId:type:)
case remains intact for backward compatibility.Confirm that decoding logic continues to expect the original case, or implement a custom
init(from:)
inEndpointPath
to handle both legacy and new representations.Suggested diff in
EndpointPath.swift
:Follow-ups:
.uploadChannelAttachment
to use.uploadAttachment(channelId:type:)
..uploadStandaloneAttachment
for the new uploads endpoint.📝 Committable suggestion