Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: Adapters, how to create them #1193

Merged
merged 2 commits into from
Oct 22, 2020
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
130 changes: 130 additions & 0 deletions docs/adapters/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,136 @@ However, sometimes you need to access a feature specific to one of the adapters
When that happens, you can pass a block when specifying the adapter to customize it.
The block parameter will change based on the adapter you're using. See each adapter page for more details.

## Write your own adapter

Adapters have methods that can help you implement support for a new backend.

This example will use a fictional HTTP backend gem called `FlorpHttp`. It doesn't
exist. Its only function is to make this example more concrete.

### An Adapter _is_ a Middleware

There are only two things which are actually mandatory for an adapter middleware to function:

- a `#call` implementation
- a call to `#save_response` inside `#call`, which will keep the Response around.

These are the only two things.

The rest of this text is about methods which make the authoring easier.

### Helpful class: `::Faraday::Adapter`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subclassing Faraday::Adapter is also necessary to access save_response and to be recognised as an adapter by the rack builder, so I'd make that a requirement.


By subclassing `::Faraday::Adapter`, you get helpful methods defined:

```ruby
class FlorpHttp < ::Faraday::Adapter
end
```

### Helpful method: `#build_connection`

Faraday abstracts all your backend's concrete stuff behind its user-facing API.
You take care of setting up the connection from the supplied parameters.

Example from the excon adapter: it gets an `Env` and reads its information
to instantiate an `Excon` object:

```ruby
class FlorpHttp < ::Faraday::Adapter
def build_connection(env)
opts = opts_from_env(env)
::Excon.new(env[:url].to_s, opts.merge(@connection_options))
end
end
```

The `env` contains stuff like:

- `env[:ssl]`
- `env[:request]`

There are helper to fetch timeouts: `#request_timeout(type, options)` knows
about supported timeout types, and falls back to `:timeout` if they are not set.
You can use those when building the options you need for your backend's instantiation.

So, use the information provided in `env` to instantiate your backend's connection class.
Return that instance. Now, Faraday knows how to create and reuse that connection.

### Nickname for your adapter: `.register_middleware`

You may register a nickname for your adapter. People can then refer to your adapter with that name.
You do that using `.register_middleware`, like this:

```ruby
class FlorpHttp < ::Faraday::Adapter
register_middleware(
File.expand_path('adapter', __dir__),
florp_http: [ :FlorpHttp, 'florp_http' ]
)
# ...
end
```

## Does your backend support parallel operation?

:warning: This is slightly more involved, and this section is not fully formed.

Vague example, excerpted from [the test suite about parallel requests](https://github.com/lostisland/faraday/blob/master/spec/support/shared_examples/request_method.rb#L179)

```ruby
response_1 = nil
response_2 = nil

conn.in_parallel do
response_1 = conn.get('/about')
response_2 = conn.get('/products')
end

puts response_1.status
puts response_2.status
```

First, in your class definition, you can tell Faraday that your backend supports parallel operation:

```ruby
class FlorpHttp < ::Faraday::Adapter
dependency do
require 'florp_http'
end

self.supports_parallel = true
end
```

Then, implement a method which returns a ParallelManager:

```ruby
class FlorpHttp < ::Faraday::Adapter
dependency do
require 'florp_http'
end

self.supports_parallel = true

def self.setup_parallel_manager(_options = nil)
FlorpParallelManager.new # NB: we will need to define this
end
end

class FlorpParallelManager
def add(request, method, *args, &block)
# Collect the requests
end

def run
# Process the requests
end
end
```

Compare to the finished example [em-synchrony](https://github.com/lostisland/faraday/blob/master/lib/faraday/adapter/em_synchrony.rb) and its [ParallelManager implementation](https://github.com/lostisland/faraday/blob/master/lib/faraday/adapter/em_synchrony/parallel_manager.rb).

[net_http]: ./net-http
[persistent]: ./net-http-persistent
[excon]: ./excon
Expand Down