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.
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