faraday-multipart 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []