jahuty 2.0.0 → 3.2.0
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 +4 -4
- data/.circleci/config.yml +33 -0
- data/.rubocop.yml +15 -0
- data/CHANGELOG.md +24 -0
- data/Gemfile +3 -1
- data/README.md +176 -39
- data/Rakefile +11 -3
- data/bin/console +4 -3
- data/bin/setup +2 -0
- data/jahuty.gemspec +33 -16
- data/lib/jahuty.rb +25 -15
- data/lib/jahuty/action/base.rb +15 -0
- data/lib/jahuty/action/index.rb +9 -0
- data/lib/jahuty/action/show.rb +16 -0
- data/lib/jahuty/api/client.rb +37 -0
- data/lib/jahuty/cache/facade.rb +46 -0
- data/lib/jahuty/client.rb +51 -0
- data/lib/jahuty/exception/{not_ok.rb → error.rb} +6 -1
- data/lib/jahuty/request/base.rb +16 -0
- data/lib/jahuty/request/factory.rb +29 -0
- data/lib/jahuty/resource/factory.rb +27 -0
- data/lib/jahuty/resource/problem.rb +25 -0
- data/lib/jahuty/resource/render.rb +26 -0
- data/lib/jahuty/response/handler.rb +60 -0
- data/lib/jahuty/service/base.rb +12 -0
- data/lib/jahuty/service/snippet.rb +97 -0
- data/lib/jahuty/util.rb +25 -0
- data/lib/jahuty/version.rb +3 -1
- metadata +140 -12
- data/lib/jahuty/data/problem.rb +0 -21
- data/lib/jahuty/data/render.rb +0 -21
- data/lib/jahuty/service/connect.rb +0 -23
- data/lib/jahuty/service/render.rb +0 -25
- data/lib/jahuty/snippet.rb +0 -15
data/lib/jahuty.rb
CHANGED
@@ -1,23 +1,33 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require 'jahuty/version'
|
4
4
|
|
5
|
-
require
|
6
|
-
require
|
5
|
+
require 'jahuty/action/base'
|
6
|
+
require 'jahuty/action/index'
|
7
|
+
require 'jahuty/action/show'
|
7
8
|
|
8
|
-
require
|
9
|
+
require 'jahuty/api/client'
|
9
10
|
|
10
|
-
require
|
11
|
-
require "jahuty/service/render"
|
11
|
+
require 'jahuty/cache/facade'
|
12
12
|
|
13
|
-
|
14
|
-
|
13
|
+
require 'jahuty/exception/error'
|
14
|
+
|
15
|
+
require 'jahuty/request/base'
|
16
|
+
require 'jahuty/request/factory'
|
17
|
+
|
18
|
+
require 'jahuty/resource/problem'
|
19
|
+
require 'jahuty/resource/render'
|
20
|
+
require 'jahuty/resource/factory'
|
21
|
+
|
22
|
+
require 'jahuty/response/handler'
|
15
23
|
|
16
|
-
|
17
|
-
|
24
|
+
require 'jahuty/service/base'
|
25
|
+
require 'jahuty/service/snippet'
|
18
26
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
27
|
+
require 'jahuty/client'
|
28
|
+
|
29
|
+
require 'jahuty/util'
|
30
|
+
|
31
|
+
module Jahuty
|
32
|
+
BASE_URI = 'https://api.jahuty.com'
|
23
33
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jahuty
|
4
|
+
module Action
|
5
|
+
# Provides common logic for service actions.
|
6
|
+
class Base
|
7
|
+
attr_accessor :resource, :params
|
8
|
+
|
9
|
+
def initialize(resource:, params: {})
|
10
|
+
@resource = resource
|
11
|
+
@params = params
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jahuty
|
4
|
+
module Action
|
5
|
+
# Displays a specific resource.
|
6
|
+
class Show < Base
|
7
|
+
attr_accessor :id
|
8
|
+
|
9
|
+
def initialize(resource:, id:, params: {})
|
10
|
+
@id = id
|
11
|
+
|
12
|
+
super(resource: resource, params: params)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'faraday'
|
4
|
+
|
5
|
+
module Jahuty
|
6
|
+
module Api
|
7
|
+
# Handles HTTP requests and responses.
|
8
|
+
class Client
|
9
|
+
HEADERS = {
|
10
|
+
'Accept' => 'application/json;q=0.9,*/*;q=0.8',
|
11
|
+
'Accept-Encoding' => 'gzip, deflate',
|
12
|
+
'Content-Type' => 'application/json; charset=utf-8',
|
13
|
+
'User-Agent' => "Jahuty Ruby SDK v#{::Jahuty::VERSION}"
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
def initialize(api_key:)
|
17
|
+
@api_key = api_key
|
18
|
+
end
|
19
|
+
|
20
|
+
def send(request)
|
21
|
+
@client ||= Faraday.new(url: ::Jahuty::BASE_URI, headers: headers)
|
22
|
+
|
23
|
+
@client.send(
|
24
|
+
request.method.to_sym,
|
25
|
+
request.path,
|
26
|
+
{ params: request.params }
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def headers
|
33
|
+
{ 'Authorization' => "Bearer #{@api_key}" }.merge(HEADERS)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jahuty
|
4
|
+
module Cache
|
5
|
+
# Abstracts away the differences in cache implementation methods and
|
6
|
+
# argument lists.
|
7
|
+
class Facade
|
8
|
+
def initialize(cache)
|
9
|
+
@cache = cache
|
10
|
+
end
|
11
|
+
|
12
|
+
def delete(key)
|
13
|
+
if @cache.respond_to? :delete
|
14
|
+
@cache.delete key
|
15
|
+
elsif @cache.respond_to? :unset
|
16
|
+
@cache.unset key
|
17
|
+
else
|
18
|
+
raise NoMethodError, 'Cache must respond to :delete or :unset'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def read(key)
|
23
|
+
if @cache.respond_to? :read
|
24
|
+
@cache.read key
|
25
|
+
elsif @cache.respond_to? :get
|
26
|
+
@cache.get key
|
27
|
+
else
|
28
|
+
raise NoMethodError, 'Cache must respond to :read or :get'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def write(key, value, expires_in: nil)
|
33
|
+
if Object.const_defined?('::ActiveSupport::Cache::Store') &&
|
34
|
+
@cache.is_a?(::ActiveSupport::Cache::Store)
|
35
|
+
@cache.write key, value, expires_in: expires_in, race_condition_ttl: 10
|
36
|
+
elsif @cache.respond_to? :write
|
37
|
+
@cache.write key, value, expires_in: expires_in
|
38
|
+
elsif @cache.respond_to? :set
|
39
|
+
@cache.set key, value, expires_in: expires_in
|
40
|
+
else
|
41
|
+
raise NoMethodError, 'Cache must respond to :write or :set'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'mini_cache'
|
4
|
+
|
5
|
+
module Jahuty
|
6
|
+
# Executes requests against Jahuty's API and returns resources.
|
7
|
+
class Client
|
8
|
+
def initialize(api_key:, cache: nil, expires_in: nil)
|
9
|
+
@api_key = api_key
|
10
|
+
@cache = Cache::Facade.new(cache || ::MiniCache::Store.new)
|
11
|
+
@expires_in = expires_in
|
12
|
+
@services = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
# Allows services to be accessed as properties (e.g., jahuty.snippets).
|
16
|
+
def method_missing(name, *args, &block)
|
17
|
+
if args.empty? && name == :snippets
|
18
|
+
unless @services.key?(name)
|
19
|
+
@services[name] = Service::Snippet.new(
|
20
|
+
client: self, cache: @cache, expires_in: @expires_in
|
21
|
+
)
|
22
|
+
end
|
23
|
+
@services[name]
|
24
|
+
else
|
25
|
+
super
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def request(action)
|
30
|
+
@requests ||= Request::Factory.new
|
31
|
+
|
32
|
+
request = @requests.call(action)
|
33
|
+
|
34
|
+
@client ||= Api::Client.new(api_key: @api_key)
|
35
|
+
|
36
|
+
response = @client.send(request)
|
37
|
+
|
38
|
+
@responses ||= Response::Handler.new
|
39
|
+
|
40
|
+
result = @responses.call(action, response)
|
41
|
+
|
42
|
+
raise Exception::Error.new(result), 'API problem' if result.is_a?(Resource::Problem)
|
43
|
+
|
44
|
+
result
|
45
|
+
end
|
46
|
+
|
47
|
+
def respond_to_missing?(name, include_private = false)
|
48
|
+
name == :snippets || super
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -1,10 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Jahuty
|
2
4
|
module Exception
|
3
|
-
|
5
|
+
# Thrown when a client- or server-error occurs.
|
6
|
+
class Error < ::StandardError
|
4
7
|
attr_reader :problem
|
5
8
|
|
6
9
|
def initialize(problem)
|
7
10
|
@problem = problem
|
11
|
+
|
12
|
+
super
|
8
13
|
end
|
9
14
|
|
10
15
|
def message
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jahuty
|
4
|
+
module Request
|
5
|
+
# Provides common logic for all requests.
|
6
|
+
class Base
|
7
|
+
attr_accessor :method, :path, :params
|
8
|
+
|
9
|
+
def initialize(method:, path:, params: {})
|
10
|
+
@method = method
|
11
|
+
@path = path
|
12
|
+
@params = params
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jahuty
|
4
|
+
module Request
|
5
|
+
# Instantiates a request from an action.
|
6
|
+
class Factory
|
7
|
+
def call(action)
|
8
|
+
Base.new(
|
9
|
+
method: 'get',
|
10
|
+
path: path(action),
|
11
|
+
params: action.params
|
12
|
+
)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def path(action)
|
18
|
+
case action
|
19
|
+
when ::Jahuty::Action::Show
|
20
|
+
"snippets/#{action.id}/render"
|
21
|
+
when ::Jahuty::Action::Index
|
22
|
+
'snippets/render'
|
23
|
+
else
|
24
|
+
raise ArgumentError, 'Action is not supported'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jahuty
|
4
|
+
module Resource
|
5
|
+
# Instantiates and returns a resource.
|
6
|
+
class Factory
|
7
|
+
CLASSES = {
|
8
|
+
problem: Problem.name,
|
9
|
+
render: Render.name
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
def call(resource_name, payload)
|
13
|
+
klass = class_name(resource_name.to_sym)
|
14
|
+
|
15
|
+
raise ArgumentError, "#{resource_name} missing" if klass.nil?
|
16
|
+
|
17
|
+
Object.const_get(klass).send(:from, **payload)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def class_name(resource_name)
|
23
|
+
CLASSES[resource_name.to_sym]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jahuty
|
4
|
+
module Resource
|
5
|
+
# An application/problem+json response. The API should respond with a
|
6
|
+
# problem whenever a client- or server-error occurs.
|
7
|
+
class Problem
|
8
|
+
attr_accessor :status, :type, :detail
|
9
|
+
|
10
|
+
def initialize(status:, type:, detail:)
|
11
|
+
@status = status
|
12
|
+
@type = type
|
13
|
+
@detail = detail
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.from(data)
|
17
|
+
raise ArgumentError.new, 'Key :status missing' unless data.key?(:status)
|
18
|
+
raise ArgumentError.new, 'Key :type missing' unless data.key?(:type)
|
19
|
+
raise ArgumentError.new, 'Key :detail missing' unless data.key?(:detail)
|
20
|
+
|
21
|
+
Problem.new(data.slice(:status, :type, :detail))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jahuty
|
4
|
+
module Resource
|
5
|
+
# A snippet's rendered content.
|
6
|
+
class Render
|
7
|
+
attr_accessor :content, :snippet_id
|
8
|
+
|
9
|
+
def initialize(content:, snippet_id:)
|
10
|
+
@content = content
|
11
|
+
@snippet_id = snippet_id
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.from(data)
|
15
|
+
raise ArgumentError.new, 'Key :content missing' unless data.key?(:content)
|
16
|
+
raise ArgumentError.new, 'Key :snippet_id missing' unless data.key?(:snippet_id)
|
17
|
+
|
18
|
+
Render.new(data.slice(:content, :snippet_id))
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
@content
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Jahuty
|
6
|
+
module Response
|
7
|
+
# Inspects the response and returns the appropriate resource or collection.
|
8
|
+
class Handler
|
9
|
+
def call(action, response)
|
10
|
+
resource_name = name_resource action, response
|
11
|
+
|
12
|
+
payload = parse response
|
13
|
+
|
14
|
+
@resources ||= ::Jahuty::Resource::Factory.new
|
15
|
+
|
16
|
+
if collection?(action, payload)
|
17
|
+
payload.map { |data| @resources.call resource_name, data }
|
18
|
+
elsif resource?(action, payload)
|
19
|
+
@resources.call resource_name, payload
|
20
|
+
else
|
21
|
+
raise ArgumentError, 'Action and payload mismatch'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def collection?(action, payload)
|
28
|
+
action.is_a?(Action::Index) && payload.is_a?(::Array)
|
29
|
+
end
|
30
|
+
|
31
|
+
def name_resource(action, response)
|
32
|
+
if success? response
|
33
|
+
action.resource
|
34
|
+
elsif problem? response
|
35
|
+
'problem'
|
36
|
+
else
|
37
|
+
raise ArgumentError, 'Unexpected response'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def parse(response)
|
42
|
+
JSON.parse(response.body, symbolize_names: true)
|
43
|
+
end
|
44
|
+
|
45
|
+
def problem?(response)
|
46
|
+
response.headers['Content-Type'].include?('application/problem+json') &&
|
47
|
+
(response.status < 200 || response.status >= 300)
|
48
|
+
end
|
49
|
+
|
50
|
+
def resource?(action, payload)
|
51
|
+
!action.is_a?(Action::Index) && payload.is_a?(::Object)
|
52
|
+
end
|
53
|
+
|
54
|
+
def success?(response)
|
55
|
+
response.headers['Content-Type'].include?('application/json') &&
|
56
|
+
response.status.between?(200, 299)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|