wrappi 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d76d08b4ea48473c331491f345bf3349020d2939038abab2df92c6b2ee39a159
4
- data.tar.gz: c17875fe3958e459610c7996f285e493f50b589e053703a5a14aefd54c1061d7
3
+ metadata.gz: 13686ad4035cddda1cbf1ef0c76cd1d67d221900dd2bcf929e41daff2f81b1dd
4
+ data.tar.gz: 4d313c68d7d3c401ce78d0dbd5a8c088f6818767d7e6807a8620f3e0f2acf751
5
5
  SHA512:
6
- metadata.gz: '03181ab50cbfeb1e55b408bf5d8fd11e919b9e6f6528ff0c4670b1296cac684fc67a0a29b376b292772cde316ed9cfe9a0e50475278c2a5da05946728afed817'
7
- data.tar.gz: 289a464e32f98a82c0098e5372e96935377a8ec2c9da5c9f8d3f7c3e55bbc7d33542e51f7fe7c4b565b70fae787a1ee7186b1c95084ef6bb94bef31348f7f1e4
6
+ metadata.gz: 7a1a0fc2bbff8c668f246b15562bff9c33d098b22f2cd4b12a5342425f8242bf6fb5a2aeb2578e1a1c283b05340a83aab942fd5b9fea2b208d3572c24f03da9e
7
+ data.tar.gz: e4f5f5490059cb6fa8469c44583271186445273d46ca9cc77d139f90b38feefade82cc2617b6eb8a1d7ee17b15b3605ad3c7db472e472cdc759c7b1389f4b2de
@@ -0,0 +1,2 @@
1
+ service_name: travis-ci
2
+ repo_token: dTTopKFna7x9zP2JSbTbiWwOtUTywNmdl
data/Gemfile CHANGED
@@ -2,5 +2,15 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in wrappi.gemspec
4
4
  gemspec
5
- gem 'pry'
6
- gem 'cache_mock'
5
+
6
+ group :development, :test do
7
+ gem 'cache_mock'
8
+ gem 'pry'
9
+ # gem 'rubocop', '~> 0.37.2', require: false unless RUBY_VERSION =~ /^1.8/
10
+ gem 'coveralls', require: false
11
+
12
+ platforms :mri, :mingw do
13
+ gem 'pry', require: false
14
+ gem 'pry-coolline', require: false
15
+ end
16
+ end
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  [![Build Status](https://travis-ci.org/arturictus/wrappi.svg?branch=master)](https://travis-ci.org/arturictus/wrappi)
2
2
  [![Maintainability](https://api.codeclimate.com/v1/badges/8751a0b6523a52b5e23e/maintainability)](https://codeclimate.com/github/arturictus/wrappi/maintainability)
3
- [![Test Coverage](https://api.codeclimate.com/v1/badges/8751a0b6523a52b5e23e/test_coverage)](https://codeclimate.com/github/arturictus/wrappi/test_coverage)
3
+ [![Coverage Status](https://coveralls.io/repos/github/arturictus/wrappi/badge.svg?branch=master)](https://coveralls.io/github/arturictus/wrappi?branch=master)
4
4
 
5
5
  # Wrappi
6
6
  Making APIs fun again!
@@ -251,6 +251,7 @@ end
251
251
  | timeout | Hash | { write: 9, connect: 9, read: 9 } | |
252
252
  | use_ssl_context | Boolean | false | |
253
253
  | ssl_context | OpenSSL::SSL::SSLContext | | |
254
+ | basic_auth | Hash (keys: user, pass) | | |
254
255
 
255
256
  #### Endpoint
256
257
 
@@ -261,7 +262,7 @@ end
261
262
  | verb | Symbol | :get | * |
262
263
  | default_params | Hash `or` block -> Hash | {} | |
263
264
  | headers | Hash `or` block -> Hash | proc { client.headers } | |
264
- | basic_auth | Hash (keys: user, pass) `or` block -> Hash | | |
265
+ | basic_auth | Hash (keys: user, pass) `or` block -> Hash | proc { client.basic_auth } | |
265
266
  | follow_redirects | Boolean `or` block -> Boolean | true | |
266
267
  | body_type | Symbol, one of: :json,:form,:body | :json | |
267
268
  | cache | Boolean `or` block -> Boolean | proc { options[:cache] }| |
@@ -527,6 +528,68 @@ It holds the common configuration for all the endpoints (`Wrappi::Endpoint`).
527
528
  end
528
529
  ```
529
530
 
531
+ ## Code Organization
532
+ ### Build a gem
533
+
534
+ Wrappi is designed to be able to build HTTP client gems with it.
535
+
536
+ ```ruby
537
+ module GithubCLI
538
+ class Client < Wrappi::Client
539
+ setup do |config|
540
+ config.domain = 'https://api.github.com'
541
+ config.headers = {
542
+ 'Content-Type' => 'application/json',
543
+ 'Accept' => 'application/vnd.github.v3+json',
544
+ }
545
+ end
546
+
547
+ class << self
548
+ attr_accessor :my_custom_config
549
+ end
550
+ end
551
+
552
+ def self.setup
553
+ yield(Client)
554
+ end
555
+
556
+ class Endpoint < Wrappi::Endpoint
557
+ client Client
558
+ end
559
+
560
+ class User < Endpoint
561
+ verb :get
562
+ path "users/:username"
563
+ end
564
+
565
+ def self.user(params, opts = {})
566
+ User.new(params, opts)
567
+ end
568
+ end
569
+
570
+ user = GithubCLI.user(username: 'arturictus')
571
+ user.success?
572
+ ```
573
+
574
+ ### In your project
575
+
576
+ ## The HTTP clients war
577
+
578
+ In ruby there are many ruby clients an everyone has an opinion of which one is the
579
+ best.
580
+ Every new API client that you install in your project will install a different HTTP client
581
+ adding redundant and unnecessary dependencies in your project.
582
+ That's why __Wrappi is designed to be HTTP client agnostic__.
583
+ Right now is implemented with [HTTP gem](https://github.com/http/http) (my favorite) but all the logic is decoupled from
584
+ the HTTP client.
585
+
586
+ All 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.
587
+
588
+ __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.
589
+
590
+ Right now is not designed the system to change HTTP clients via configuration but if you are interested to implement one let me know
591
+ and we will figure out the way.
592
+
530
593
  ## Development
531
594
 
532
595
  After checking out the repo, run `bin/setup` to install dependencies.
@@ -0,0 +1,163 @@
1
+ # Wrappi an HTTP framework
2
+
3
+ I would like to introduce you the gem I recently created to build HTTP clients.
4
+
5
+ My motivations:
6
+
7
+ - I've been using a lot of API clients in my projects an every time I have to use
8
+ one I have to memorize the behavior. Or some of them where not easy to use or with
9
+ estrange interface.
10
+
11
+ - When building myself API clients I find myself repeating the same process of finding how to
12
+ implement the best practices and flexibility enough for use cases, same abstractions to handle request errors, codes, etc.
13
+ Basically redoing something that I think could be abstracted in a framework.
14
+
15
+ That's why I builded Wrappi.
16
+
17
+ Let me convince you that your life will be much easier if you use Wrappi.
18
+
19
+ Let's do a Github Client together:
20
+
21
+ __The client:__
22
+
23
+ Client is where the shared configurations for you calls are stored. Domain, api keys, headers.
24
+
25
+ Here it is an example:
26
+
27
+ ```ruby
28
+ module GithubCLI
29
+ class Client < Wrappi::Client
30
+ setup do |config|
31
+ config.domain = 'https://api.github.com'
32
+ config.headers = {
33
+ 'Content-Type' => 'application/json',
34
+ 'Accept' => 'application/vnd.github.v3+json',
35
+ }
36
+ end
37
+ end
38
+ end
39
+ ```
40
+ How you see we created a namespace `GithubCLI`.
41
+ I like my clients to be appended with `CLI` or `API` because when you use the gem in you project you will need
42
+ to create a wrapper around it and if the gem is called like the service I makes you have to be creative about the name and
43
+ your application code is less descriptive.
44
+
45
+ see by yourself:
46
+
47
+ ```ruby
48
+ module Github
49
+ def self.update_repos(user)
50
+ g = GithubCLI.users(username: user.github_username)
51
+ if g.success?
52
+ g.body['repos'].each do |r|
53
+ # what ever
54
+ end
55
+ else
56
+ false
57
+ end
58
+ end
59
+ end
60
+
61
+ Github.update_repos(user)
62
+ ```
63
+ In this case the service is integrated to our application. We just call `Github` in
64
+ our application.
65
+
66
+ Next we created a Client holding all the global settings to make a call to any API. That means the domain, headers and params that will be shared
67
+ between all the endpoints in which we use this client.
68
+
69
+ Now let's create our first endpoint:
70
+
71
+ ```ruby
72
+ module GithubCLI
73
+ class client < Wrappi:Client
74
+ # ...
75
+ end
76
+
77
+
78
+ class User < Wrappi::Endpoint
79
+ client Client
80
+ verb :get
81
+ path "users/:username"
82
+ end
83
+ end
84
+ ```
85
+
86
+ Here we just defined a class iheriting from `Wrappi::Endpoint`. In this class we just set few configs:
87
+ client, verb and path. This is all we need to make our first request.
88
+
89
+ ```ruby
90
+ req = GithubCLI::User.new(username: 'arturictus')
91
+ user.error? # => false
92
+ user.status_code # => 200
93
+ user.body # => {"login"=>"arturictus", "id"=>1930175, ...}
94
+ ```
95
+
96
+ Yep!, done.
97
+
98
+ Right now is when you say: "I can do the same in a single line of code" and you are right. But now is when things get interesting.
99
+
100
+ Imagine that you have have an API key. It's not a hard thing to fix right?, maybe we create a class that all our endpoints inherit from?..
101
+
102
+ In wrappi you can just do it with a line of code:
103
+
104
+ ```ruby
105
+ module GithubCLI
106
+ class Client < Wrappi::Client
107
+ setup do |config|
108
+ config.domain = 'https://api.github.com'
109
+ config.headers = {
110
+ 'Content-Type' => 'application/json',
111
+ 'Accept' => 'application/vnd.github.v3+json',
112
+ }
113
+ config.params = { api_token: 'very_secret' } # Look here
114
+ end
115
+ end
116
+ end
117
+ ```
118
+
119
+ Now all the endpoints using this client will send with the request the `api_token` param.
120
+
121
+ ```ruby
122
+ req = GithubCLI::User.new(username: 'arturictus')
123
+ req.conssumated_params # => { api_token: ...}
124
+ ```
125
+
126
+ Ok, not impressive but we are starting to safe lines of code.
127
+
128
+
129
+ ```ruby
130
+ module GithubCLI
131
+ class Client < Wrappi::Client
132
+ setup do |config|
133
+ config.domain = 'https://api.github.com'
134
+ config.headers = {
135
+ 'Content-Type' => 'application/json',
136
+ 'Accept' => 'application/vnd.github.v3+json',
137
+ }
138
+ end
139
+
140
+ class << self
141
+ attr_accessor :my_custom_config
142
+ end
143
+ end
144
+
145
+ def self.setup
146
+ yield(Client)
147
+ end
148
+
149
+ class Endpoint < Wrappi::Endpoint
150
+ client Client
151
+ end
152
+
153
+ class User < Endpoint
154
+ verb :get
155
+ path "users/:username"
156
+ end
157
+
158
+ def self.user(params, opts = {})
159
+ User.new(params, opts)
160
+ end
161
+ end
162
+
163
+ ```
@@ -5,7 +5,7 @@ require "wrappi"
5
5
 
6
6
  # Github example:
7
7
 
8
- module Github
8
+ module GithubCLI
9
9
  class Client < Wrappi::Client
10
10
  setup do |config|
11
11
  config.domain = 'https://api.github.com'
@@ -14,12 +14,28 @@ module Github
14
14
  'Accept' => 'application/vnd.github.v3+json',
15
15
  }
16
16
  end
17
+
18
+ class << self
19
+ attr_accessor :my_custom_config
20
+ end
17
21
  end
18
- class User < Wrappi::Endpoint
22
+
23
+ def self.setup
24
+ yield(Client)
25
+ end
26
+
27
+ class Endpoint < Wrappi::Endpoint
19
28
  client Client
29
+ end
30
+
31
+ class User < Endpoint
20
32
  verb :get
21
33
  path "users/:username"
22
34
  end
35
+
36
+ def self.user(params, opts = {})
37
+ User.new(params, opts)
38
+ end
23
39
  end
24
40
 
25
41
 
@@ -18,6 +18,7 @@ module Wrappi
18
18
  config_accessor(:params) { {} }
19
19
  config_accessor(:cache)
20
20
  config_accessor(:async_handler) { AsyncHandler }
21
+ config_accessor(:basic_auth)
21
22
 
22
23
  def self.setup
23
24
  yield(self)
@@ -15,7 +15,8 @@ module Wrappi
15
15
  body_type: :json,
16
16
  cache: proc { options[:cache] },
17
17
  cache_options: {},
18
- async_callback: proc {}
18
+ async_callback: proc {},
19
+ basic_auth: proc { client.basic_auth }
19
20
  }
20
21
  )
21
22
  attr_reader :input_params, :options
@@ -111,8 +112,29 @@ module Wrappi
111
112
  "?#{d}"
112
113
  end
113
114
 
115
+ # URI behaviour
116
+ # example:
117
+ #
118
+ # URI.join('https://hello.com/foo/bar', '/bin').to_s
119
+ # => "https://hello.com/bin"
120
+ #
121
+ # URI.join('https://hello.com/foo/bar', 'bin').to_s
122
+ # => "https://hello.com/foo/bin"
123
+ #
124
+ #
125
+ # URI.join('https://hello.com/foo/bar/', '/bin').to_s
126
+ # => "https://hello.com/bin"
127
+ #
128
+ # We want this behaviour:
129
+ # URI.join('https://hello.com/foo/bar/', 'bin').to_s
130
+ # => "https://hello.com/foo/bar/bin"
114
131
  def _url
115
- URI.join("#{client.domain}/", path_gen.path) # TODO: remove heading "/" of path
132
+ URI.join(domain, path_gen.for_uri)
133
+ end
134
+
135
+ def domain
136
+ return client.domain if client.domain =~ /\/$/
137
+ "#{client.domain}/"
116
138
  end
117
139
 
118
140
  def params
@@ -15,6 +15,28 @@ module Wrappi
15
15
  end
16
16
  alias_method :path, :compiled_path
17
17
 
18
+ # removes first character if path starts with `/`
19
+ # this is because URI will remove all the paths in between
20
+ # example:
21
+ #
22
+ # URI.join('https://hello.com/foo/bar', '/bin').to_s
23
+ # => "https://hello.com/bin"
24
+ #
25
+ # URI.join('https://hello.com/foo/bar', 'bin').to_s
26
+ # => "https://hello.com/foo/bin"
27
+ #
28
+ #
29
+ # URI.join('https://hello.com/foo/bar/', '/bin').to_s
30
+ # => "https://hello.com/bin"
31
+ #
32
+ # We want this behaviour:
33
+ # URI.join('https://hello.com/foo/bar/', 'bin').to_s
34
+ # => "https://hello.com/foo/bar/bin"
35
+ def for_uri
36
+ return compiled_path unless compiled_path =~ /^\//
37
+ compiled_path.dup.tap { |s| s[0] = '' }
38
+ end
39
+
18
40
  def processed_params
19
41
  return input_params unless interpolable?
20
42
  @processed_params ||= input_params.reject{ |k, v| keys_in_params.include?(k.to_sym) }
@@ -0,0 +1,45 @@
1
+ module Wrappi
2
+ class Endpoint
3
+
4
+ def fixture_name
5
+ "#{self.class}#{fixture_params_key}.json"
6
+ end
7
+
8
+ def fixture_params_key
9
+ return if processed_params.empty?
10
+ d = Digest::MD5.hexdigest processed_params.to_json
11
+ "-#{d}"
12
+ end
13
+
14
+ def fixture_content
15
+ return {} unless success?
16
+ {
17
+ request: {
18
+ method: verb.to_s,
19
+ url: url,
20
+ domain: domain,
21
+ headers: headers,
22
+ params: consummated_params,
23
+ path: path_gen.path
24
+ },
25
+ response: {
26
+ status: status,
27
+ body: body
28
+ }
29
+ }
30
+ end
31
+ end
32
+
33
+ module Testing
34
+ def store_response(path, &block)
35
+ endpoint = block.call
36
+ raise "Not succesful call to #{endpoint.class}" unless endpoint.success?
37
+ file_fullname = File.join(path, endpoint.fixture_name)
38
+ return endpoint if File.exists?(file_fullname)
39
+ File.open(file_fullname, "w") do |f|
40
+ f.write(JSON.pretty_generate(endpoint.fixture_content))
41
+ end
42
+ endpoint
43
+ end
44
+ end
45
+ end
@@ -1,3 +1,3 @@
1
1
  module Wrappi
2
- VERSION = "0.2.2"
2
+ VERSION = "0.2.3"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wrappi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Artur Pañach
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-02-03 00:00:00.000000000 Z
11
+ date: 2019-04-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -135,6 +135,7 @@ executables: []
135
135
  extensions: []
136
136
  extra_rdoc_files: []
137
137
  files:
138
+ - ".coveralls.yml"
138
139
  - ".gitignore"
139
140
  - ".rspec"
140
141
  - ".travis.yml"
@@ -143,6 +144,7 @@ files:
143
144
  - LICENSE.txt
144
145
  - README.md
145
146
  - Rakefile
147
+ - article.md
146
148
  - bin/console
147
149
  - bin/dev_server
148
150
  - bin/setup
@@ -161,6 +163,7 @@ files:
161
163
  - lib/wrappi/request/template.rb
162
164
  - lib/wrappi/request/with_body.rb
163
165
  - lib/wrappi/response.rb
166
+ - lib/wrappi/testing.rb
164
167
  - lib/wrappi/uncalled_request.rb
165
168
  - lib/wrappi/version.rb
166
169
  - wrappi.gemspec