typekit-client 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.rspec +2 -0
  4. data/CHANGELOG.md +7 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +350 -0
  8. data/Rakefile +7 -0
  9. data/bin/typekit +146 -0
  10. data/lib/typekit.rb +13 -0
  11. data/lib/typekit/client.rb +38 -0
  12. data/lib/typekit/configuration.rb +16 -0
  13. data/lib/typekit/configuration/base.rb +21 -0
  14. data/lib/typekit/configuration/default.rb +34 -0
  15. data/lib/typekit/connection.rb +10 -0
  16. data/lib/typekit/connection/adaptor.rb +13 -0
  17. data/lib/typekit/connection/adaptor/standard.rb +32 -0
  18. data/lib/typekit/connection/dispatcher.rb +17 -0
  19. data/lib/typekit/connection/request.rb +23 -0
  20. data/lib/typekit/connection/response.rb +20 -0
  21. data/lib/typekit/core.rb +16 -0
  22. data/lib/typekit/helper.rb +43 -0
  23. data/lib/typekit/parser.rb +14 -0
  24. data/lib/typekit/parser/json.rb +13 -0
  25. data/lib/typekit/parser/yaml.rb +13 -0
  26. data/lib/typekit/processor.rb +38 -0
  27. data/lib/typekit/routing.rb +9 -0
  28. data/lib/typekit/routing/map.rb +43 -0
  29. data/lib/typekit/routing/node.rb +5 -0
  30. data/lib/typekit/routing/node/base.rb +45 -0
  31. data/lib/typekit/routing/node/collection.rb +34 -0
  32. data/lib/typekit/routing/node/operation.rb +32 -0
  33. data/lib/typekit/routing/node/root.rb +8 -0
  34. data/lib/typekit/routing/node/scope.rb +19 -0
  35. data/lib/typekit/routing/proxy.rb +17 -0
  36. data/lib/typekit/version.rb +3 -0
  37. data/spec/cassettes/index_kits_ok.yml +16 -0
  38. data/spec/cassettes/index_kits_unauthorized.yml +16 -0
  39. data/spec/cassettes/show_families_calluna_found.yml +16 -0
  40. data/spec/spec_helper.rb +20 -0
  41. data/spec/support/rest_helper.rb +22 -0
  42. data/spec/typekit/client_spec.rb +36 -0
  43. data/spec/typekit/configuration_spec.rb +34 -0
  44. data/spec/typekit/connection/adaptor_spec.rb +24 -0
  45. data/spec/typekit/connection/dispatcher_spec.rb +36 -0
  46. data/spec/typekit/connection/request_spec.rb +13 -0
  47. data/spec/typekit/helper_spec.rb +93 -0
  48. data/spec/typekit/processor_spec.rb +34 -0
  49. data/spec/typekit/routing/map_spec.rb +106 -0
  50. data/spec/typekit/routing/node_spec.rb +40 -0
  51. data/typekit-client.gemspec +33 -0
  52. metadata +208 -0
data/lib/typekit.rb ADDED
@@ -0,0 +1,13 @@
1
+ require_relative 'typekit/version'
2
+ require_relative 'typekit/core'
3
+
4
+ require_relative 'typekit/helper'
5
+ require_relative 'typekit/configuration'
6
+
7
+ require_relative 'typekit/connection'
8
+ require_relative 'typekit/routing'
9
+
10
+ require_relative 'typekit/parser'
11
+ require_relative 'typekit/processor'
12
+
13
+ require_relative 'typekit/client'
@@ -0,0 +1,38 @@
1
+ require 'forwardable'
2
+
3
+ module Typekit
4
+ class Client
5
+ extend Forwardable
6
+
7
+ def_delegators :@config, :map, :dispatcher, :processor
8
+ private def_delegator :dispatcher, :deliver
9
+ private def_delegator :processor, :process
10
+
11
+ def initialize(config: :default, **options)
12
+ @config = Configuration.build(config, **options)
13
+ end
14
+
15
+ def perform(*arguments)
16
+ process(deliver(trace(*arguments)))
17
+ end
18
+
19
+ Typekit.actions.each do |action|
20
+ define_method(action) do |*arguments|
21
+ perform(action, *arguments)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def prepare(action, *path)
28
+ parameters = path.last.is_a?(Hash) ? path.pop : {}
29
+ [ action.to_sym, path.flatten.map(&:to_sym), parameters ]
30
+ end
31
+
32
+ def trace(*arguments)
33
+ action, path, parameters = prepare(*arguments)
34
+ request = Connection::Request.new(action: action, parameters: parameters)
35
+ map.trace(request, path)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,16 @@
1
+ require_relative 'configuration/base'
2
+ require_relative 'configuration/default'
3
+
4
+ module Typekit
5
+ module Configuration
6
+ Error = Class.new(Typekit::Error)
7
+
8
+ def self.build(name, **options)
9
+ self.const_get(name.to_s.capitalize).new(**options)
10
+ rescue NameError
11
+ raise Error, 'Unknown configuration'
12
+ rescue ArgumentError => e
13
+ raise Error, 'Not enough arguments'
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,21 @@
1
+ module Typekit
2
+ module Configuration
3
+ class Base
4
+ attr_reader :version, :format, :token
5
+
6
+ def initialize(version: 1, format: :json, token:)
7
+ @version = version
8
+ @format = format
9
+ @token = token
10
+ end
11
+
12
+ [ :map, :dispatcher, :processor ].each do |component|
13
+ class_eval <<-METHOD, __FILE__, __LINE__ + 1
14
+ def #{ component }
15
+ @#{ component } ||= build_#{ component }
16
+ end
17
+ METHOD
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,34 @@
1
+ module Typekit
2
+ module Configuration
3
+ class Default < Base
4
+ private
5
+
6
+ def build_map
7
+ context = [ Typekit.address, "v#{ version }", format ]
8
+ Routing::Map.new do
9
+ scope context do
10
+ resources :families, only: :show do
11
+ show ':variant', on: :member
12
+ end
13
+
14
+ resources :kits do
15
+ resources :families, only: [ :show, :update, :delete ]
16
+ show :published, on: :member
17
+ update :publish, on: :member
18
+ end
19
+
20
+ resources :libraries, only: [ :index, :show ]
21
+ end
22
+ end
23
+ end
24
+
25
+ def build_dispatcher
26
+ Connection::Dispatcher.new(adaptor: :standard, token: token)
27
+ end
28
+
29
+ def build_processor
30
+ Processor.new(format: format)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,10 @@
1
+ require_relative 'connection/adaptor'
2
+ require_relative 'connection/request'
3
+ require_relative 'connection/response'
4
+ require_relative 'connection/dispatcher'
5
+
6
+ module Typekit
7
+ module Connection
8
+ Error = Class.new(Typekit::Error)
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ require_relative 'adaptor/standard'
2
+
3
+ module Typekit
4
+ module Connection
5
+ module Adaptor
6
+ def self.build(name)
7
+ self.const_get(name.to_s.capitalize).new
8
+ rescue NameError
9
+ raise Error, 'Unknown connection adaptor'
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,32 @@
1
+ require 'net/https'
2
+ require 'uri'
3
+
4
+ module Typekit
5
+ module Connection
6
+ module Adaptor
7
+ class Standard
8
+ def process(method, address, parameters = {}, headers = {})
9
+ klass = Net::HTTP.const_get(method.to_s.capitalize)
10
+ request = klass.new(build_uri(address, parameters))
11
+ headers.each { |k, v| request[k] = v }
12
+ http = Net::HTTP.new(request.uri.host, request.uri.port)
13
+ http.use_ssl = true if address =~ /^https:/
14
+ response = http.request(request)
15
+ [ response.code, response.to_hash, response.body ]
16
+ rescue NameError
17
+ raise Error, 'Invalid method'
18
+ rescue SocketError
19
+ raise Error, 'Connection failed'
20
+ end
21
+
22
+ private
23
+
24
+ def build_uri(address, parameters)
25
+ chunks = [ address ]
26
+ chunks << Helper.build_query(parameters) unless parameters.empty?
27
+ URI(chunks.join('?'))
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,17 @@
1
+ module Typekit
2
+ module Connection
3
+ class Dispatcher
4
+ def initialize(adaptor: :standard, token:)
5
+ @token = token
6
+ @adaptor = Adaptor.build(adaptor)
7
+ end
8
+
9
+ def deliver(request)
10
+ method = Helper.translate_action(request.action)
11
+ code, _, body = @adaptor.process(method, request.address,
12
+ request.parameters, 'X-Typekit-Token' => @token)
13
+ Response.new(code: code.to_i, content: body)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ require 'forwardable'
2
+
3
+ module Typekit
4
+ module Connection
5
+ class Request
6
+ extend Forwardable
7
+
8
+ attr_reader :action, :parameters, :path
9
+ def_delegators :@path, :<<
10
+
11
+ def initialize(action:, parameters: {})
12
+ @action = action
13
+ @parameters = parameters
14
+ @path = []
15
+ end
16
+
17
+ def address
18
+ # TODO: cache?
19
+ @path.map(&:to_s).join('/')
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,20 @@
1
+ module Typekit
2
+ module Connection
3
+ class Response
4
+ attr_reader :code, :content
5
+
6
+ def initialize(code:, content:)
7
+ @code = code
8
+ @content = content
9
+ end
10
+
11
+ def success?
12
+ @code == 200
13
+ end
14
+
15
+ def redirect?
16
+ @code == 302
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,16 @@
1
+ module Typekit
2
+ Error = Class.new(StandardError)
3
+
4
+ @address = 'https://typekit.com/api'.freeze
5
+
6
+ @actions = [ :index, :show, :create, :update, :delete ].freeze
7
+ @collection_actions = [ :index, :create ].freeze
8
+ @member_actions = [ :show, :update, :delete ].freeze
9
+ @action_dictionary = { :index => :get, :show => :get,
10
+ :create => :post, :update => :post, :delete => :delete }.freeze
11
+
12
+ singleton_class.class_eval do
13
+ attr_reader :address, :actions, :collection_actions,
14
+ :member_actions, :action_dictionary
15
+ end
16
+ end
@@ -0,0 +1,43 @@
1
+ require 'rack/utils'
2
+
3
+ module Typekit
4
+ module Helper
5
+ Error = Class.new(Typekit::Error)
6
+
7
+ def self.member_action?(action)
8
+ if Typekit.member_actions.include?(action)
9
+ true
10
+ elsif Typekit.collection_actions.include?(action)
11
+ false
12
+ else
13
+ raise Error, 'Unknown action'
14
+ end
15
+ end
16
+
17
+ def self.translate_action(action)
18
+ Typekit.action_dictionary[action] or raise Error, 'Unknown action'
19
+ end
20
+
21
+ def self.build_query(parameters)
22
+ Rack::Utils.build_nested_query(prepare_parameters(parameters))
23
+ end
24
+
25
+ private
26
+
27
+ def self.prepare_parameters(parameters)
28
+ # PATCH: https://github.com/rack/rack/issues/557
29
+ Hash[
30
+ parameters.map do |key, value|
31
+ case value
32
+ when Integer, TrueClass, FalseClass
33
+ [ key, value.to_s ]
34
+ when Hash
35
+ [ key, prepare_parameters(value) ]
36
+ else
37
+ [ key, value ]
38
+ end
39
+ end
40
+ ]
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,14 @@
1
+ require_relative 'parser/json'
2
+ require_relative 'parser/yaml'
3
+
4
+ module Typekit
5
+ module Parser
6
+ Error = Class.new(Typekit::Error)
7
+
8
+ def self.build(format)
9
+ self.const_get(format.to_s.upcase).new
10
+ rescue NameError
11
+ raise Error, 'Unknown format'
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ require 'json'
2
+
3
+ module Typekit
4
+ module Parser
5
+ class JSON
6
+ def process(data)
7
+ ::JSON.parse(data)
8
+ rescue
9
+ raise Error, 'Unable to parse'
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ require 'yaml'
2
+
3
+ module Typekit
4
+ module Parser
5
+ class YAML
6
+ def process(data)
7
+ ::YAML.load(data)
8
+ rescue
9
+ raise Error, 'Unable to parse'
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,38 @@
1
+ module Typekit
2
+ class Processor
3
+ ERRORS = {
4
+ 400 => 'There are errors in the data provided by your application',
5
+ 401 => 'Authentication is needed to access the requested endpoint',
6
+ 403 => 'Your application has been rate limited',
7
+ 404 => 'You are requesting a resource that does not exist',
8
+ 500 => 'Typekit’s servers are unable to process the request',
9
+ 503 => 'Typekit’s API is offline for maintenance'
10
+ }.freeze
11
+
12
+ def initialize(format:)
13
+ @parser = Parser.build(format)
14
+ end
15
+
16
+ def process(response)
17
+ if response.success?
18
+ data = @parser.process(response.content)
19
+ process_success(response, data)
20
+ else
21
+ data = @parser.process(response.content) rescue {}
22
+ process_failure(response, data)
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def process_success(request, data)
29
+ data
30
+ end
31
+
32
+ def process_failure(request, data)
33
+ return data if request.redirect?
34
+ message = data['errors'] || ERRORS[request.code] || 'Unknown server error'
35
+ raise Error, Array(message).join(', ')
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,9 @@
1
+ require_relative 'routing/node'
2
+ require_relative 'routing/proxy'
3
+ require_relative 'routing/map'
4
+
5
+ module Typekit
6
+ module Routing
7
+ Error = Class.new(Typekit::Error)
8
+ end
9
+ end
@@ -0,0 +1,43 @@
1
+ require 'forwardable'
2
+
3
+ module Typekit
4
+ module Routing
5
+ class Map
6
+ extend Forwardable
7
+
8
+ def_delegator :@root, :assemble, :trace
9
+
10
+ def initialize(&block)
11
+ @root = Node::Root.new
12
+ define(&block) if block_given?
13
+ end
14
+
15
+ def define(&block)
16
+ proxy = Proxy.new(self)
17
+ proxy.instance_eval(&block)
18
+ end
19
+
20
+ def define_scope(path, parent: @root, &block)
21
+ child = Node::Scope.new(path)
22
+ parent.append(child)
23
+ proxy = Proxy.new(self, parent: child)
24
+ proxy.instance_eval(&block)
25
+ end
26
+
27
+ def define_resources(name, parent: @root, **options, &block)
28
+ child = Node::Collection.new(name, **options)
29
+ parent.append(child)
30
+ return unless block_given?
31
+ proxy = Proxy.new(self, parent: child)
32
+ proxy.instance_eval(&block)
33
+ end
34
+
35
+ Typekit.actions.each do |action|
36
+ define_method "define_#{ action }" do |name, parent:, **options|
37
+ child = Node::Operation.new(name, action: action, **options)
38
+ parent.append(child)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end