garage_client 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.rspec +1 -0
  4. data/CHANGELOG.md +40 -0
  5. data/Gemfile +8 -0
  6. data/Guardfile +7 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +196 -0
  9. data/Rakefile +8 -0
  10. data/garage_client.gemspec +36 -0
  11. data/gemfiles/Gemfile.faraday-0.8.x +4 -0
  12. data/lib/garage_client.rb +33 -0
  13. data/lib/garage_client/cachers/base.rb +44 -0
  14. data/lib/garage_client/client.rb +93 -0
  15. data/lib/garage_client/configuration.rb +51 -0
  16. data/lib/garage_client/error.rb +37 -0
  17. data/lib/garage_client/request.rb +38 -0
  18. data/lib/garage_client/request/json_encoded.rb +59 -0
  19. data/lib/garage_client/resource.rb +63 -0
  20. data/lib/garage_client/response.rb +123 -0
  21. data/lib/garage_client/response/cacheable.rb +27 -0
  22. data/lib/garage_client/response/raise_http_exception.rb +34 -0
  23. data/lib/garage_client/version.rb +3 -0
  24. data/spec/features/configuration_spec.rb +46 -0
  25. data/spec/fixtures/example.yaml +56 -0
  26. data/spec/fixtures/examples.yaml +60 -0
  27. data/spec/fixtures/examples_dictionary.yaml +60 -0
  28. data/spec/fixtures/examples_without_pagination.yaml +58 -0
  29. data/spec/garage_client/cacher_spec.rb +55 -0
  30. data/spec/garage_client/client_spec.rb +228 -0
  31. data/spec/garage_client/configuration_spec.rb +106 -0
  32. data/spec/garage_client/error_spec.rb +37 -0
  33. data/spec/garage_client/request/json_encoded_spec.rb +66 -0
  34. data/spec/garage_client/resource_spec.rb +102 -0
  35. data/spec/garage_client/response_spec.rb +450 -0
  36. data/spec/garage_client_spec.rb +48 -0
  37. data/spec/spec_helper.rb +56 -0
  38. metadata +275 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 648321b65499a513c8305e6aac962afb10bf99f6
4
+ data.tar.gz: 17c99e87555fadc63de66ac384d09a354feeb00b
5
+ SHA512:
6
+ metadata.gz: dc3e8ea2288e52f6ec2e9d9ef1b9f92318478ea543852cf77706ca53665f20d5d63e6445d88496ad0a415733772241addb8a67ffc4529e54415883ccab20e6d4
7
+ data.tar.gz: 57c200a989fc68dcd096ced3dc853ca92eb4d03246e2d26afec236c60fd3d06332d284ed642723b8f3e396b3f6fd643f15c0e0c105ba275579009ea347d006a6
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ coverage/
6
+ coverage.vim
7
+ .ruby-version
8
+ gemfiles/*.lock
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/CHANGELOG.md ADDED
@@ -0,0 +1,40 @@
1
+ ## 2.1.0
2
+ - Access token is now embedded in authorization header as a bearer token, instead of embedding in query parameter, to avoid logging and exposing access token value.
3
+ Before: access token is embedded in both query parameter (`?access_token=XXX`) and in authorization header (`Authorization: Token token="XXX"`).
4
+ After: access tok is embedded in authorization header as a bearer token (`Authorization: Bearer XXX`).
5
+
6
+ ## 2.0.0
7
+ - Remove `user_agent` configuration, use `headers` option instead.
8
+
9
+ ## 1.3.0
10
+ - Provide a convenient way to cache HTTP response
11
+
12
+ ## 1.2.4
13
+ - Raise GarageClient::InvalidResponseType when receives invalid response (e.g. String)
14
+
15
+ ## 1.2.3
16
+ - Fixed response.respond_to?(:name)
17
+
18
+ ## 1.2.2
19
+ - GarageClient::Response supports Link header parsing
20
+
21
+ ## 1.2.1
22
+ - Set Content-Type with multipart/form-data when multipart params are detected
23
+
24
+ ## 1.2.0
25
+ - `:headers` option will overwrite the entire headers
26
+ - `:default_headers` will be deprecated. Please use `:headers`
27
+ - `Garage.version` was deprecated. Please use `Garage::VERSION`
28
+ - `Garage.configuration` was added to configure settings
29
+
30
+ ## 1.1.2
31
+ - Remove needless empty module clause (7f5e13)
32
+ - Change gemspec dependency (`hashie ~> 1.2.0` to `hashie >= 1.2.0`) (632ea1)
33
+
34
+ ## 1.1.1
35
+ - GarageClient::Error accepts no argument initialization
36
+
37
+ ## 1.1.0
38
+ - Add ``:default_headers`` option
39
+ - Verbose exception message
40
+ - Resource#links does not raise error when _links is not existed
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem "simplecov-vim", :platform => :mri_19
7
+ gem "simplecov-rcov", :platform => :mri_19
8
+ end
data/Guardfile ADDED
@@ -0,0 +1,7 @@
1
+
2
+ guard 'rspec', :version => 2 do
3
+ watch(%r{^spec/.+_spec\.rb})
4
+ watch(%r{^lib/(.+)\.rb}) { |m| "spec/lib/#{m[1]}_spec.rb" }
5
+ watch('spec/spec_helper.rb') { "spec" }
6
+ end
7
+
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Cookpad Inc.
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,196 @@
1
+ # GarageClient
2
+ GarageClient is a simple Ruby library to provide a primitive client interface to the Garage application API.
3
+
4
+ ## Install
5
+ Install from rake command:
6
+
7
+ ```
8
+ $ bundle install
9
+ $ rake install
10
+ ```
11
+
12
+ or modify Gemfile in your application and invoke `bundle install`.
13
+
14
+ ```ruby
15
+ # Gemfile
16
+ gem "garage_client"
17
+ ```
18
+
19
+ ## Usage
20
+ Here are quick examples.
21
+
22
+ ### Client initialization
23
+ ```ruby
24
+ require "garage_client"
25
+
26
+ # First, you have to create a GarageClient::Client with an access token.
27
+ # You can get `YOUR_ACCESS_TOKEN` with OAuth 2.0 flow.
28
+ client = GarageClient::Client.new(access_token: YOUR_ACCESS_TOKEN)
29
+ ```
30
+
31
+ ### Read from Garage application
32
+ ```ruby
33
+ # GET https://garage.example.com/v1/recipes
34
+ client.get("/recipes")
35
+
36
+ # GET https://garage.example.com/v1/recipes?fields=id,name,ingredients
37
+ client.get("/recipes", fields: "id,name,ingredients")
38
+
39
+ # GET https://garage.example.com/v1/me
40
+ user = client.get("/me")
41
+
42
+ # GET https://garage.example.com/v1/users/:user_id/recipes?fields=__default__,user[id,name]
43
+ client.get("/users/#{user.id}/recipes", fields: "__default__,user[id,name]")
44
+ ```
45
+
46
+ ### Write to Garage application
47
+ ```ruby
48
+ # POST https://garage.example.com/v1/suggestions
49
+ client.post("/suggestions", message: "suggestion message")
50
+
51
+ # POST https://garage.example.com/v1/users/:user_id/bookmark_tags with JSON {"name":"tag name"}
52
+ bookmark_tag = client.post("/users/#{user.id}/bookmark_tags", name: "tag name")
53
+
54
+ # PUT https://garage.example.com/v1/bookmark_tags/:id with JSON {"name":"new tag name"}
55
+ client.put("/bookmark_tags/#{bookmark_tag.id}", name: "new tag name")
56
+
57
+ # DELETE https://garage.example.com/v1/bookmark_tags/:id
58
+ client.delete("/bookmark_tags/#{bookmark_tag.id}")
59
+ ```
60
+
61
+ ### Response
62
+ ```ruby
63
+ # `.get` method returns a GarageClient::Response of a resource.
64
+ user = client.get("/me")
65
+ user.id
66
+ user.url
67
+ user.name
68
+
69
+ # `.get` method also returns a GarageClient::Response of an array of resources.
70
+ # In this case, the response object can respond to `.total_count` method.
71
+ recipes = client.get("/recipes")
72
+ recipes.total_count
73
+ recipes[0].id
74
+ recipes[0].name
75
+
76
+ # While Garage application API returns all default properties, some additional properties are not included in them.
77
+ # You can specify the returned properties by `?fields=...` URI query parameters.
78
+ recipes = client.get("/recipes", fields: "__default__,user")
79
+ recipes[0].id
80
+ recipes[0].name
81
+ recipes[0].user.id
82
+ recipes[0].user.url
83
+ recipes[0].user.name
84
+
85
+ # `.post` method also returns a GarageClient::Response of the newly created resource.
86
+ suggestion = client.post("/suggestions", message: "suggestion message")
87
+ suggestion.message
88
+ ```
89
+
90
+ ## Configuration
91
+ There are the following options:
92
+
93
+ - `adapter` - faraday adapter for http client (default: `:net_http`)
94
+ - `cacher` - take a cacher class in which caching logic is defined (default: nil)
95
+ - `headers` - default http headers (default: `{ "Accept" => "application/json", "User-Agent" => "garage_client #{VERSION}" }`)
96
+ - `endpoint` - Garage application API endpoint (default: nil)
97
+ - `path_prefix` - API path prefix (default: `'/v1'`)
98
+ - `verbose` - Enable verbose http log (default: `false`)
99
+
100
+ You can configure the global settings:
101
+
102
+ ```ruby
103
+ GarageClient.configure do |c|
104
+ c.endpoint = "http://localhost:3000"
105
+ c.verbose = true
106
+ end
107
+ ```
108
+
109
+ or each GarageClient::Client settings:
110
+
111
+ ```ruby
112
+ client = GarageClient::Client.new(
113
+ adapter: :test,
114
+ headers: { "Host" => "garage.example.com" },
115
+ endpoint: "http://localhost:3000",
116
+ path_prefix: "/v2",
117
+ verbose: true,
118
+ )
119
+ ```
120
+
121
+ ## Exceptions
122
+ GarageClient raises one of the following exceptions upon an error.
123
+ Make sure to always look out for these in your code.
124
+
125
+ ```ruby
126
+ GarageClient::Unauthorized
127
+ GarageClient::Forbidden
128
+ GarageClient::NotFound
129
+ GarageClient::NotAcceptable
130
+ GarageClient::Conflict
131
+ GarageClient::UnsupportedMediaType
132
+ GarageClient::UnprocessableEntity
133
+ GarageClient::InternalServerError
134
+ GarageClient::ServiceUnavailable
135
+ ```
136
+
137
+ ## Utility
138
+ `.properties` returns a list of properties of the resource.
139
+
140
+ ```ruby
141
+ user = client.get("/me")
142
+ user.properties #=> [:id, :url, :name, :_links]
143
+ ```
144
+
145
+ `.links` returns a list of link names related to the resource.
146
+
147
+ ```ruby
148
+ user = client.get("/me")
149
+ user.properties #=> [:self, :bookmarks, :recipes, ...]
150
+ user.links.recipes #=> "https://garage.example.com/v1/users/:user_id/recipes"
151
+ ```
152
+
153
+ ## Caching
154
+ Define a cacher class with your custom caching logic to let it cache response, inheriting GarageClient::Cachers::Base.
155
+ It must override `read_from_cache?`, `written_to_cache?`, `key`, and `store` to compose your caching logic.
156
+
157
+ ```ruby
158
+ class MyCacher < GarageClient::Cachers::Base
159
+ private
160
+
161
+ def read_from_cache?
162
+ has_get_method? && has_cached_path?
163
+ end
164
+
165
+ def written_to_cache?
166
+ read_from_cache?
167
+ end
168
+
169
+ def key
170
+ @env[:url].to_s
171
+ end
172
+
173
+ def store
174
+ Rails.cache
175
+ end
176
+
177
+ def options
178
+ { expires_in: 5.minutes }
179
+ end
180
+
181
+ def has_get_method?
182
+ @env[:method] == :get
183
+ end
184
+
185
+ def has_cached_path?
186
+ case @env[:url].path
187
+ when %r<^/v1/searches>
188
+ true
189
+ when %r<^/v1/recipes/\d+>
190
+ true
191
+ end
192
+ end
193
+ end
194
+
195
+ GarageClient::Client.new(cacher: MyCacher)
196
+ ```
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env rake
2
+ require 'rubygems'
3
+ require 'rspec/core/rake_task'
4
+ require 'bundler/gem_tasks'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+ task :default => :spec
8
+ task :test => :spec
@@ -0,0 +1,36 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.unshift File.expand_path("../lib", __FILE__)
3
+ require 'garage_client/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.version = GarageClient::VERSION
7
+ s.name = "garage_client"
8
+ s.homepage = "https://github.com/cookpad/garage_client"
9
+ s.summary = "Ruby client library for the Garage API"
10
+ s.description = s.summary
11
+
12
+ s.files = `git ls-files`.split($\)
13
+ s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
14
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
15
+ s.require_paths = ['lib']
16
+ s.authors = ['Cookpad Inc.']
17
+ s.email = ['kaihatsu@cookpad.com']
18
+
19
+ s.add_dependency 'activesupport', '> 3.2.0'
20
+ s.add_dependency 'faraday', '>= 0.8.0'
21
+ s.add_dependency 'faraday_middleware'
22
+ s.add_dependency 'hashie', '>= 1.2.0'
23
+ s.add_dependency 'link_header'
24
+ s.add_dependency 'mime-types', '~> 1.16'
25
+
26
+ s.add_dependency 'system_timer' if RUBY_VERSION < '1.9'
27
+
28
+ s.add_development_dependency "rake", ">= 0.9.2"
29
+ s.add_development_dependency "rspec"
30
+ s.add_development_dependency "json"
31
+ s.add_development_dependency "guard-rspec"
32
+ s.add_development_dependency "webmock"
33
+ s.add_development_dependency "pry"
34
+ # Until bug fixed: https://github.com/colszowka/simplecov/issues/281
35
+ s.add_development_dependency "simplecov", "~> 0.7.1"
36
+ end
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'faraday', '~> 0.8.0'
4
+ gemspec path: '../'
@@ -0,0 +1,33 @@
1
+ require 'active_support/all'
2
+ require 'faraday'
3
+ require 'faraday_middleware'
4
+
5
+ require 'garage_client/version'
6
+ require 'garage_client/cachers/base'
7
+ require 'garage_client/configuration'
8
+ require 'garage_client/error'
9
+ require 'garage_client/request'
10
+ require 'garage_client/request/json_encoded'
11
+ require 'garage_client/response'
12
+ require 'garage_client/response/cacheable'
13
+ require 'garage_client/response/raise_http_exception'
14
+ require 'garage_client/resource'
15
+ require 'garage_client/client'
16
+
17
+ module GarageClient
18
+ class << self
19
+ GarageClient::Configuration.keys.each do |key|
20
+ delegate key, "#{key}=", to: :configuration
21
+ end
22
+
23
+ delegate 'default_headers', 'default_headers=', to: :configuration
24
+
25
+ def configuration
26
+ @configuration ||= GarageClient::Configuration.new
27
+ end
28
+
29
+ def configure(&block)
30
+ configuration.instance_eval(&block)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,44 @@
1
+ # Inherit this abstract class and pass it to garage_client client to cache its responses.
2
+ module GarageClient
3
+ module Cachers
4
+ class Base
5
+ def initialize(env)
6
+ @env = env
7
+ end
8
+
9
+ def call
10
+ response = read_from_cache? && store.read(key, options) || yield
11
+ store.write(key, response, options) if written_to_cache?
12
+ response
13
+ end
14
+
15
+ private
16
+
17
+ # Return boolean to tell if we need to cache the response or not.
18
+ def allowed_to_read_cache?
19
+ raise NotImplementedError, "You must implement #{self.class}##{__method__}"
20
+ end
21
+
22
+ # Return boolean to tell if we can try to check cache or not.
23
+ def allowed_to_write_cache?
24
+ raise NotImplementedError, "You must implement #{self.class}##{__method__}"
25
+ end
26
+
27
+ # Return string to cache key to store a given HTTP response.
28
+ def key
29
+ raise NotImplementedError, "You must implement #{self.class}##{__method__}"
30
+ end
31
+
32
+ # Return store-object to get or write response (e.g. Rails.cache).
33
+ # This store-object must respond to `fetch(key, options)` method signature.
34
+ def store
35
+ raise NotImplementedError, "You must implement #{self.class}##{__method__}"
36
+ end
37
+
38
+ # Return hash table to be used as store's options.
39
+ def options
40
+ {}
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,93 @@
1
+ module GarageClient
2
+ class Client
3
+ include GarageClient::Request
4
+
5
+ def self.property(key)
6
+ define_method(key) do
7
+ options.fetch(key, GarageClient.configuration.send(key))
8
+ end
9
+
10
+ define_method("#{key}=") do |value|
11
+ options[key] = value
12
+ end
13
+ end
14
+
15
+ attr_reader :options
16
+
17
+ property :adapter
18
+ property :cacher
19
+ property :endpoint
20
+ property :path_prefix
21
+ property :verbose
22
+
23
+ def initialize(options = {})
24
+ require_necessaries(options)
25
+ @options = options
26
+ end
27
+
28
+ def headers
29
+ @headers ||= GarageClient.configuration.headers.merge(given_headers.stringify_keys)
30
+ end
31
+ alias :default_headers :headers
32
+
33
+ def headers=(value)
34
+ @headers = value
35
+ end
36
+ alias :default_headers= :headers=
37
+
38
+ def access_token
39
+ options[:access_token]
40
+ end
41
+
42
+ def access_token=(value)
43
+ options[:access_token] = value
44
+ end
45
+
46
+ def me(params = {}, options = {})
47
+ get('/me', params, options)
48
+ end
49
+
50
+ def conn
51
+ @conn ||= connection
52
+ end
53
+
54
+ def apply_auth_middleware(faraday_builder)
55
+ faraday_builder.authorization :Bearer, access_token if access_token
56
+ end
57
+
58
+ def connection
59
+ Faraday.new(headers: headers, url: endpoint) do |builder|
60
+ # Response Middlewares
61
+ builder.use Faraday::Response::Logger if verbose
62
+ builder.use FaradayMiddleware::Mashify
63
+ builder.use Faraday::Response::ParseJson, :content_type => /\bjson$/
64
+ builder.use GarageClient::Response::Cacheable, cacher: cacher if cacher
65
+ builder.use GarageClient::Response::RaiseHttpException
66
+
67
+ # Request Middlewares
68
+ builder.use Faraday::Request::Multipart
69
+ builder.use GarageClient::Request::JsonEncoded
70
+
71
+ # Low-level Middlewares
72
+ apply_auth_middleware builder
73
+ builder.adapter(*adapter)
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def given_headers
80
+ options[:headers] || options[:default_headers] || {}
81
+ end
82
+
83
+ def require_necessaries(options)
84
+ if !options[:endpoint] && !default_options.endpoint
85
+ raise "Missing endpoint configuration"
86
+ end
87
+ end
88
+
89
+ def default_options
90
+ GarageClient.configuration
91
+ end
92
+ end
93
+ end