typekit-client 0.0.2
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.
- 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
|