diff --git a/CHANGELOG.md b/CHANGELOG.md index aed6787d..0a8bd97b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/lib/nylas/utils/file_utils.rb b/lib/nylas/utils/file_utils.rb index fff71f04..1e730f33 100644 --- a/lib/nylas/utils/file_utils.rb +++ b/lib/nylas/utils/file_utils.rb @@ -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 @@ -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 diff --git a/spec/nylas/utils/file_utils_spec.rb b/spec/nylas/utils/file_utils_spec.rb index ac896192..e2357b8a 100644 --- a/spec/nylas/utils/file_utils_spec.rb +++ b/spec/nylas/utils/file_utils_spec.rb @@ -21,7 +21,8 @@ filename: "file.txt", content_type: "text/plain", size: 100, - content: mock_file + content: mock_file, + file_path: file_path ) end @@ -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 @@ -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