typekit-client 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +6 -0
  4. data/CHANGELOG.md +9 -5
  5. data/Guardfile +7 -0
  6. data/README.md +160 -131
  7. data/bin/typekit +21 -8
  8. data/lib/typekit/client.rb +5 -5
  9. data/lib/typekit/configuration/base.rb +1 -1
  10. data/lib/typekit/configuration/default.rb +9 -5
  11. data/lib/typekit/connection/dispatcher.rb +2 -2
  12. data/lib/typekit/connection/request.rb +5 -2
  13. data/lib/typekit/connection/response.rb +4 -8
  14. data/lib/typekit/helper.rb +22 -0
  15. data/lib/typekit/processing/converter/boolean.rb +11 -0
  16. data/lib/typekit/processing/converter/datetime.rb +11 -0
  17. data/lib/typekit/processing/converter/errors.rb +22 -0
  18. data/lib/typekit/processing/converter/record.rb +15 -0
  19. data/lib/typekit/processing/converter/records.rb +18 -0
  20. data/lib/typekit/processing/converter/unknown.rb +14 -0
  21. data/lib/typekit/processing/converter.rb +32 -0
  22. data/lib/typekit/processing/parser/json.rb +15 -0
  23. data/lib/typekit/processing/parser/yaml.rb +15 -0
  24. data/lib/typekit/processing/parser.rb +14 -0
  25. data/lib/typekit/processing/translator.rb +16 -0
  26. data/lib/typekit/processing.rb +9 -0
  27. data/lib/typekit/record/base.rb +30 -0
  28. data/lib/typekit/record/family.rb +7 -0
  29. data/lib/typekit/record/kit.rb +7 -0
  30. data/lib/typekit/record/library.rb +7 -0
  31. data/lib/typekit/record/variation.rb +8 -0
  32. data/lib/typekit/record.rb +35 -0
  33. data/lib/typekit/routing/{map.rb → mapper.rb} +1 -1
  34. data/lib/typekit/routing/node/base.rb +1 -0
  35. data/lib/typekit/routing.rb +1 -1
  36. data/lib/typekit/version.rb +1 -1
  37. data/lib/typekit.rb +4 -4
  38. data/spec/cassettes/delete_kits_xxx_ok.yml +16 -0
  39. data/spec/typekit/client_spec.rb +11 -3
  40. data/spec/typekit/configuration_spec.rb +20 -4
  41. data/spec/typekit/connection/dispatcher_spec.rb +4 -4
  42. data/spec/typekit/connection/response_spec.rb +18 -0
  43. data/spec/typekit/helper_spec.rb +34 -0
  44. data/spec/typekit/processing/converter_spec.rb +54 -0
  45. data/spec/typekit/processing/parser_spec.rb +23 -0
  46. data/spec/typekit/record/base_spec.rb +33 -0
  47. data/spec/typekit/record_spec.rb +48 -0
  48. data/spec/typekit/routing/mapper_spec.rb +177 -0
  49. data/spec/typekit/routing/node_spec.rb +31 -10
  50. data/typekit-client.gemspec +2 -1
  51. metadata +53 -13
  52. data/lib/typekit/parser/json.rb +0 -13
  53. data/lib/typekit/parser/yaml.rb +0 -13
  54. data/lib/typekit/parser.rb +0 -14
  55. data/lib/typekit/processor.rb +0 -38
  56. data/spec/typekit/processor_spec.rb +0 -34
  57. data/spec/typekit/routing/map_spec.rb +0 -106
@@ -22,6 +22,28 @@ module Typekit
22
22
  Rack::Utils.build_nested_query(prepare_parameters(parameters))
23
23
  end
24
24
 
25
+ def self.pluralize(name)
26
+ case name
27
+ when /^.*s$/
28
+ name
29
+ when /^(?<root>.*)y$/
30
+ "#{ Regexp.last_match(:root) }ies"
31
+ else
32
+ "#{ name }s"
33
+ end
34
+ end
35
+
36
+ def self.singularize(name)
37
+ case name
38
+ when /^(?<root>.*)ies$/
39
+ "#{ Regexp.last_match(:root) }y"
40
+ when /^(?<root>.*)s$/
41
+ Regexp.last_match(:root)
42
+ else
43
+ name
44
+ end
45
+ end
46
+
25
47
  private
26
48
 
27
49
  def self.prepare_parameters(parameters)
@@ -0,0 +1,11 @@
1
+ module Typekit
2
+ module Processing
3
+ module Converter
4
+ class Boolean
5
+ def process(response, object)
6
+ object # already boolean
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Typekit
2
+ module Processing
3
+ module Converter
4
+ class DateTime
5
+ def process(response, object)
6
+ ::DateTime.parse(object)
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,22 @@
1
+ module Typekit
2
+ module Processing
3
+ module Converter
4
+ class Errors
5
+ ERRORS = {
6
+ 400 => 'There are errors in the data provided by your application',
7
+ 401 => 'Authentication is needed to access the requested endpoint',
8
+ 403 => 'Your application has been rate limited',
9
+ 404 => 'You are requesting a resource that does not exist',
10
+ 500 => 'The servers of Typekit are unable to process the request',
11
+ 503 => 'The Typekit API is offline for maintenance'
12
+ }
13
+ ERRORS.default = 'Unknown server error'
14
+ ERRORS.freeze
15
+
16
+ def process(response, errors)
17
+ raise Error, Array(errors || ERRORS[response.code]).join(', ')
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,15 @@
1
+ module Typekit
2
+ module Processing
3
+ module Converter
4
+ class Record
5
+ def initialize(name)
6
+ @klass = Typekit::Record.const_get(name.to_s.capitalize)
7
+ end
8
+
9
+ def process(response, attributes)
10
+ @klass.new(attributes)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ module Typekit
2
+ module Processing
3
+ module Converter
4
+ class Records
5
+ def initialize(name)
6
+ name = Helper.singularize(name.to_s).capitalize
7
+ @klass = Typekit::Record.const_get(name)
8
+ end
9
+
10
+ def process(response, attribute_collection)
11
+ attribute_collection.map do |attributes|
12
+ @klass.new(attributes)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ module Typekit
2
+ module Processing
3
+ module Converter
4
+ class Unknown
5
+ def initialize(name)
6
+ end
7
+
8
+ def process(response, object)
9
+ object
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,32 @@
1
+ require_relative 'converter/record'
2
+ require_relative 'converter/records'
3
+ require_relative 'converter/boolean'
4
+ require_relative 'converter/datetime'
5
+ require_relative 'converter/errors'
6
+ require_relative 'converter/unknown'
7
+
8
+ module Typekit
9
+ module Processing
10
+ module Converter
11
+ MAPPING = {
12
+ 'ok' => Boolean,
13
+ 'errors' => Errors,
14
+ 'published' => DateTime
15
+ }.freeze
16
+
17
+ def self.build(name)
18
+ if MAPPING.key?(name)
19
+ MAPPING[name].new
20
+ elsif Typekit::Record.collection?(name)
21
+ Records.new(name)
22
+ elsif Typekit::Record.member?(name)
23
+ Record.new(name)
24
+ else
25
+ Unknown.new(name)
26
+ end
27
+ rescue NameError
28
+ raise Error, 'Unknown converter'
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,15 @@
1
+ require 'json'
2
+
3
+ module Typekit
4
+ module Processing
5
+ module Parser
6
+ class JSON
7
+ def process(data)
8
+ ::JSON.parse(data)
9
+ rescue
10
+ raise Error, 'Unable to parse'
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ require 'yaml'
2
+
3
+ module Typekit
4
+ module Processing
5
+ module Parser
6
+ class YAML
7
+ def process(data)
8
+ ::YAML.load(data)
9
+ rescue
10
+ raise Error, 'Unable to parse'
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ require_relative 'parser/json'
2
+ require_relative 'parser/yaml'
3
+
4
+ module Typekit
5
+ module Processing
6
+ module Parser
7
+ def self.build(format)
8
+ self.const_get(format.to_s.upcase).new
9
+ rescue NameError
10
+ raise Error, 'Unknown format'
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ module Typekit
2
+ module Processing
3
+ class Translator
4
+ def initialize(format:)
5
+ @parser = Parser.build(format)
6
+ end
7
+
8
+ def process(response)
9
+ data = @parser.process(response.body) rescue nil
10
+ data = { nil => nil } unless data.is_a?(Hash) && data.length == 1
11
+ name, object = *data.first
12
+ Converter.build(name).process(response, object)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,9 @@
1
+ require_relative 'processing/parser'
2
+ require_relative 'processing/converter'
3
+ require_relative 'processing/translator'
4
+
5
+ module Typekit
6
+ module Processing
7
+ Error = Class.new(Typekit::Error)
8
+ end
9
+ end
@@ -0,0 +1,30 @@
1
+ require 'forwardable'
2
+
3
+ module Typekit
4
+ module Record
5
+ class Base
6
+ extend Forwardable
7
+
8
+ attr_reader :attributes
9
+ def_delegator :attributes, :to_json
10
+
11
+ def initialize(attributes = {})
12
+ @attributes = Hash[attributes.map { |k, v| [ k.to_sym, v ] }]
13
+ end
14
+
15
+ def method_missing(name, *arguments)
16
+ if name.to_s =~ /^(?<name>.*)=$/
17
+ name = Regexp.last_match(:name).to_sym
18
+ return super unless arguments.length == 1
19
+ return super unless @attributes.key?(name)
20
+ @attributes[name] = arguments.first
21
+ else
22
+ return super unless arguments.length.zero?
23
+ return super unless @attributes.key?(name)
24
+ @attributes[name]
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
@@ -0,0 +1,7 @@
1
+ module Typekit
2
+ module Record
3
+ class Family < Base
4
+ # has_many :libraries, :variations
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Typekit
2
+ module Record
3
+ class Kit < Base
4
+ # has_many :families
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Typekit
2
+ module Record
3
+ class Library < Base
4
+ # has_many :families
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ module Typekit
2
+ module Record
3
+ class Variation < Base
4
+ # belongs_to :family
5
+ # has_many :libraries
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,35 @@
1
+ require_relative 'record/base'
2
+ require_relative 'record/family'
3
+ require_relative 'record/variation'
4
+ require_relative 'record/kit'
5
+ require_relative 'record/library'
6
+
7
+ module Typekit
8
+ module Record
9
+ def self.classes
10
+ @classes ||= ObjectSpace.each_object(Class).select do |klass|
11
+ klass < Base
12
+ end
13
+ end
14
+
15
+ def self.collections
16
+ @collections ||= members.map(&:to_s).map do |name|
17
+ Helper.pluralize(name.to_s)
18
+ end.map(&:to_sym)
19
+ end
20
+
21
+ def self.members
22
+ @members ||= classes.map(&:to_s).map(&:downcase).map do |name|
23
+ name.sub(/^.*::/, '')
24
+ end.map(&:to_sym)
25
+ end
26
+
27
+ def self.collection?(name)
28
+ collections.include?(name.to_sym)
29
+ end
30
+
31
+ def self.member?(name)
32
+ members.include?(name.to_sym)
33
+ end
34
+ end
35
+ end
@@ -2,7 +2,7 @@ require 'forwardable'
2
2
 
3
3
  module Typekit
4
4
  module Routing
5
- class Map
5
+ class Mapper
6
6
  extend Forwardable
7
7
 
8
8
  def_delegator :@root, :assemble, :trace
@@ -37,6 +37,7 @@ module Typekit
37
37
 
38
38
  def authorize(request)
39
39
  raise Error, 'Not permitted' unless permitted?(request)
40
+ request.sign(self)
40
41
  request
41
42
  end
42
43
  end
@@ -1,6 +1,6 @@
1
1
  require_relative 'routing/node'
2
2
  require_relative 'routing/proxy'
3
- require_relative 'routing/map'
3
+ require_relative 'routing/mapper'
4
4
 
5
5
  module Typekit
6
6
  module Routing
@@ -1,3 +1,3 @@
1
1
  module Typekit
2
- VERSION = '0.0.2'
2
+ VERSION = '0.0.3'
3
3
  end
data/lib/typekit.rb CHANGED
@@ -1,13 +1,13 @@
1
1
  require_relative 'typekit/version'
2
- require_relative 'typekit/core'
3
2
 
3
+ require_relative 'typekit/core'
4
4
  require_relative 'typekit/helper'
5
5
  require_relative 'typekit/configuration'
6
6
 
7
- require_relative 'typekit/connection'
8
7
  require_relative 'typekit/routing'
8
+ require_relative 'typekit/connection'
9
+ require_relative 'typekit/processing'
9
10
 
10
- require_relative 'typekit/parser'
11
- require_relative 'typekit/processor'
11
+ require_relative 'typekit/record'
12
12
 
13
13
  require_relative 'typekit/client'
@@ -0,0 +1,16 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: delete
5
+ uri: https://typekit.com/api/v1/json/kits/xxx
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: '{"ok":true}'
16
+ recorded_at: Fri, 30 May 2014 00:00:00 GMT
@@ -9,9 +9,9 @@ describe Typekit::Client do
9
9
  context 'when successful' do
10
10
  options = { vcr: { cassette_name: 'index_kits_ok' } }
11
11
 
12
- it 'returns hashes', options do
12
+ it 'returns Records', options do
13
13
  result = subject.index(:kits)
14
- expect(result).to be_kind_of(Hash)
14
+ expect(result.map(&:class).uniq).to eq([ Typekit::Record::Kit ])
15
15
  end
16
16
  end
17
17
 
@@ -20,7 +20,7 @@ describe Typekit::Client do
20
20
 
21
21
  it 'raises exceptions', options do
22
22
  expect { subject.index(:kits) }.to \
23
- raise_error(Typekit::Error, /(authentication|authorized)/i)
23
+ raise_error(Typekit::Processing::Error, /Not authorized/i)
24
24
  end
25
25
  end
26
26
 
@@ -33,4 +33,12 @@ describe Typekit::Client do
33
33
  end
34
34
  end
35
35
  end
36
+
37
+ describe '#delete' do
38
+ options = { vcr: { cassette_name: 'delete_kits_xxx_ok' } }
39
+
40
+ it 'returns true', options do
41
+ expect(subject.delete(:kits, 'xxx')).to be(true)
42
+ end
43
+ end
36
44
  end
@@ -25,10 +25,26 @@ describe Typekit::Configuration do
25
25
  end
26
26
  end
27
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)
28
+ describe 'Base' do
29
+ describe '#mapper' do
30
+ it 'returns a Mapper' do
31
+ expect(build(:default, token: 'nekot').mapper).to \
32
+ be_kind_of(Typekit::Routing::Mapper)
33
+ end
34
+ end
35
+
36
+ describe '#dispatcher' do
37
+ it 'returns a Dispatcher' do
38
+ expect(build(:default, token: 'nekot').dispatcher).to \
39
+ be_kind_of(Typekit::Connection::Dispatcher)
40
+ end
41
+ end
42
+
43
+ describe '#translator' do
44
+ it 'returns a Translator' do
45
+ expect(build(:default, token: 'nekot').translator).to \
46
+ be_kind_of(Typekit::Processing::Translator)
47
+ end
32
48
  end
33
49
  end
34
50
  end
@@ -12,14 +12,14 @@ describe Typekit::Connection::Dispatcher do
12
12
  double('Request', action: action, address: address, parameters: {})
13
13
  end
14
14
 
15
- describe '#deliver' do
15
+ describe '#process' do
16
16
  restful_actions.each do |action|
17
17
  method = rest_http_dictionary[action]
18
18
 
19
19
  context "when sending #{ action } Requests" do
20
20
  it 'sets the token header' do
21
21
  stub = stub_http_request(method, address)
22
- response = subject.deliver(create_request(action))
22
+ response = subject.process(create_request(action))
23
23
  expect(stub).to have_requested(method, address).
24
24
  with(:headers => { 'X-Typekit-Token' => token })
25
25
  end
@@ -27,8 +27,8 @@ describe Typekit::Connection::Dispatcher do
27
27
  it 'returns Responses' do
28
28
  stub_http_request(method, address).
29
29
  to_return(code: '200', body: 'Hej!')
30
- response = subject.deliver(create_request(action))
31
- expect([ response.code, response.content ]).to eq([ 200, 'Hej!' ])
30
+ response = subject.process(create_request(action))
31
+ expect([ response.code, response.body ]).to eq([ 200, 'Hej!' ])
32
32
  end
33
33
  end
34
34
  end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+ require 'typekit'
3
+
4
+ describe Typekit::Connection::Response do
5
+ let(:subject_class) { Typekit::Connection::Response }
6
+
7
+ def create(code: 200, body: '')
8
+ subject_class.new(code: code, body: body)
9
+ end
10
+
11
+ it 'is considered to be successful for HTTP OK' do
12
+ expect(create(code: 200)).to be_success
13
+ end
14
+
15
+ it 'is considered to be successful for HTTP Found' do
16
+ expect(create(code: 302)).to be_success
17
+ end
18
+ end
@@ -38,6 +38,40 @@ describe Typekit::Helper do
38
38
  end
39
39
  end
40
40
 
41
+ describe '.pluralize' do
42
+ {
43
+ 'kit' => 'kits',
44
+ 'kits' => 'kits',
45
+ 'family' => 'families',
46
+ 'families' => 'families',
47
+ 'library' => 'libraries',
48
+ 'libraries' => 'libraries',
49
+ 'variant' => 'variants',
50
+ 'variants' => 'variants'
51
+ }.each do |k, v|
52
+ it "returns #{ v } for #{ k }" do
53
+ expect(subject_module.pluralize(k)).to eq(v)
54
+ end
55
+ end
56
+ end
57
+
58
+ describe '.singularize' do
59
+ {
60
+ 'kit' => 'kit',
61
+ 'kits' => 'kit',
62
+ 'family' => 'family',
63
+ 'families' => 'family',
64
+ 'library' => 'library',
65
+ 'libraries' => 'library',
66
+ 'variant' => 'variant',
67
+ 'variants' => 'variant'
68
+ }.each do |k, v|
69
+ it "returns #{ v } for #{ k }" do
70
+ expect(subject_module.singularize(k)).to eq(v)
71
+ end
72
+ end
73
+ end
74
+
41
75
  describe '.build_query' do
42
76
  it 'handels ordinary parameters' do
43
77
  queries = [
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+ require 'typekit'
3
+
4
+ describe Typekit::Processing::Converter do
5
+ def create(name)
6
+ Typekit::Processing::Converter.build(name)
7
+ end
8
+
9
+ let(:request) { double(code: 200) }
10
+
11
+ describe '.build#process' do
12
+ %w{family kit library}.each do |name|
13
+ it "maps '#{ name }' to a Record" do
14
+ result = create(name).process(request, {})
15
+ expect(result).to be_kind_of(Typekit::Record::Base)
16
+ end
17
+ end
18
+
19
+ %w{families kits libraries}.each do |name|
20
+ it "maps '#{ name }' to an array of Records" do
21
+ result = create(name).process(request, [ {} ])
22
+ result.each { |r| expect(r).to be_kind_of(Typekit::Record::Base) }
23
+ end
24
+ end
25
+
26
+ %w{ok}.each do |name|
27
+ it "maps '#{ name }' to a boolean" do
28
+ result = create(name).process(request, true)
29
+ expect(result).to be_kind_of(::TrueClass)
30
+ end
31
+ end
32
+
33
+ %w{published}.each do |name|
34
+ it "maps '#{ name }' to a datetime" do
35
+ result = create(name).process(request, '2010-05-20T21:15:31Z')
36
+ expect(result).to be_kind_of(::DateTime)
37
+ end
38
+ end
39
+
40
+ [ nil, 'errors' ].each do |name|
41
+ it "raises an exception for '#{ name }'" do
42
+ expect { create(name).process(request, nil) }.to \
43
+ raise_error(Typekit::Processing::Error)
44
+ end
45
+ end
46
+
47
+ %w{kittens puppies}.each do |name|
48
+ it "returns unknowns like '#{ name }' as they are" do
49
+ result = create(name).process(request, 42)
50
+ expect(result).to be(42)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+ require 'typekit'
3
+
4
+ describe Typekit::Processing::Parser do
5
+ let(:subject_class) { Typekit::Processing::Parser }
6
+
7
+ it 'supports JSON' do
8
+ subject = subject_class.build(:json)
9
+ result = subject.process('{ "kits": [] }')
10
+ expect(result).to eq("kits" => [])
11
+ end
12
+
13
+ it 'supports YAML' do
14
+ subject = subject_class.build(:yaml)
15
+ result = subject.process("---\nkits: []")
16
+ expect(result).to eq("kits" => [])
17
+ end
18
+
19
+ it 'does not support XML' do
20
+ expect { subject_class.build(:xml) }.to \
21
+ raise_error(Typekit::Processing::Error, /Unknown format/i)
22
+ end
23
+ end