Skip to content

Commit

Permalink
Enable SDK to reattach large files to messages on retry (#487)
Browse files Browse the repository at this point in the history
This PR enables the SDK to re-attach large files when trying to retry the same request body. If a file_path is provided with the attachment request and the file stream is closed, the SDK will attempt to reopen it. The helper function attach_file_request_builder will now set the file_path in the object, so if you're currently using that helper function, the attachment will be ready for reattachment.
  • Loading branch information
mrashed-dev committed Sep 23, 2024
1 parent 8dd54b2 commit ff15a14
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 7 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

### Unreleased
* Enable SDK to reattach large files to messages on retry

### 6.1.1 / 2024-08-20
* Fixed sending attachments less than 3MB

Expand Down
20 changes: 15 additions & 5 deletions lib/nylas/utils/file_utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,29 @@ module FileUtils
# @return The form data to send to the API and the opened files.
# @!visibility private
def self.build_form_request(request_body)
attachments = request_body.delete(:attachments) || request_body.delete("attachments") || []
attachments = request_body[:attachments] || request_body["attachments"] || []
serializable_body = request_body.reject { |key, _| [:attachments, "attachments"].include?(key) }
request_body_copy = Marshal.load(Marshal.dump(serializable_body))

# RestClient will not send a multipart request if there are no attachments
# so we need to return the message payload to be used as a json payload
return [request_body, []] if attachments.empty?
return [request_body_copy, []] if attachments.empty?

# Prepare the data to return
message_payload = request_body.to_json
message_payload = request_body_copy.to_json

form_data = {}
opened_files = []

attachments.each_with_index do |attachment, index|
file = attachment[:content] || attachment["content"]
if file.respond_to?(:closed?) && file.closed?
unless attachment[:file_path]
raise ArgumentError, "The file at index #{index} is closed and no file_path was provided."
end

file = File.open(attachment[:file_path], "rb")
end

form_data.merge!({ "file#{index}" => file })
opened_files << file
end
Expand Down Expand Up @@ -98,7 +107,8 @@ def self.attach_file_request_builder(file_path)
filename: filename,
content_type: content_type,
size: size,
content: content
content: content,
file_path: file_path
}
end
end
Expand Down
43 changes: 41 additions & 2 deletions spec/nylas/utils/file_utils_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
filename: "file.txt",
content_type: "text/plain",
size: 100,
content: mock_file
content: mock_file,
file_path: file_path
)
end

Expand All @@ -37,7 +38,8 @@
filename: "file.txt",
content_type: "application/octet-stream",
size: file_size,
content: mock_file
content: mock_file,
file_path: file_path
)
end
end
Expand Down Expand Up @@ -77,6 +79,43 @@

expect(form_request).to eq([request_body, []])
end

it "raises an error if the file is closed and no file_path is provided" do
attachments = [{ content: mock_file }]
request_body = { attachments: attachments }

allow(mock_file).to receive(:closed?).and_return(true)

expect do
described_class.build_form_request(request_body)
end.to raise_error(ArgumentError, "The file at index 0 is closed and no file_path was provided.")
end

it "opens the file if it is closed and file_path is provided" do
file_path = "/path/to/file.txt"
attachments = [{ content: mock_file, file_path: file_path }]
request_body = { attachments: attachments }

allow(mock_file).to receive(:closed?).and_return(true)
allow(File).to receive(:open).with(file_path, "rb").and_return(mock_file)

form_data, opened_files = described_class.build_form_request(request_body)

expect(form_data).to include("file0" => mock_file)
expect(opened_files).to include(mock_file)
end

it "adds the file to form_data if it is open" do
attachments = [{ content: mock_file }]
request_body = { attachments: attachments }

allow(mock_file).to receive(:closed?).and_return(false)

form_data, opened_files = described_class.build_form_request(request_body)

expect(form_data).to include("file0" => mock_file)
expect(opened_files).to include(mock_file)
end
end

describe "#build_json_request" do
Expand Down

0 comments on commit ff15a14

Please sign in to comment.