{"id":16215523,"url":"https://github.com/arturictus/wrappi","last_synced_at":"2025-03-19T10:30:23.068Z","repository":{"id":23269480,"uuid":"98587027","full_name":"arturictus/wrappi","owner":"arturictus","description":"Making APIs fun again!","archived":false,"fork":false,"pushed_at":"2023-06-05T16:03:43.000Z","size":178,"stargazers_count":4,"open_issues_count":17,"forks_count":2,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-03-17T06:03:02.641Z","etag":null,"topics":["api-client","async","cache","framework","retries"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/arturictus.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-07-27T23:16:05.000Z","updated_at":"2023-06-05T15:29:56.000Z","dependencies_parsed_at":"2024-10-27T20:30:10.326Z","dependency_job_id":"005d258c-0981-4a46-9be6-ab0e71ab9563","html_url":"https://github.com/arturictus/wrappi","commit_stats":{"total_commits":74,"total_committers":1,"mean_commits":74.0,"dds":0.0,"last_synced_commit":"cc2a5952c5cbd2843030edc2523c139fb793c8aa"},"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arturictus%2Fwrappi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arturictus%2Fwrappi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arturictus%2Fwrappi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arturictus%2Fwrappi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/arturictus","download_url":"https://codeload.github.com/arturictus/wrappi/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244407604,"owners_count":20447819,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api-client","async","cache","framework","retries"],"created_at":"2024-10-10T11:15:14.112Z","updated_at":"2025-03-19T10:30:22.748Z","avatar_url":"https://github.com/arturictus.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://travis-ci.org/arturictus/wrappi.svg?branch=master)](https://travis-ci.org/arturictus/wrappi)\n[![Maintainability](https://api.codeclimate.com/v1/badges/8751a0b6523a52b5e23e/maintainability)](https://codeclimate.com/github/arturictus/wrappi/maintainability)\n[![Coverage Status](https://coveralls.io/repos/github/arturictus/wrappi/badge.svg?branch=master)](https://coveralls.io/github/arturictus/wrappi?branch=master)\n\n# Wrappi\nMaking APIs fun again!\n\nWrappi is a Framework to create API clients. The intention is to bring the best practices and standardize how API clients behave.\nIt allows to create API clients in a declarative way improving readability and unifying the behavior. It abstracts complex operations like caching, retries, background requests and error handling.\n\nEnjoy!\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'wrappi'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install wrappi\n\n## Usage\n\n__Github example:__\n\nYou can test this examples running `bin/console`\n\n```ruby\nmodule GithubCLI\n  class Client \u003c Wrappi::Client\n    setup do |config|\n      config.domain = 'https://api.github.com'\n      config.headers = {\n        'Content-Type' =\u003e 'application/json',\n        'Accept' =\u003e 'application/vnd.github.v3+json',\n      }\n    end\n  end\n\n  class User \u003c Wrappi::Endpoint\n    client Client\n    verb :get\n    path \"users/:username\"\n  end\nend\n```\n\n```ruby\nuser = GithubCLI::User.new(username: 'arturictus')\nuser.success? # =\u003e true\nuser.error? # =\u003e false\nuser.status_code # =\u003e 200\nuser.body # =\u003e {\"login\"=\u003e\"arturictus\", \"id\"=\u003e1930175, ...}\n```\n\n#### #success?\n\nThe next behaviours are using `#success?` method. You can override by redefining your own success?\n\nThe current `#success?` is defined like this:\n\n_wrappi/endpoint.rb_\n```ruby\n  def self.success?(request)\n    request.code \u003c 300 \u0026\u0026 request.code \u003e= 200\n  end\n```\n\nOverrride your own in Endpoint\n```ruby\n  class User \u003c Wrappi::Endpoint\n    client Client\n    verb :get\n    path \"users/:username\"\n\n    def self.success?(request)\n      request.status == 200\n    end\n  end\n```\n\n##### #on_success | #on_error\n\n```ruby\nGithubCLI::User.new(username: 'arturictus')\n               .on_success do |inst|\n                 inst.status_code # =\u003e 200\n                 inst.body # =\u003e {\"login\"=\u003e\"arturictus\", \"id\"=\u003e1930175, ...}\n                 # do something useful\n               end.on_error do |inst|\n                 puts \"Error retrieving use\"\n               end\n```\n\n##### ::body\nIf you just need to retrieve the body and handle the error response on your side\n\n```ruby\nGithubCLI::User.body(username: 'arturictus') # =\u003e {\"login\"=\u003e\"arturictus\", \"id\"=\u003e1930175, ...}\n```\n```ruby\nGithubCLI::User.body(username: 'sdfsdfasdjfojaspdjfpsajdpfoijsapdofijsadf')\n# =\u003e {\"message\"=\u003e\"Not Found\", \"documentation_url\"=\u003e\"https://developer.github.com/v3/users/#get-a-single-user\"}\n```\n\n##### ::call\n\nreturns `false` if unsuccessful and instance if successful\n\n```ruby\nif req = GithubCLI::User.call(username: 'arturictus')\n  req.body # =\u003e {\"login\"=\u003e\"arturictus\", \"id\"=\u003e1930175, ...}\nelse\n  # Handle error\nend\n```\n##### ::call!\n\nRaises error if unsuccessful, returns instance if successful\n\n```ruby\nbegin\n  req = GithubCLI::User.call!(username: 'arturictus')\n  req.body # =\u003e {\"login\"=\u003e\"arturictus\", \"id\"=\u003e1930175, ...}\nrescue =\u003e Wrappi::UnsuccessfulResponse\n  # Handle error or not\nend\n```\n\nThe error:\n\n```ruby\nGithubCLI::User.call!(username: 'sdfsdfasdjfojaspdjfpsajdpfoijsapdofijsadf')\n# Wrappi::UnsuccessfulResponse ()\n#     raw_body: {\"message\":\"Not Found\",\"documentation_url\":\"https://developer.github.com/v3/users/#get-a-single-user\"}\n#     code: 404\n#     uri: https://api.github.com/users/sdfsdfasdjfojaspdjfpsajdpfoijsapdofijsadf\n#     success: false\n```\n\n#### Async\nWrappi comes with a background Job out of the box. If you are in a Rails app the `#async`\nmethod will queue a new job (`\u003c ActiveJob::Base`) that will make the request and trigger the async callback\nafter the request is made.\n\nexample:\n\n```ruby\nclass User \u003c Wrappi::Endpoint\n  client Client\n  verb :get\n  path \"users/:username\"\n  async_callback do |opts|\n    # this will be called in background after the request is made\n    if success?\n      if opts[:create]\n        CreateUserService.call(body)\n      elsif opts[:update]\n        UpdateUserService.call(body)\n      end\n    end\n  end\nend\n# This will execute the request in a background job\nGithub::User.new(username: 'arturictus').async(create: true)\n```\n\nIf you need to send options to your Job (the `::set` method) you can pass the key `set`\nto the options.\n\n```ruby\nGithub::User.new(username: 'arturictus').async(create: true, set: { wait: 10.minutes })\n```\n\n#### Cache\nYou can enable cache per endpoint. It depends on `::success?` method to determine if it will be cached or nor.\n\nSet the cache Handler in your client.\nIt must behave like `Rails.cache` and respond to:\n  - `read([key])`\n  - `write([key, value, options])`\n\n```ruby\nclass Client \u003c Wrappi::Client\n  setup do |config|\n    config.domain = 'https://api.github.com'\n    config.cache = Rails.cache\n  end\nend\n```\n\nEnable cache in your endpoint.\n```ruby\nclass User \u003c Wrappi::Endpoint\n  cache true # enable for endpoint\n  client Client\n  verb :get\n  path \"users/:username\"\nend\n\nuser = User.new(username: 'arturictus')\nuser.response.class # =\u003e Wrappi::Response\nuser.flush\nuser.response.class # =\u003e Wrappi::CachedResponse\nuser.success? # =\u003e true\nuser.body # =\u003e {\"login\"=\u003e\"arturictus\", \"id\"=\u003e1930175, ...}\n```\n\nWhen cached the response will be a `Wrappi::CachedResponse`. `Wrappi::CachedResponse` behaves\nlike `Wrappi::Response` that means you can use the endpoint in the same way as it was a non cached.\nSee `cache_options` to fine tune your cache with expiration and other cache options.\n\nYou can use options to cache a single request.\n\n```ruby\nclass User \u003c Wrappi::Endpoint\n  client Client\n  verb :get\n  path \"users/:username\"\nend\nUser.new({username: 'arturictus'}, cache: true)\nuser.response.class # =\u003e Wrappi::Response\nuser.flush\nuser.response.class # =\u003e Wrappi::CachedResponse\nuser.success? # =\u003e true\nuser.body # =\u003e {\"login\"=\u003e\"arturictus\", \"id\"=\u003e1930175, ...}\n```\n\n#### Retry\nSometimes you want to retry if certain conditions affected your request.\n\nThis will retry if status code is not `200`\n\n```ruby\n  class User \u003c Wrappi::Endpoint\n    client Client\n    verb :get\n    path \"users/:username\"\n    retry_if do |response|\n      response.code != 200\n    end\n  end\n```\n\nCheck more configuration options and examples for `retry_if` and `retry_options` below.\n\n#### Flexibility\n\n__options:__\n\nPass a second argument with options.\n```ruby\nparams = { username: 'arturictus' }\noptions = { options_in_my_instance: \"yeah!\" }\n\nUser.new(params, options)\n```\n\n__Dynamic configurations:__\n\nAll the configs in `Endpoint` are evaluated at instance level except: `around_request` and `retry_if` because of their nature.\nThat allows you to fine tune the configuration at a instance level.\n\nexample:\n\nRight now the default for `cache` config is: `proc { options[:cache] }`.\n\n```ruby\n  class User \u003c Wrappi::Endpoint\n    client Client\n    verb :get\n    path \"users/:username\"\n    cache do\n      if input_params[:username] == 'arturictus'\n        false\n      else\n        options[:cache]          \n      end\n    end\n  end\n```\n\n\n\n__endpoint is a ruby class:__ :open_mouth:\n\n```ruby\n  class User \u003c Wrappi::Endpoint\n    client Client\n    verb :get\n    path \"users/:username\"\n    cache do\n      cache?\n    end\n\n    def cache?\n      if input_params[:username] == 'arturictus'\n        false\n      else\n        options[:cache]          \n      end\n    end\n\n    def parsed_response\n      @parsed_response ||= MyParser.new(body)\n    end\n  end\n```\n\n__inheritance:__\nAll the configs will be inherited\n\n```ruby\nclass UserDetail \u003c User\n  path \"users/:username/detail\"\nend\n```\n\n### Configurations\n\n#### Client\n\n| Name            | Type                     | Default                                                                  | Required |\n|-----------------|--------------------------|--------------------------------------------------------------------------|----------|\n| domain          | String                   |                                                                          | *        |\n| params          | Hash                     |                                                                          |          |\n| headers         | Hash                     | { 'Content-Type' =\u003e 'application/json', 'Accept' =\u003e 'application/json' } |          |\n| async_handler   | const                    | Wrappi::AsyncHandler                                                     |          |\n| cache           | const                    |                                                                          |          |\n| logger          | Logger                   | Logger.new(STDOUT)                                                       |          |\n| timeout         | Hash                     | { write: 9, connect: 9, read: 9 }                                        |          |\n| use_ssl_context | Boolean                  | false                                                                    |          |\n| ssl_context     | OpenSSL::SSL::SSLContext |                                                                          |          |\n| basic_auth      | Hash (keys: user, pass)  |                                                                          |          |\n\n#### Endpoint\n\n| Name             | Type                                       | Default                 | Required |\n|------------------|--------------------------------------------|-------------------------|----------|\n| client           | Wrappi::Client                             |                         | *        |\n| path             | String                                     |                         | *        |\n| verb             | Symbol                                     | :get                    | *        |\n| default_params   | Hash `or` block -\u003e Hash                    | {}                      |          |\n| headers          | Hash `or` block -\u003e Hash                    | proc { client.headers } |          |\n| basic_auth       | Hash (keys: user, pass) `or` block -\u003e Hash | proc { client.basic_auth } |          |\n| follow_redirects | Boolean `or` block -\u003e Boolean              | true                    |          |\n| body_type        | Symbol, one of: :json,:form,:body          | :json                   |          |\n| cache            | Boolean `or` block -\u003e Boolean              | proc { options[:cache] }|          |\n| cache_options    | block -\u003e Hash                              |                         |          |\n| retry_if         | block                                      |                         |          |\n| retry_options    | Hash `or` block -\u003e Hash                    |                         |          |\n| around_request   | block                                      |                         |          |\n| async_callback   | block                                      |                         |          |\n\n### Client\n\nIs the main configuration for your service.\n\nIt holds the common configuration for all the endpoints (`Wrappi::Endpoint`).\n\n#### Required:\n\n  - __domain:__ Yep, you know.\n    ```ruby\n    config.domain = 'https://api.github.com'\n    ```\n\n#### Optionals:\n\n  - __params:__ Set global params for all the `Endpoints`.\n    This is a great place to put the `api_key`.\n    ```ruby\n    config.params = { \"api_key\" =\u003e \"asdfasdfoerkwlejrwer\" }\n    ```\n    default: `{}`\n\n  - __logger:__ Set your logger.\n\n    default: `Logger.new(STDOUT)`\n    ```ruby\n    config.logger = Rails.logger\n    ```\n\n  - __headers:__ Headers for all the endpoints. Format, Authentication.\n\n    default:\n    ```ruby\n    { 'Content-Type' =\u003e 'application/json', 'Accept' =\u003e 'application/json' }\n    ```\n    ```ruby\n    config.headers = {\n      \"Content-Type\" =\u003e \"application/json\",\n      \"Accept' =\u003e 'application/json\",\n      \"Auth-Token\" =\u003e \"verysecret\"\n    }\n    ```\n  - __async_handler:__ If you are not in Rails app or you have another background mechanism in place\n    you can configure here how the requests will be send to the background.\n    When `#async` is called on an Endpoint instance the `async_handler` const will be called with:\n    current endpoint instance (`self`) and the options passed to the async method.\n    ```ruby\n    class MyAsyncHandler\n      def self.call(endpoint, opts)\n        # send to background\n      end\n    end\n    class Client \u003c Wrappi::Client\n      setup do |config|\n        config.domain = 'https://api.github.com'\n        config.async_handler = MyAsyncHandler\n      end\n    end\n    endpoint_inst.async(this_opts_are_for_the_handler: true)\n    ```\n\n  - __timeout:__ Set your specific timout. When you set timeout it will be merged with defaults.\n\n    default: `{ write: 9, connect: 9, read: 9 }`\n\n    ```ruby\n      class Client \u003c Wrappi::Client\n        setup do |config|\n          config.domain = 'https://api.github.com'\n          config.timeout = { read: 3 }\n        end\n      end\n      Client.timeout # =\u003e { write: 9, connect: 9, read: 3 }\n    ```\n\n  - __use_ssl_context:__ It has to be set to `true` for using the `ssl_context`\n\n     default: `false`\n\n  - __ssl_context:__ If you need to set an ssl_context.\n\n     default: `nil`\n     ```ruby\n     config.ssl_context = OpenSSL::SSL::SSLContext.new.tap do |ctx|\n                            ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE\n                          end\n     ```\n\n### Endpoint\n\n#### Required:\n  - __client:__ `Wrappi::Client` `class`\n    ```ruby\n      client MyClient\n    ```\n\n  - __path:__ The path to the resource.\n    You can use doted notation and they will be interpolated with the params\n\n    ```ruby\n      class MyEndpoint \u003c Wrappi::Endpoint\n        client MyClient\n        verb :get\n        path \"/users/:id\"\n      end\n      endpoint = MyEndpoint.new(id: \"the_id\", other: \"foo\")\n      endpoint.url_with_params #=\u003e \"http://domain.com/users/the_id?other=foo\"\n      endpoint.url #=\u003e \"http://domain.com/users/the_id\"\n      endpoint.consummated_params #=\u003e {\"other\"=\u003e\"foo\"}\n    ```\n    Notice how interpolated params are removed from the query or the body\n\n  - __verb:__\n\n    default: `:get`\n    - `:get`\n    - `:post`\n    - `:delete`\n    - `:put`\n\n\n#### Optional:\n\n  - __default_params:__ Default params for the request. This params will be added\n    to all the instances unless you override them.\n\n    default: `{}`\n\n    ```ruby\n    class MyEndpoint \u003c Wrappi::Endpoint\n      client MyClient\n      verb :get\n      path \"/users/:id\"\n      default_params do\n        { other: \"bar\", foo: \"foo\" }\n      end\n    end\n    endpoint = MyEndpoint.new(id: \"the_id\", other: \"foo\")\n    endpoint.consummated_params #=\u003e {\"other\"=\u003e\"foo\",\"foo\" =\u003e \"foo\" }\n    ```\n\n  - __headers:__ You can modify the client headers here. Notice that if you want\n    to use the client headers as well you will have to merge them.\n\n    default: `proc { client.headers }`\n    ```ruby\n    class MyEndpoint \u003c Wrappi::Endpoint\n      client MyClient\n      verb :get\n      path \"/users\"\n      headers do\n        client.headers #=\u003e { 'Content-Type' =\u003e 'application/json', 'Accept' =\u003e 'application/json' }\n        client.headers.merge('Agent' =\u003e 'wrappi')\n      end\n    end\n    endpoint = MyEndpoint.new()\n    endpoint.headers #=\u003e { 'Agent' =\u003e 'wrappi', 'Content-Type' =\u003e 'application/json', 'Accept' =\u003e 'application/json'}\n    ```\n\n  - __basic_auth:__ If your endpoint requires basic_auth here is the place. keys\n    have to be: `user` and `pass`.\n\n    default: `nil`\n    ```ruby\n      basic_auth do\n        { user: 'wrappi', pass: 'secret'}\n      end\n    ```\n\n  - __follow_redirects:__ If the request responds with a redirection it will follow them.\n\n    default: `true`\n\n  - __body_type:__ Body type.\n\n    default: `:json`\n\n    - :json\n    - :form\n    - :body (Binary data)\n\n  - __async_callback:__ When request is executed in the background with `#async(opts = {})` this\n    callback will be called with this opts as and argument in the block.\n    The block is executed in the endpoint instance. You can access to all the methods in Endpoint.\n\n    default: `proc {}`\n\n    ```ruby\n    async_callback do |opts|\n      if success?\n        MyCreationService.call(body) if opts[:create]\n      end\n    end\n    MyEndpoint.new().async(create: true)\n    ```\n\n#### Flow Control:\n\n  This configs allows you fine tune your request adding middleware, retries and cache.\n  The are executed in this nested stack:\n  ```\n    cache\n      |- retry\n        |- around_request\n  ```\n  Check [specs](/blob/master/spec/wrappi/executer_spec.rb) for more examples.\n\n  - __cache:__ Cache the request if successful.\n\n    default: `proc { options[:cache] }`\n  - __cache_options:__ Options for the `cache` to receive on `write`\n   ```ruby\n     cache_options do\n       { expires_in: 12, another_opt: true }\n     end\n   ```\n\n   default: `{}`\n  - __retry_if:__ Block to evaluate if request has to be retried. In the block are\n    yielded `Response` instance. If the block returns `true` the request will be retried.\n    ```ruby\n      retry_if do |response|\n        response.status != 200 # =\u003e true or false\n      end\n    ```\n\n    Use case:\n\n    We have a service that returns an aggregation of hotels available to book for a city. The service will start the aggregation in the background and will return `200` if the aggregation is completed if the aggregation is not completed will return `201` making us know that we should call again to retrieve all the data. This behavior only occurs if we pass the param: `onlyIfComplete`.\n\n    ```ruby\n      retry_if do |response, endpoint|\n        endpoint.consummated_params[\"onlyIfComplete\"] \u0026\u0026\n          response.status_code == 201\n      end\n    ```\n    Notice that this block will never be executed if an error occur (like timeouts). For retrying on errors use the `retry_options`\n\n  - __retry_options:__ We are using the great gem [retryable](https://github.com/nfedyashev/retryable) to accomplish this behavior.\n  Check the documentation for fine tuning. I just paste some examples for convenience.\n\n  ```ruby\n    retry_options do\n      { tries: 5, on: [ArgumentError, Wrappi::TimeoutError] } # or\n      { tries: :infinite, sleep: 0 }\n    end\n  ```\n  - __around_request:__ This block is executed surrounding the request. The request\n  will only get executed if you call `request.call`.\n  ```ruby\n    around_request do |request, endpoint|\n      endpoint.logger.info(\"making a request to #{endpoint.url} with params: #{endpoint.consummated_params}\")\n      request.call # IMPORTANT\n      endpoint.logger.info(\"response status is: #{request.status_code}\")\n    end\n  ```\n\n## Code Organization\n### Build a gem\n\nWrappi is designed to be able to build HTTP client gems with it.\n\n```ruby\nmodule GithubCLI\n  class Client \u003c Wrappi::Client\n    setup do |config|\n      config.domain = 'https://api.github.com'\n      config.headers = {\n        'Content-Type' =\u003e 'application/json',\n        'Accept' =\u003e 'application/vnd.github.v3+json',\n      }\n    end\n\n    class \u003c\u003c self\n      attr_accessor :my_custom_config\n    end\n  end\n\n  def self.setup\n    yield(Client)\n  end\n\n  class Endpoint \u003c Wrappi::Endpoint\n    client Client\n  end\n\n  class User \u003c Endpoint\n    verb :get\n    path \"users/:username\"\n  end\n\n  def self.user(params, opts = {})\n    User.new(params, opts)\n  end\nend\n\nuser = GithubCLI.user(username: 'arturictus')\nuser.success?\n```\n\n#### Customization in you parent project\n\nOnce you created a gem Wrappi allows to parent projects to customize endpoints without having to change the gem's code.\n\nexample customizing `GithubCLI::User`\n\n```ruby\nGithubCLI::User.setup do\n  cache true\n  async_callback do |opts|\n    if success?\n      # do something\n    end\n  end\nend\n```\n\nExample customizing all the Endpoints, adding loging to all the requests and changing client depending of enviroment:\n\n```ruby\nGithubCLI::Endpoint.setup do\n  client do\n    if ENV['production']\n      GithubCLI::Client\n    else\n      GithubCLI::MyStagingClient\n    end\n  end\n\n  around_request do |request, endpoint|\n    endpoint.logger.info(\"making a request to #{endpoint.url} with params: #{endpoint.consummated_params}\")\n    request.call # IMPORTANT\n    endpoint.logger.info(\"response status is: #{request.status_code}\")\n  end\nend\n```\n\n## The HTTP clients war\n\nIn ruby there are many ruby clients an everyone has an opinion of which one is the\nbest.\nEvery new API client that you install in your project will install a different HTTP client\nadding redundant and unnecessary dependencies in your project.\nThat's why __Wrappi is designed to be HTTP client agnostic__.\nRight now is implemented with [HTTP gem](https://github.com/http/http) (my favorite) but all the logic is decoupled from\nthe HTTP client.\n\nAll the configuration, metadata and logic to build the request is hold by an instance of Endpoint. Allowing to create adapters that translates this processed metadata to the target HTTP client.\n\n__Tests are HTTP client agnostic__. To help the development of these adapters and probe the reliability of the gem most of the test are run against a Rails application. __All the tests that probe an HTTP call are running this HTTP call against a local server__ making all test End To End and again, HTTP client agnostic.\n\nRight now is not designed the system to change HTTP clients via configuration but if you are interested to implement one let me know\nand we will figure out the way.\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies.\n\n```\nbin/dev_server\n```\nThis will run a rails server. The test are running against it.\n\n```\nbundle exec rspec\n```\n\nYou can also run `bin/console` for an interactive prompt that will allow you to experiment.\n\n#### Docker\n\nRun dummy server with docker:\n```\ndocker build -t wrappi/dummy -f spec/dummy/Dockerfile .\ndocker run -d -p 127.0.0.1:9873:9873 wrappy/dummy /bin/sh -c \"bin/rails server -b 0.0.0.0 -p 9873\"\n```\nTry:\n```\ncurl 127.0.0.1:9873 #=\u003e {\"controller\":\"pages\",\"action\":\"show_body\"}\n```\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/arturictus/wrappi. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.\n\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farturictus%2Fwrappi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farturictus%2Fwrappi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farturictus%2Fwrappi/lists"}