faraday-multipart 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7bd6e0b4dcb4d5905af5f8ee93c61a77858f13d1bfbbc00c3b9ea24091ef6af9
4
+ data.tar.gz: fd8d9f6e6cad9ad03e98e2b756be32c6862b23798552149d93568a6902bc6f19
5
+ SHA512:
6
+ metadata.gz: b5644f53bad5e159723fbb174164270dc4de8668eac04d278004c9b11608337771f73295e73160125c954dd6a36b0af702bada137c4f9894a935517c3afc06e7
7
+ data.tar.gz: 37613553511b02097c04d4524e13fe1c3fc946b2549a07dc2b76e9519a570fa88f6f7717964c0cdbcf9425b09b46565cd8b8b73cd3cb8707ee597480d0c67f4b
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## Unreleased
4
+
5
+ * Initial release.
data/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 The Faraday Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,111 @@
1
+ # Faraday Multipart
2
+
3
+ [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/lostisland/faraday-multipart/ci)](https://github.com/lostisland/faraday-multipart/actions?query=branch%3Amain)
4
+ [![Gem](https://img.shields.io/gem/v/faraday-multipart.svg?style=flat-square)](https://rubygems.org/gems/faraday-multipart)
5
+ [![License](https://img.shields.io/github/license/lostisland/faraday-multipart.svg?style=flat-square)](LICENSE.md)
6
+
7
+ The `Multipart` middleware converts a `Faraday::Request#body` Hash of key/value pairs into a multipart form request, but
8
+ only under these conditions:
9
+
10
+ * The request's Content-Type is "multipart/form-data"
11
+ * Content-Type is unspecified, AND one of the values in the Body responds to
12
+ `#content_type`.
13
+
14
+ Faraday contains a couple helper classes for multipart values:
15
+
16
+ * `Faraday::Multipart::FilePart` wraps binary file data with a Content-Type. The file data can be specified with a String path to a
17
+ local file, or an IO object.
18
+ * `Faraday::Multipart::ParamPart` wraps a String value with a Content-Type, and optionally a Content-ID.
19
+
20
+ ## Installation
21
+
22
+ Add this line to your application's Gemfile:
23
+
24
+ ```ruby
25
+ gem 'faraday-multipart'
26
+ ```
27
+
28
+ And then execute:
29
+
30
+ ```shell
31
+ bundle install
32
+ ```
33
+
34
+ Or install it yourself as:
35
+
36
+ ```shell
37
+ gem install faraday-multipart
38
+ ```
39
+
40
+ ## Usage
41
+
42
+ First of all, you'll need to add the multipart middleware to your Faraday connection:
43
+
44
+ ```ruby
45
+ require 'faraday/multipart'
46
+
47
+ conn = Faraday.new(...) do |f|
48
+ f.request :multipart
49
+ # ...
50
+ end
51
+ ```
52
+
53
+ Payload can be a mix of POST data and multipart values.
54
+
55
+ ```ruby
56
+ # regular POST form value
57
+ payload = { string: 'value' }
58
+
59
+ # filename for this value is File.basename(__FILE__)
60
+ payload[:file] = Faraday::Multipart::FilePart.new(__FILE__, 'text/x-ruby')
61
+
62
+ # specify filename because IO object doesn't know it
63
+ payload[:file_with_name] = Faraday::Multipart::FilePart.new(
64
+ File.open(__FILE__),
65
+ 'text/x-ruby',
66
+ File.basename(__FILE__)
67
+ )
68
+
69
+ # Sets a custom Content-Disposition:
70
+ # nil filename still defaults to File.basename(__FILE__)
71
+ payload[:file_with_header] = Faraday::Multipart::FilePart.new(
72
+ __FILE__,
73
+ 'text/x-ruby',
74
+ nil,
75
+ 'Content-Disposition' => 'form-data; foo=1'
76
+ )
77
+
78
+ # Upload raw json with content type
79
+ payload[:raw_data] = Faraday::Multipart::ParamPart.new(
80
+ { a: 1 }.to_json,
81
+ 'application/json'
82
+ )
83
+
84
+ # optionally sets Content-ID too
85
+ payload[:raw_with_id] = Faraday::Multipart::ParamPart.new(
86
+ { a: 1 }.to_json,
87
+ 'application/json',
88
+ 'foo-123'
89
+ )
90
+
91
+ conn.post('/', payload)
92
+ ```
93
+
94
+ ## Development
95
+
96
+ After checking out the repo, run `bin/setup` to install dependencies.
97
+
98
+ Then, run `bin/test` to run the tests.
99
+
100
+ To install this gem onto your local machine, run `rake build`.
101
+
102
+ To release a new version, make a commit with a message such as "Bumped to 0.0.2" and then run `rake release`. See how it
103
+ works [here](https://bundler.io/guides/creating_gem.html#releasing-the-gem).
104
+
105
+ ## Contributing
106
+
107
+ Bug reports and pull requests are welcome on [GitHub](https://github.com/lostisland/faraday-multipart).
108
+
109
+ ## License
110
+
111
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stringio'
4
+
5
+ # multipart-post gem
6
+ require 'composite_io'
7
+ require 'parts'
8
+
9
+ module Faraday
10
+ module Multipart
11
+ # Multipart value used to POST a binary data from a file or
12
+ #
13
+ # @example
14
+ # payload = { file: Faraday::FilePart.new("file_name.ext", "content/type") }
15
+ # http.post("/upload", payload)
16
+ #
17
+
18
+ # @!method initialize(filename_or_io, content_type, filename = nil, opts = {})
19
+ #
20
+ # @param filename_or_io [String, IO] Either a String filename to a local
21
+ # file or an open IO object.
22
+ # @param content_type [String] String content type of the file data.
23
+ # @param filename [String] Optional String filename, usually to add context
24
+ # to a given IO object.
25
+ # @param opts [Hash] Optional Hash of String key/value pairs to describethis
26
+ # this uploaded file. Expected Header keys include:
27
+ # * Content-Transfer-Encoding - Defaults to "binary"
28
+ # * Content-Disposition - Defaults to "form-data"
29
+ # * Content-Type - Defaults to the content_type argument.
30
+ # * Content-ID - Optional.
31
+ #
32
+ # @return [Faraday::FilePart]
33
+ #
34
+ # @!attribute [r] content_type
35
+ # The uploaded binary data's content type.
36
+ #
37
+ # @return [String]
38
+ #
39
+ # @!attribute [r] original_filename
40
+ # The base filename, taken either from the filename_or_io or filename
41
+ # arguments in #initialize.
42
+ #
43
+ # @return [String]
44
+ #
45
+ # @!attribute [r] opts
46
+ # Extra String key/value pairs to make up the header for this uploaded file.
47
+ #
48
+ # @return [Hash]
49
+ #
50
+ # @!attribute [r] io
51
+ # The open IO object for the uploaded file.
52
+ #
53
+ # @return [IO]
54
+ FilePart = ::UploadIO
55
+
56
+ Parts = ::Parts
57
+
58
+ # Similar to, but not compatible with CompositeReadIO provided by the
59
+ # multipart-post gem.
60
+ # https://github.com/nicksieger/multipart-post/blob/master/lib/composite_io.rb
61
+ class CompositeReadIO
62
+ def initialize(*parts)
63
+ @parts = parts.flatten
64
+ @ios = @parts.map(&:to_io)
65
+ @index = 0
66
+ end
67
+
68
+ # @return [Integer] sum of the lengths of all the parts
69
+ def length
70
+ @parts.inject(0) { |sum, part| sum + part.length }
71
+ end
72
+
73
+ # Rewind each of the IOs and reset the index to 0.
74
+ #
75
+ # @return [void]
76
+ def rewind
77
+ @ios.each(&:rewind)
78
+ @index = 0
79
+ end
80
+
81
+ # Read from IOs in order until `length` bytes have been received.
82
+ #
83
+ # @param length [Integer, nil]
84
+ # @param outbuf [String, nil]
85
+ def read(length = nil, outbuf = nil)
86
+ got_result = false
87
+ outbuf = outbuf ? (+outbuf).replace('') : +''
88
+
89
+ while (io = current_io)
90
+ if (result = io.read(length))
91
+ got_result ||= !result.nil?
92
+ result.force_encoding('BINARY') if result.respond_to?(:force_encoding)
93
+ outbuf << result
94
+ length -= result.length if length
95
+ break if length&.zero?
96
+ end
97
+ advance_io
98
+ end
99
+ !got_result && length ? nil : outbuf
100
+ end
101
+
102
+ # Close each of the IOs.
103
+ #
104
+ # @return [void]
105
+ def close
106
+ @ios.each(&:close)
107
+ end
108
+
109
+ def ensure_open_and_readable
110
+ # Rubinius compatibility
111
+ end
112
+
113
+ private
114
+
115
+ def current_io
116
+ @ios[@index]
117
+ end
118
+
119
+ def advance_io
120
+ @index += 1
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Faraday
6
+ module Multipart
7
+ # Middleware for supporting multi-part requests.
8
+ class Middleware < Faraday::Request::UrlEncoded
9
+ DEFAULT_BOUNDARY_PREFIX = '-----------RubyMultipartPost'
10
+
11
+ self.mime_type = 'multipart/form-data'
12
+
13
+ def initialize(app = nil, options = {})
14
+ super(app)
15
+ @options = options
16
+ end
17
+
18
+ # Checks for files in the payload, otherwise leaves everything untouched.
19
+ #
20
+ # @param env [Faraday::Env]
21
+ def call(env)
22
+ match_content_type(env) do |params|
23
+ env.request.boundary ||= unique_boundary
24
+ env.request_headers[CONTENT_TYPE] +=
25
+ "; boundary=#{env.request.boundary}"
26
+ env.body = create_multipart(env, params)
27
+ end
28
+ @app.call env
29
+ end
30
+
31
+ # @param env [Faraday::Env]
32
+ def process_request?(env)
33
+ type = request_type(env)
34
+ env.body.respond_to?(:each_key) && !env.body.empty? && (
35
+ (type.empty? && has_multipart?(env.body)) ||
36
+ (type == self.class.mime_type)
37
+ )
38
+ end
39
+
40
+ # Returns true if obj is an enumerable with values that are multipart.
41
+ #
42
+ # @param obj [Object]
43
+ # @return [Boolean]
44
+ def has_multipart?(obj)
45
+ if obj.respond_to?(:each)
46
+ (obj.respond_to?(:values) ? obj.values : obj).each do |val|
47
+ return true if val.respond_to?(:content_type) || has_multipart?(val)
48
+ end
49
+ end
50
+ false
51
+ end
52
+
53
+ # @param env [Faraday::Env]
54
+ # @param params [Hash]
55
+ def create_multipart(env, params)
56
+ boundary = env.request.boundary
57
+ parts = process_params(params) do |key, value|
58
+ part(boundary, key, value)
59
+ end
60
+ parts << Faraday::Multipart::Parts::EpiloguePart.new(boundary)
61
+
62
+ body = Faraday::Multipart::CompositeReadIO.new(parts)
63
+ env.request_headers[Faraday::Env::ContentLength] = body.length.to_s
64
+ body
65
+ end
66
+
67
+ def part(boundary, key, value)
68
+ if value.respond_to?(:to_part)
69
+ value.to_part(boundary, key)
70
+ else
71
+ Faraday::Multipart::Parts::Part.new(boundary, key, value)
72
+ end
73
+ end
74
+
75
+ # @return [String]
76
+ def unique_boundary
77
+ "#{DEFAULT_BOUNDARY_PREFIX}-#{SecureRandom.hex}"
78
+ end
79
+
80
+ # @param params [Hash]
81
+ # @param prefix [String]
82
+ # @param pieces [Array]
83
+ def process_params(params, prefix = nil, pieces = nil, &block)
84
+ params.inject(pieces || []) do |all, (key, value)|
85
+ if prefix
86
+ key = @options[:flat_encode] ? prefix.to_s : "#{prefix}[#{key}]"
87
+ end
88
+
89
+ case value
90
+ when Array
91
+ values = value.inject([]) { |a, v| a << [nil, v] }
92
+ process_params(values, key, all, &block)
93
+ when Hash
94
+ process_params(value, key, all, &block)
95
+ else
96
+ all << block.call(key, value) # rubocop:disable Performance/RedundantBlockCall
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Faraday
4
+ module Multipart
5
+ # Multipart value used to POST data with a content type.
6
+ class ParamPart
7
+ # @param value [String] Uploaded content as a String.
8
+ # @param content_type [String] String content type of the value.
9
+ # @param content_id [String] Optional String of this value's Content-ID.
10
+ #
11
+ # @return [Faraday::ParamPart]
12
+ def initialize(value, content_type, content_id = nil)
13
+ @value = value
14
+ @content_type = content_type
15
+ @content_id = content_id
16
+ end
17
+
18
+ # Converts this value to a form part.
19
+ #
20
+ # @param boundary [String] String multipart boundary that must not exist in
21
+ # the content exactly.
22
+ # @param key [String] String key name for this value.
23
+ #
24
+ # @return [Faraday::Parts::Part]
25
+ def to_part(boundary, key)
26
+ Faraday::Multipart::Parts::Part.new(boundary, key, value, headers)
27
+ end
28
+
29
+ # Returns a Hash of String key/value pairs.
30
+ #
31
+ # @return [Hash]
32
+ def headers
33
+ {
34
+ 'Content-Type' => content_type,
35
+ 'Content-ID' => content_id
36
+ }
37
+ end
38
+
39
+ # The content to upload.
40
+ #
41
+ # @return [String]
42
+ attr_reader :value
43
+
44
+ # The value's content type.
45
+ #
46
+ # @return [String]
47
+ attr_reader :content_type
48
+
49
+ # The value's content ID, if given.
50
+ #
51
+ # @return [String, nil]
52
+ attr_reader :content_id
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Faraday
4
+ module Multipart
5
+ VERSION = '1.0.0'
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require_relative 'multipart/file_part'
5
+ require_relative 'multipart/param_part'
6
+ require_relative 'multipart/middleware'
7
+ require_relative 'multipart/version'
8
+
9
+ module Faraday
10
+ # Main Faraday::Multipart module.
11
+ module Multipart
12
+ Faraday::Request.register_middleware(multipart: Faraday::Multipart::Middleware)
13
+ end
14
+ end
metadata ADDED
@@ -0,0 +1,208 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: faraday-multipart
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Mattia Giuffrida
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-01-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: multipart-post
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '1.2'
34
+ - - "<"
35
+ - !ruby/object:Gem::Version
36
+ version: '3'
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '1.2'
44
+ - - "<"
45
+ - !ruby/object:Gem::Version
46
+ version: '3'
47
+ - !ruby/object:Gem::Dependency
48
+ name: bundler
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '2.0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '2.0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rake
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '13.0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '13.0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: rspec
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '3.0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '3.0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: simplecov
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: 0.21.0
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: 0.21.0
103
+ - !ruby/object:Gem::Dependency
104
+ name: rubocop
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: 1.21.0
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: 1.21.0
117
+ - !ruby/object:Gem::Dependency
118
+ name: rubocop-packaging
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: 0.5.0
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: 0.5.0
131
+ - !ruby/object:Gem::Dependency
132
+ name: rubocop-performance
133
+ requirement: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: '1.0'
138
+ type: :development
139
+ prerelease: false
140
+ version_requirements: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - "~>"
143
+ - !ruby/object:Gem::Version
144
+ version: '1.0'
145
+ - !ruby/object:Gem::Dependency
146
+ name: rubocop-rspec
147
+ requirement: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - "~>"
150
+ - !ruby/object:Gem::Version
151
+ version: '2.0'
152
+ type: :development
153
+ prerelease: false
154
+ version_requirements: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - "~>"
157
+ - !ruby/object:Gem::Version
158
+ version: '2.0'
159
+ description: 'Perform multipart-post requests using Faraday.
160
+
161
+ '
162
+ email:
163
+ - giuffrida.mattia@gmail.com
164
+ executables: []
165
+ extensions: []
166
+ extra_rdoc_files: []
167
+ files:
168
+ - CHANGELOG.md
169
+ - LICENSE.md
170
+ - README.md
171
+ - lib/faraday/multipart.rb
172
+ - lib/faraday/multipart/file_part.rb
173
+ - lib/faraday/multipart/middleware.rb
174
+ - lib/faraday/multipart/param_part.rb
175
+ - lib/faraday/multipart/version.rb
176
+ homepage: https://github.com/lostisland/faraday-multipart
177
+ licenses:
178
+ - MIT
179
+ metadata:
180
+ bug_tracker_uri: https://github.com/lostisland/faraday-multipart/issues
181
+ changelog_uri: https://github.com/lostisland/faraday-multipart/blob/v1.0.0/CHANGELOG.md
182
+ documentation_uri: http://www.rubydoc.info/gems/faraday-multipart/1.0.0
183
+ homepage_uri: https://github.com/lostisland/faraday-multipart
184
+ source_code_uri: https://github.com/lostisland/faraday-multipart
185
+ wiki_uri: https://github.com/lostisland/faraday-multipart/wiki
186
+ post_install_message:
187
+ rdoc_options: []
188
+ require_paths:
189
+ - lib
190
+ required_ruby_version: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '2.6'
195
+ - - "<"
196
+ - !ruby/object:Gem::Version
197
+ version: '4'
198
+ required_rubygems_version: !ruby/object:Gem::Requirement
199
+ requirements:
200
+ - - ">="
201
+ - !ruby/object:Gem::Version
202
+ version: '0'
203
+ requirements: []
204
+ rubygems_version: 3.1.6
205
+ signing_key:
206
+ specification_version: 4
207
+ summary: Perform multipart-post requests using Faraday.
208
+ test_files: []