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

Expose Faraday's options for API wrapper #4

Open
zverok opened this issue Dec 4, 2017 · 9 comments
Open

Expose Faraday's options for API wrapper #4

zverok opened this issue Dec 4, 2017 · 9 comments

Comments

@zverok
Copy link
Contributor

zverok commented Dec 4, 2017

The idea is: Faraday allows to setup a lot of options (setup logger, timeouts, request headers), change adapter (backend HTTP library), add middleware (for example, add response caching). Some of this can be useful for TLAW users. Proposed syntax:

api = SomeApiWrapper.new(...) do |faraday|
  faraday.timeout = 5
end
@cdesch
Copy link

cdesch commented Jan 19, 2018

If I'm understanding that correctly (sorry, new to the tlaw gem), this would allow us to put request headers in each request?

Like this, where

uri = URI("http://localhost:8080/places")
params = [{'uuid': '0514b...', 'name':'Athens'}]
headers = {
    'Authorization'=>"Bearer: #{@jwt_token}",
    'Content-Type' =>'application/json',
    'Accept'=>'application/json'
}

http = Net::HTTP.new(uri.host, uri.port)
response = http.post(uri.path, params.to_json, headers)

or specify options via @instance_variables like this?

   @options = {
          headers: {
              "Authorization"=> "Bearer #{@access_token}"
          }
      }

Maybe if there was a pre_process method that added the headers to the request each time? E.g.

   ...
   define do 
      base 'https://www.example.com/api'
      
      desc %Q{
        Ruby API Wrapper for Example
       }

       docs 'https://developer.forecast.io/docs/v2'

       param :api_key, required: true,
         desc: 'Register at https://example to obtain it'

       # --> Pre Processorer here to add the authorization token to the request <--
       pre_process('header') {
          pre_process_add "Authorization", "Bearer #{@access_token}"
       } 

       endpoint :audience

       post_process 'currently.time', &Time.method(:at)

   end
   ...

It might look a little like:

  def pre_process(key = nil, &block)
    @object.request_processor.add_pre_processor(key, &block)
  end
  ...
  class RequestProcessor
    ...
       def add_pre_processor(key = nil, &block)
           @pre_processors << (key ? Key.new(key, &block) : Base.new(&block))
       end
    ...
       # And this is where I get lost on how I might implement it myself.
    ...
 end

I'd like to give it a shot, but I'm not exactly sure how I would add the request headers to it from here. It seems fairly straightforward until I get deep into the ResponseProcessor class and how I might use that as a foundation for a RequestProcessor class. If someone could help me with that part I may be able to write the other parts.

@zverok
Copy link
Contributor Author

zverok commented Jan 20, 2018

Hey, thanks for your interest!

In fact, this particular issue is pretty simple:

  1. Imagine we already have defined TLAW wrapper for some service...
  2. When we create an instance of it, we can provide user with access to "raw" internal HTTP library, to set some options, like timeouts, gzipping, particular HTTP backend (faraday supports several), request logging and so on.
  3. So, the implementation should just update API#initialize method (and make the whole wrapper use one client, because currently, each endpoint creates its own @client instance), accept block and path Faraday instance to it.

But, what are you assumed, is also necessary feature of passing some parameter as a header

# ...somewhere inside API description
param :jwt_token, format: ->(token) { "Bearer: #{@jwt_token}" }, header: 'Authorization'

^ the last argument says it should be passed as an auth.header instead of get param.
Then it can be used this way:

client = SomeApiClient.new(jwt_token: "some_token")
client.some_method

If you are interested in the latter (passing headers), I'd be happy to see a PR (and help you with it). Where you start to look is Endpoint#call (to add custom headers) and redesign of Endpoint#construct_url (it currently hardcoded to make all the params into URL)

@cdesch
Copy link

cdesch commented Jan 22, 2018

Thanks @zverok. I might give it a go. It seems like it could work. I'll use the params in the meantime. Thanks!

@marcandre
Copy link
Contributor

Running in the same issue, which is three-fold:

  1. There is no retry option set by default. This is for GET, so I think there should be
  2. No API to access the Faraday object
  3. Even worse, the Faraday object is created on the fly and used only for a single call. I think those objects are meant to be reused. Is the plan to have one such object per Endpoint, or a single one for the root API? (I'm enclined to put it at the root.

@marcandre
Copy link
Contributor

I'd add a 4th item that initializing the connection would ideally be in its own method. Right now its in initialize, which makes monkeypatching quite horrible.

@zverok
Copy link
Contributor Author

zverok commented Nov 17, 2018

Let me be honest: I am in the middle of NaNoWriMo right now. It requires absolutely unprecedented amount of energy and attention.

But I am VERY happy somebody finally found this library useful, and I pinky swear that first thing in December I will be back to all the old issues and unresolved PRs and generally review the code and architecture and make a lot of decisions. Does it work for you?..

@zverok zverok mentioned this issue Nov 17, 2018
@marcandre
Copy link
Contributor

I hope the NaNoWriMo went well.

@cdesch
Copy link

cdesch commented Dec 7, 2018

@zverok Thanks! Will you be sharing the novel?

@zverok
Copy link
Contributor Author

zverok commented Dec 9, 2018

Phew, sorry guys, finally back :) Now I am all yours.

image
(@cdesch I'll try to publish it next year, but it is in Ukrainian anyways :))


@marcandre I believe the most reasonable change would having ONE faraday object per whole API structure, and provide direct access to it via (at least):

api = MyTLAWAPI.new(...) do |faraday|
  faraday.use SomeMiddleware
end

Moreover, it is probably useful to have in API definition DSL some block like

setup_faraday do |faraday|
  faraday.use SomeMiddleware
end

WDYT?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants