Skip to content

Commit

Permalink
Add Customer model and resource.
Browse files Browse the repository at this point in the history
  • Loading branch information
nnhansg committed Nov 11, 2019
1 parent 453c5b6 commit bb38829
Show file tree
Hide file tree
Showing 13 changed files with 467 additions and 6 deletions.
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Change Log

All notable changes to this project will be documented in this file.

## [0.1.4] - 2019-11-11

- Add `Customer` resource
- Add `json` and `faraday` gems

## [0.1.0] - 2019-11-08

- Init project

### Added

- Added ability to request data.
6 changes: 3 additions & 3 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require "bundler/gem_tasks"
require "rspec/core/rake_task"
require 'bundler/gem_tasks'
require 'rspec/core/rake_task'

RSpec::Core::RakeTask.new(:spec)

task :default => :spec
task default: :spec
11 changes: 9 additions & 2 deletions lib/unleashed.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
require 'unleashed/version'
require_relative 'unleashed/client'
require_relative 'unleashed/default'
require_relative 'unleashed/error'
require_relative 'unleashed/version'

module Unleashed
# Your code goes here...
class << self
include Unleashed::Configurable
end
end

Unleashed.setup
159 changes: 159 additions & 0 deletions lib/unleashed/client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
require_relative 'configurable'
require_relative 'error'
require_relative 'models/base_model'
require_relative 'models/customer'
require_relative 'resources/base_resource'
require_relative 'resources/customer_resource'
require 'json'
require 'faraday'

module Unleashed
# Client for the Unleashed API
#
# @see https://apidocs.unleashedsoftware.com
class Client
include Unleashed::Configurable

def initialize(options = {})
# Use options passed in, but fall back to module defaults
Unleashed::Configurable.keys.each do |key|
instance_variable_set(
:"@#{key}", options[key] || Unleashed.instance_variable_get(:"@#{key}")
)
end
end

# Create a new Faraday connection
#
# @return [Faraday::Connection]
def connection
Faraday.new(url: @api_endpoint) do |faraday|
faraday.adapter :net_http
end
end

# Create a signature for request
def signature(params = '')
hash = OpenSSL::HMAC.digest('sha256', @api_key, params)
Base64.strict_encode64(hash)
end

def init_default_headers(request)
request.headers['client-type'] = @client_type_header
request.headers['Accept'] = 'application/json'
request.headers['Content-Type'] = 'application/json'
request.headers['api-auth-id'] = @api_id
request.headers['api-auth-signature'] = signature(request.params.to_query)
end

# Make a HTTP GET request
#
# @param url [String] The path, relative to {api_endpoint}
# @param parameters [Hash] Query params for request
# @return [Faraday::Response]
def get(url, parameters = {}, headers = {}, skip_status_check = false)
response = connection.get do |request|
request.url "#{api_endpoint}#{url}"
request.params = parameters

# Set headers
init_default_headers(request)

# Assign more custom headers
headers.each do |key, value|
request.headers[key] = value
end
end

on_complete(response) unless skip_status_check
response
end

# # Make a HTTP POST request
# #
# # @param url [String] The path, relative to {#api_endpoint}
# # @param parameters [Hash] Query params for request
# # @return [Faraday::Response]
# def post(url, parameters = {})
# response = connection.post do |req|
# req.url "#{api_endpoint}#{url}"
# req.headers['Content-Type'] = 'application/json'
# req.body = parameters.to_json
# end
# on_complete(response)
# response
# end

# # Make a HTTP PATCH request
# #
# # @param url [String] The path, relative to {#api_endpoint}
# # @param parameters [Hash] Query params for request
# # @return [Faraday::Response]
# def patch(url, parameters = {})
# response = connection.patch do |req|
# req.url "#{api_endpoint}#{url}"
# req.headers['Content-Type'] = 'application/json'
# req.body = parameters.to_json
# end
# on_complete(response)
# response
# end

# # Make a HTTP DELETE request
# #
# # @param url [String] The path, relative to {#api_endpoint}
# # @param parameters [Hash] Query params for request
# # @return [Faraday::Response]
# def delete(url, parameters = {})
# response = connection.delete do |req|
# req.url "#{api_endpoint}#{url}"
# req.headers['Content-Type'] = 'application/json'
# req.body = parameters.to_json
# end
# on_complete(response)
# response
# end

# Show details of your Platform.
#
# @see https://reference.Unleashed.com/#show-marketplace
#
# @return [Hash]
def marketplace
JSON.parse(get('marketplace').body)['marketplaces']
end

# Available resources for {Client}
#
# @return [Hash]
def self.resources
{
customers: CustomerResource
}
end

# Catch calls for resources
#
def method_missing(name, *args, &block)
if self.class.resources.keys.include?(name)
resources[name] ||= self.class.resources[name].new(self)
resources[name]
else
super
end
end

# Resources being currently used
#
# @return [Hash]
def resources
@resources ||= {}
end

private

def on_complete(response)
fail Unleashed::Error.from_response(response, @errors_format) unless response.success?
end
end
end
40 changes: 40 additions & 0 deletions lib/unleashed/configurable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
module Unleashed
# Configuration options for {Client}, defaulting to values in {Default}.
module Configurable
attr_accessor :api_domain, :api_id, :api_key, :client_type_header, :errors_format

class << self
# List of configurable keys for {Unleashed::Client}.
#
# @return [Array] of option keys
def keys
@keys ||= [
:api_domain,
:api_id,
:api_key,
:client_type_header,
:errors_format
]
end
end

# Reset configuration options to default values.
def reset!
Unleashed::Configurable.keys.each do |key|
instance_variable_set(:"@#{key}", Unleashed::Default.options[key])
end

self
end

alias setup reset!

# API endpoint to be used by {Unleashed::Client}.
# Built from {#api_domain}
#
# @return [String]
def api_endpoint
"https://#{@api_domain}/"
end
end
end
53 changes: 53 additions & 0 deletions lib/unleashed/default.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
module Unleashed
# Default configuration options for {Client}
module Default
# Default API domain
API_DOMAIN = 'api.unleashedsoftware.com'.freeze
# Default client_type_header
CLIENT_TYPE_HEADER = 'API-Sandbox'.freeze

class << self
# Configuration options.
#
# @return [Hash]
def options
Hash[Unleashed::Configurable.keys.map { |key| [key, send(key)] }]
end

# Default API domain from ENV or {API_DOMAIN}.
#
# @return [String]
def api_domain
ENV['UNLEASHED_API_DOMAIN'] || API_DOMAIN
end

# Default api_id from ENV.
#
# @return [String]
def api_id
ENV['UNLEASHED_API_ID']
end

# Default api_key from ENV.
#
# @return [String]
def api_key
ENV['UNLEASHED_API_KEY']
end

# Default client_type_header from ENV.
#
# @return [String]
def client_type_header
ENV['UNLEASHED_CLIENT_TYPE_HEADER'] || CLIENT_TYPE_HEADER
end

# Default errors_format from ENV.
#
# @return [String]
def errors_format
ENV['UNLEASHED_ERRORS_FORMAT'] || 'processed'
end
end
end
end
97 changes: 97 additions & 0 deletions lib/unleashed/error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
module Unleashed
# Custom error class for rescuing from all Unleashed errors
class Error < StandardError
# Returns the appropriate Unleashed::Error subclass based on status
#
# @param [Faraday::Response] response Faraday HTTP response
# @return [Unleashed::Error]
def self.from_response(response, errors_format = nil)
klass = case response.status
when 400 then Unleashed::BadRequest
when 401 then Unleashed::Unauthorized
when 403 then Unleashed::Forbidden
when 404 then Unleashed::NotFound
when 405 then Unleashed::MethodNotAllowed
when 406 then Unleashed::NotAcceptable
when 409 then Unleashed::Conflict
when 422 then Unleashed::UnprocessableEntity
when 400..499 then Unleashed::ClientError
when 500 then Unleashed::InternalServerError
when 501 then Unleashed::NotImplemented
when 502 then Unleashed::BadGateway
when 503 then Unleashed::ServiceUnavailable
when 500..599 then Unleashed::ServerError
end
klass ? klass.new(response, errors_format) : new(response, errors_format)
end

def initialize(response = nil, errors_format = nil)
@response = response
@errors_format = errors_format
super(build_error_message)
end

private

def build_error_message
return nil if @response.nil? || @response.body.nil?

case @errors_format
when 'raw'
@response.body
else
json_response = JSON.parse(@response.body)
message = ''
message << json_response['message'] if json_response.key?('message')

if json_response.key?('errors')
message << json_response['errors'].map { |attribute, content| "#{attribute}: #{content}" }.join(', ')
end

message
end
end
end

# Raised on errors in the 400-499 range
class ClientError < Error; end

# Raised when Unleashed returns a 400 HTTP status code
class BadRequest < ClientError; end

# Raised when Unleashed returns a 401 HTTP status code
class Unauthorized < ClientError; end

# Raised when Unleashed returns a 403 HTTP status code
class Forbidden < ClientError; end

# Raised when Unleashed returns a 404 HTTP status code
class NotFound < ClientError; end

# Raised when Unleashed returns a 405 HTTP status code
class MethodNotAllowed < ClientError; end

# Raised when Unleashed returns a 406 HTTP status code
class NotAcceptable < ClientError; end

# Raised when Unleashed returns a 409 HTTP status code
class Conflict < ClientError; end

# Raised when Unleashed returns a 422 HTTP status code
class UnprocessableEntity < ClientError; end

# Raised on errors in the 500-599 range
class ServerError < Error; end

# Raised when Unleashed returns a 500 HTTP status code
class InternalServerError < ServerError; end

# Raised when Unleashed returns a 501 HTTP status code
class NotImplemented < ServerError; end

# Raised when Unleashed returns a 502 HTTP status code
class BadGateway < ServerError; end

# Raised when Unleashed returns a 503 HTTP status code
class ServiceUnavailable < ServerError; end
end
Loading

0 comments on commit bb38829

Please sign in to comment.