typekit-client 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +350 -0
- data/Rakefile +7 -0
- data/bin/typekit +146 -0
- data/lib/typekit.rb +13 -0
- data/lib/typekit/client.rb +38 -0
- data/lib/typekit/configuration.rb +16 -0
- data/lib/typekit/configuration/base.rb +21 -0
- data/lib/typekit/configuration/default.rb +34 -0
- data/lib/typekit/connection.rb +10 -0
- data/lib/typekit/connection/adaptor.rb +13 -0
- data/lib/typekit/connection/adaptor/standard.rb +32 -0
- data/lib/typekit/connection/dispatcher.rb +17 -0
- data/lib/typekit/connection/request.rb +23 -0
- data/lib/typekit/connection/response.rb +20 -0
- data/lib/typekit/core.rb +16 -0
- data/lib/typekit/helper.rb +43 -0
- data/lib/typekit/parser.rb +14 -0
- data/lib/typekit/parser/json.rb +13 -0
- data/lib/typekit/parser/yaml.rb +13 -0
- data/lib/typekit/processor.rb +38 -0
- data/lib/typekit/routing.rb +9 -0
- data/lib/typekit/routing/map.rb +43 -0
- data/lib/typekit/routing/node.rb +5 -0
- data/lib/typekit/routing/node/base.rb +45 -0
- data/lib/typekit/routing/node/collection.rb +34 -0
- data/lib/typekit/routing/node/operation.rb +32 -0
- data/lib/typekit/routing/node/root.rb +8 -0
- data/lib/typekit/routing/node/scope.rb +19 -0
- data/lib/typekit/routing/proxy.rb +17 -0
- data/lib/typekit/version.rb +3 -0
- data/spec/cassettes/index_kits_ok.yml +16 -0
- data/spec/cassettes/index_kits_unauthorized.yml +16 -0
- data/spec/cassettes/show_families_calluna_found.yml +16 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/support/rest_helper.rb +22 -0
- data/spec/typekit/client_spec.rb +36 -0
- data/spec/typekit/configuration_spec.rb +34 -0
- data/spec/typekit/connection/adaptor_spec.rb +24 -0
- data/spec/typekit/connection/dispatcher_spec.rb +36 -0
- data/spec/typekit/connection/request_spec.rb +13 -0
- data/spec/typekit/helper_spec.rb +93 -0
- data/spec/typekit/processor_spec.rb +34 -0
- data/spec/typekit/routing/map_spec.rb +106 -0
- data/spec/typekit/routing/node_spec.rb +40 -0
- data/typekit-client.gemspec +33 -0
- metadata +208 -0
@@ -0,0 +1,45 @@
|
|
1
|
+
module Typekit
|
2
|
+
module Routing
|
3
|
+
module Node
|
4
|
+
class Base
|
5
|
+
def append(child)
|
6
|
+
children << child
|
7
|
+
end
|
8
|
+
|
9
|
+
def assemble(request, path)
|
10
|
+
process(request, path)
|
11
|
+
return authorize(request) if path.empty?
|
12
|
+
lookup!(path.first).assemble(request, path)
|
13
|
+
end
|
14
|
+
|
15
|
+
def match(name)
|
16
|
+
end
|
17
|
+
|
18
|
+
def process(request, path)
|
19
|
+
end
|
20
|
+
|
21
|
+
def permitted?(request)
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
def children
|
27
|
+
@children ||= []
|
28
|
+
end
|
29
|
+
|
30
|
+
def lookup(name)
|
31
|
+
children.find { |c| c.match(name) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def lookup!(name)
|
35
|
+
lookup(name) or raise Error, 'Not found'
|
36
|
+
end
|
37
|
+
|
38
|
+
def authorize(request)
|
39
|
+
raise Error, 'Not permitted' unless permitted?(request)
|
40
|
+
request
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Typekit
|
2
|
+
module Routing
|
3
|
+
module Node
|
4
|
+
class Collection < Base
|
5
|
+
def initialize(name, only: nil)
|
6
|
+
@name = name
|
7
|
+
@actions = only && Array(only) || Typekit.actions
|
8
|
+
unless (@actions - Typekit.actions).empty?
|
9
|
+
raise Error, 'Not supported'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def match(name)
|
14
|
+
@name == name
|
15
|
+
end
|
16
|
+
|
17
|
+
def process(request, path)
|
18
|
+
request << path.shift # @name
|
19
|
+
return request if path.empty?
|
20
|
+
request << path.shift # id
|
21
|
+
end
|
22
|
+
|
23
|
+
def permitted?(request)
|
24
|
+
return false unless @actions.include?(request.action)
|
25
|
+
|
26
|
+
id_present = request.path.last != @name
|
27
|
+
member_action = Helper.member_action?(request.action)
|
28
|
+
|
29
|
+
id_present == member_action
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Typekit
|
2
|
+
module Routing
|
3
|
+
module Node
|
4
|
+
class Operation < Base
|
5
|
+
def initialize(name, action:, on:, **options)
|
6
|
+
# TODO: how about on == :collection?
|
7
|
+
unless Typekit.actions.include?(action) && on == :member
|
8
|
+
raise Error, 'Not supported'
|
9
|
+
end
|
10
|
+
@name = name
|
11
|
+
@action = action
|
12
|
+
end
|
13
|
+
|
14
|
+
def match(name)
|
15
|
+
if @name.is_a?(String) && @name =~ /^:/
|
16
|
+
true
|
17
|
+
else
|
18
|
+
@name == name
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def process(request, path)
|
23
|
+
request << path.shift # @name
|
24
|
+
end
|
25
|
+
|
26
|
+
def permitted?(request)
|
27
|
+
@action == request.action
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Typekit
|
2
|
+
module Routing
|
3
|
+
module Node
|
4
|
+
class Scope < Base
|
5
|
+
def initialize(path)
|
6
|
+
@path = Array(path)
|
7
|
+
end
|
8
|
+
|
9
|
+
def match(name)
|
10
|
+
!lookup(name).nil?
|
11
|
+
end
|
12
|
+
|
13
|
+
def process(request, path)
|
14
|
+
@path.each { |chunk| request << chunk }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Typekit
|
2
|
+
module Routing
|
3
|
+
class Proxy
|
4
|
+
def initialize(owner, **options)
|
5
|
+
@owner = owner
|
6
|
+
@options = options
|
7
|
+
end
|
8
|
+
|
9
|
+
def method_missing(name, *arguments, **options, &block)
|
10
|
+
name = :"define_#{ name }"
|
11
|
+
return super unless @owner.respond_to?(name)
|
12
|
+
# NOTE: https://bugs.ruby-lang.org/issues/9776
|
13
|
+
@owner.send(name, *arguments, **options, **@options, &block)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: get
|
5
|
+
uri: https://typekit.com/api/v1/json/kits
|
6
|
+
response:
|
7
|
+
status:
|
8
|
+
code: 200
|
9
|
+
message: OK
|
10
|
+
headers:
|
11
|
+
Status:
|
12
|
+
- 200 OK
|
13
|
+
body:
|
14
|
+
encoding: UTF-8
|
15
|
+
string: '{"kits":[{"id":"bas4cfe","link":"/api/v1/json/kits/bas4cfe"},{"id":"sfh6bkj","link":"/api/v1/json/kits/sfh6bkj"},{"id":"kof8zcn","link":"/api/v1/json/kits/kof8zcn"},{"id":"muq6mxx","link":"/api/v1/json/kits/muq6mxx"}]}'
|
16
|
+
recorded_at: Fri, 23 May 2014 00:00:00 GMT
|
@@ -0,0 +1,16 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: get
|
5
|
+
uri: https://typekit.com/api/v1/json/kits
|
6
|
+
response:
|
7
|
+
status:
|
8
|
+
code: 401
|
9
|
+
message: Unauthorized
|
10
|
+
headers:
|
11
|
+
Status:
|
12
|
+
- 401 Unauthorized
|
13
|
+
body:
|
14
|
+
encoding: UTF-8
|
15
|
+
string: '{"errors":["Not authorized"]}'
|
16
|
+
recorded_at: Fri, 23 May 2014 00:00:00 GMT
|
@@ -0,0 +1,16 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: get
|
5
|
+
uri: https://typekit.com/api/v1/json/families/calluna
|
6
|
+
response:
|
7
|
+
status:
|
8
|
+
code: 302
|
9
|
+
message: Found
|
10
|
+
headers:
|
11
|
+
Status:
|
12
|
+
- 302 Found
|
13
|
+
body:
|
14
|
+
encoding: UTF-8
|
15
|
+
string: '{"family":{"id":"vybr","link":"/api/v1/json/families/vybr"}}'
|
16
|
+
recorded_at: Tue, 27 May 2014 00:00:00 GMT
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require_relative 'support/rest_helper'
|
2
|
+
require 'webmock/rspec'
|
3
|
+
require 'vcr'
|
4
|
+
|
5
|
+
RSpec.configure do |config|
|
6
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
7
|
+
config.run_all_when_everything_filtered = true
|
8
|
+
config.filter_run :focus
|
9
|
+
config.order = 'random'
|
10
|
+
end
|
11
|
+
|
12
|
+
VCR.configure do |config|
|
13
|
+
config.cassette_library_dir = 'spec/cassettes'
|
14
|
+
config.hook_into :webmock
|
15
|
+
config.configure_rspec_metadata!
|
16
|
+
config.default_cassette_options = {
|
17
|
+
allow_unused_http_interactions: false
|
18
|
+
}
|
19
|
+
config.allow_http_connections_when_no_cassette = false
|
20
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module RESTHelper
|
2
|
+
def restful_actions
|
3
|
+
# TODO: keep in sync with Typekit.actions?
|
4
|
+
[ :index, :show, :create, :update, :delete ]
|
5
|
+
end
|
6
|
+
|
7
|
+
def restful_collection_actions
|
8
|
+
# TODO: keep in sync with Typekit.collection_actions?
|
9
|
+
[ :index, :create ]
|
10
|
+
end
|
11
|
+
|
12
|
+
def restful_member_actions
|
13
|
+
# TODO: keep in sync with Typekit.member_actions?
|
14
|
+
[ :show, :update, :delete ]
|
15
|
+
end
|
16
|
+
|
17
|
+
def rest_http_dictionary
|
18
|
+
# TODO: keep in sync with Typekit.action_dictionary?
|
19
|
+
{ :index => :get, :show => :get, :create => :post,
|
20
|
+
:update => :post, :delete => :delete }
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'typekit'
|
3
|
+
|
4
|
+
describe Typekit::Client do
|
5
|
+
let(:token) { 'arbitrary' }
|
6
|
+
let(:subject) { Typekit::Client.new(token: token) }
|
7
|
+
|
8
|
+
describe '#index' do
|
9
|
+
context 'when successful' do
|
10
|
+
options = { vcr: { cassette_name: 'index_kits_ok' } }
|
11
|
+
|
12
|
+
it 'returns hashes', options do
|
13
|
+
result = subject.index(:kits)
|
14
|
+
expect(result).to be_kind_of(Hash)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'when unauthorized' do
|
19
|
+
options = { vcr: { cassette_name: 'index_kits_unauthorized' } }
|
20
|
+
|
21
|
+
it 'raises exceptions', options do
|
22
|
+
expect { subject.index(:kits) }.to \
|
23
|
+
raise_error(Typekit::Error, /(authentication|authorized)/i)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'when found' do
|
28
|
+
options = { vcr: { cassette_name: 'show_families_calluna_found' } }
|
29
|
+
|
30
|
+
it 'returns the Response as is', options do
|
31
|
+
expect { subject.show(:families, 'calluna') }.not_to \
|
32
|
+
raise_error
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'typekit'
|
3
|
+
|
4
|
+
describe Typekit::Configuration do
|
5
|
+
let(:subject_module) { Typekit::Configuration }
|
6
|
+
|
7
|
+
def build(name, **options)
|
8
|
+
subject_module.build(name, **options)
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '.build' do
|
12
|
+
it 'requies an API token to be given' do
|
13
|
+
expect { build(:default) }.to \
|
14
|
+
raise_error(subject_module::Error, /Not enough arguments/i)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'returns registeries of settings' do
|
18
|
+
expect(build(:default, token: 'nekot')).to \
|
19
|
+
be_kind_of(subject_module::Base)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'raises exceptions when encouters unknown registeries' do
|
23
|
+
expect { build(:awesome, token: 'nekot') }.to \
|
24
|
+
raise_error(subject_module::Error, /Unknown configuration/i)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe 'Base#map' do
|
29
|
+
it 'returns a Map' do
|
30
|
+
expect(build(:default, token: 'nekot').map).to \
|
31
|
+
be_kind_of(Typekit::Routing::Map)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'typekit'
|
3
|
+
|
4
|
+
describe Typekit::Connection::Adaptor do
|
5
|
+
let(:address) { 'https://typekit.com/api/v1/json/kits' }
|
6
|
+
|
7
|
+
[ 'Standard' ].each do |adaptor|
|
8
|
+
subject { Typekit::Connection::Adaptor.const_get(adaptor).new }
|
9
|
+
|
10
|
+
describe "#{ adaptor }.process" do
|
11
|
+
it 'returns the code, headers, and body of the response' do
|
12
|
+
stub_http_request(:get, address).to_return(
|
13
|
+
code: '200', body: 'Hej!', headers: { 'a' => 'b' } )
|
14
|
+
response = subject.process(:get, address)
|
15
|
+
expect(response).to eq([ '200', { 'a' => [ 'b' ] }, 'Hej!' ])
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'raises exceptions when encounters unknown methods' do
|
19
|
+
expect { subject.process(:smile, address) }.to \
|
20
|
+
raise_error(Typekit::Connection::Error, /Invalid method/i)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'typekit'
|
3
|
+
|
4
|
+
describe Typekit::Connection::Dispatcher do
|
5
|
+
extend RESTHelper
|
6
|
+
|
7
|
+
let(:token) { 'arbitrary' }
|
8
|
+
let(:address) { 'https://typekit.com/api/v1/json/kits' }
|
9
|
+
let(:subject) { Typekit::Connection::Dispatcher.new(token: token) }
|
10
|
+
|
11
|
+
def create_request(action)
|
12
|
+
double('Request', action: action, address: address, parameters: {})
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '#deliver' do
|
16
|
+
restful_actions.each do |action|
|
17
|
+
method = rest_http_dictionary[action]
|
18
|
+
|
19
|
+
context "when sending #{ action } Requests" do
|
20
|
+
it 'sets the token header' do
|
21
|
+
stub = stub_http_request(method, address)
|
22
|
+
response = subject.deliver(create_request(action))
|
23
|
+
expect(stub).to have_requested(method, address).
|
24
|
+
with(:headers => { 'X-Typekit-Token' => token })
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'returns Responses' do
|
28
|
+
stub_http_request(method, address).
|
29
|
+
to_return(code: '200', body: 'Hej!')
|
30
|
+
response = subject.deliver(create_request(action))
|
31
|
+
expect([ response.code, response.content ]).to eq([ 200, 'Hej!' ])
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'typekit'
|
3
|
+
|
4
|
+
describe Typekit::Connection::Request do
|
5
|
+
subject { Typekit::Connection::Request.new(action: :show) }
|
6
|
+
|
7
|
+
describe '#address' do
|
8
|
+
it 'assembles the address' do
|
9
|
+
[ :kits, 'xxx', :families, 'yyy' ].each { |chunk| subject << chunk }
|
10
|
+
expect(subject.address).to eq('kits/xxx/families/yyy')
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'typekit'
|
3
|
+
|
4
|
+
describe Typekit::Helper do
|
5
|
+
extend RESTHelper
|
6
|
+
|
7
|
+
let(:subject_module) { Typekit::Helper }
|
8
|
+
|
9
|
+
describe '.member_action?' do
|
10
|
+
restful_member_actions.each do |action|
|
11
|
+
it "returns true for the #{ action } member action" do
|
12
|
+
expect(subject_module.member_action?(action)).to be_true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
restful_collection_actions.each do |action|
|
17
|
+
it "returns false for the #{ action } collection action" do
|
18
|
+
expect(subject_module.member_action?(action)).to be_false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'raises exceptions when encounters unknown actions' do
|
23
|
+
expect { subject_module.member_action?(:rock) }.to \
|
24
|
+
raise_error(Typekit::Helper::Error, /Unknown action/i)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '.translate_action' do
|
29
|
+
rest_http_dictionary.each do |action, method|
|
30
|
+
it "returns the #{ method } verb for the #{ action } action" do
|
31
|
+
expect(subject_module.translate_action(action)).to eq(method)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'raises exceptions when encounters unknown actions' do
|
36
|
+
expect { subject_module.translate_action(:rock) }.to \
|
37
|
+
raise_error(Typekit::Helper::Error, /Unknown action/i)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '.build_query' do
|
42
|
+
it 'handels ordinary parameters' do
|
43
|
+
queries = [
|
44
|
+
'name=Megakit&domains=localhost',
|
45
|
+
'domains=localhost&name=Megakit'
|
46
|
+
]
|
47
|
+
query = subject_module.build_query(
|
48
|
+
name: 'Megakit', domains: 'localhost')
|
49
|
+
expect(queries).to include(query)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'handles parameters whose values are ordinary lists' do
|
53
|
+
query = subject_module.build_query(
|
54
|
+
domains: [ 'example.com', 'example.net' ])
|
55
|
+
expect(query).to eq('domains[]=example.com&domains[]=example.net')
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'handles parameters whose values are object lists' do
|
59
|
+
queries = [
|
60
|
+
'families[0][id]=gkmg&families[1][id]=asdf',
|
61
|
+
'families[1][id]=asdf&families[0][id]=gkmg'
|
62
|
+
]
|
63
|
+
query = subject_module.build_query(
|
64
|
+
families: { 0 => { id: 'gkmg' }, 1 => { id: 'asdf' } })
|
65
|
+
expect(queries).to include(query)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'converts integers to decimal strings' do
|
69
|
+
query = subject_module.build_query(page: 42)
|
70
|
+
expect(query).to eq('page=42')
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'converts integers in object lists to decimal strings' do
|
74
|
+
queries = [
|
75
|
+
'primes[0][value]=2&primes[1][value]=3',
|
76
|
+
'primes[1][value]=3&primes[0][value]=2'
|
77
|
+
]
|
78
|
+
query = subject_module.build_query(
|
79
|
+
primes: { 0 => { value: 2 }, 1 => { value: 3 } })
|
80
|
+
expect(queries).to include(query)
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'converts the logical true to the string true' do
|
84
|
+
query = subject_module.build_query(badge: true)
|
85
|
+
expect(query).to eq('badge=true')
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'converts the logical false to the string false' do
|
89
|
+
query = subject_module.build_query(badge: false)
|
90
|
+
expect(query).to eq('badge=false')
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|