# Faraday Multipart

[![ci](https://github.com/lostisland/faraday-multipart/actions/workflows/ci.yml/badge.svg)](https://github.com/lostisland/faraday-multipart/actions/workflows/ci.yml)
[![Gem](https://img.shields.io/gem/v/faraday-multipart.svg?style=flat-square)](https://rubygems.org/gems/faraday-multipart)
[![License](https://img.shields.io/github/license/lostisland/faraday-multipart.svg?style=flat-square)](LICENSE.md)

The `Multipart` middleware converts a `Faraday::Request#body` Hash of key/value pairs into a multipart form request, but
only under these conditions:

* The request's Content-Type is "multipart/form-data"
* Content-Type is unspecified, AND one of the values in the Body responds to
  `#content_type`.

Faraday contains a couple helper classes for multipart values:

* `Faraday::Multipart::FilePart` wraps binary file data with a Content-Type. The file data can be specified with a String path to a
  local file, or an IO object.
* `Faraday::Multipart::ParamPart` wraps a String value with a Content-Type, and optionally a Content-ID.

## Installation

Add this line to your application's Gemfile:

```ruby
gem 'faraday-multipart'
```

And then execute:

```shell
bundle install
```

Or install it yourself as:

```shell
gem install faraday-multipart
```

## Usage

First of all, you'll need to add the multipart middleware to your Faraday connection:

```ruby
require 'faraday'
require 'faraday/multipart'

conn = Faraday.new(...) do |f|
  f.request :multipart, **options
  # ...
end
```

If you need to [specify a different content type for the multipart
request](https://www.iana.org/assignments/media-types/media-types.xhtml#multipart),
you can do so by providing the `content_type` option but it must start with
`multipart/`
otherwise it will default to `multipart/form-data`:

```ruby
conn = Faraday.new(...) do |f|
  f.request :multipart, content_type: 'multipart/mixed'
  # ...
end
```

Payload can be a mix of POST data and multipart values.

```ruby
# regular POST form value
payload = { string: 'value' }

# filename for this value is File.basename(__FILE__)
payload[:file] = Faraday::Multipart::FilePart.new(__FILE__, 'text/x-ruby')

# specify filename because IO object doesn't know it
payload[:file_with_name] = Faraday::Multipart::FilePart.new(
  File.open(__FILE__),
  'text/x-ruby',
  File.basename(__FILE__)
)

# Sets a custom Content-Disposition:
# nil filename still defaults to File.basename(__FILE__)
payload[:file_with_header] = Faraday::Multipart::FilePart.new(
  __FILE__,
  'text/x-ruby',
  nil,
  'Content-Disposition' => 'form-data; foo=1'
)

# Upload raw json with content type
payload[:raw_data] = Faraday::Multipart::ParamPart.new(
  { a: 1 }.to_json,
  'application/json'
)

# optionally sets Content-ID too
payload[:raw_with_id] = Faraday::Multipart::ParamPart.new(
  { a: 1 }.to_json,
  'application/json',
  'foo-123'
)

conn.post('/', payload)
```

### Sending an array of documents

Sometimes, the server you're calling will expect an array of documents or other values for the same key.
The `multipart` middleware will automatically handle this scenario for you:

```ruby
payload = {
  files: [
    Faraday::Multipart::FilePart.new(__FILE__, 'text/x-ruby'),
    Faraday::Multipart::FilePart.new(__FILE__, 'text/x-pdf')
  ],
  url: [
    'http://mydomain.com/callback1',
    'http://mydomain.com/callback2'
  ]
}

conn.post(url, payload)
#=> POST url[]=http://mydomain.com/callback1&url[]=http://mydomain.com/callback2
#=>   and includes both files in the request under the `files[]` name
```

However, by default these will be sent with `files[]` key and the URLs with `url[]`, similarly to arrays in URL parameters.
Some servers (e.g. Mailgun) expect each document to have the same parameter key instead.
You can instruct the `multipart` middleware to do so by providing the `flat_encode` option:

```ruby
require 'faraday'
require 'faraday/multipart'

conn = Faraday.new(...) do |f|
  f.request :multipart, flat_encode: true
  # ...
end

payload = ... # see example above

conn.post(url, payload)
#=> POST url=http://mydomain.com/callback1&url=http://mydomain.com/callback2
#=>   and includes both files in the request under the `files` name
```

This works for both `UploadIO` and normal parameters alike.

## Development

After checking out the repo, run `bin/setup` to install dependencies.

Then, run `bin/test` to run the tests.

To install this gem onto your local machine, run `rake build`.

### Releasing a new version

To release a new version, make a commit with a message such as "Bumped to 0.0.2", and change the _Unreleased_ heading in `CHANGELOG.md` to a heading like "0.0.2 (2022-01-01)", and then use GitHub Releases to author a release. A GitHub Actions workflow then publishes a new gem to [RubyGems.org](https://rubygems.org/gems/faraday-multipart).

## Contributing

Bug reports and pull requests are welcome on [GitHub](https://github.com/lostisland/faraday-multipart).

## License

The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
