garage_client 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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