Skip to content

Commit

Permalink
better error handling, fixes #12
Browse files Browse the repository at this point in the history
Adds a custom exception for each error response described at
https://app.speechmatics.com/api-details#errorCodes

If an error response does not match a known error, it's raised as a
Speechmatics::Response::Error::Unknown

All custom exceptions, including Unknown, derive from the
Speechmatics::Response::Error class
  • Loading branch information
mziwisky committed Jul 19, 2017
1 parent b0998b4 commit 7798bee
Show file tree
Hide file tree
Showing 28 changed files with 343 additions and 13 deletions.
2 changes: 2 additions & 0 deletions lib/speechmatics.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
require 'speechmatics/configuration'
require 'speechmatics/connection'
require 'speechmatics/response'
require 'speechmatics/response/error'
Gem.find_files('speechmatics/response/error/*.rb').each { |path| require path }
require 'speechmatics/api'
require 'speechmatics/api_factory'
require 'speechmatics/client'
Expand Down
2 changes: 1 addition & 1 deletion lib/speechmatics/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def request(method, path, params={}) # :nodoc:
request.body = params[:data]
end
end
Speechmatics::Response.new(response, {api: self, method: method, path: path, params: params})
Speechmatics::Response.parse(response, {api: self, method: method, path: path, params: params})
end

def base_path
Expand Down
21 changes: 9 additions & 12 deletions lib/speechmatics/response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,22 @@

module Speechmatics
class Response

def self.parse(response, request={})
new(response, request).tap do |res|
res.check_for_error
end
end

attr_accessor :raw, :request

def initialize(response, request={})
@raw = response
@request = request

check_for_error(response)
end

def check_for_error(response)
status_code_type = response.status.to_s[0]
case status_code_type
when "2"
# puts "all is well, status: #{response.status}"
when "4", "5"
raise "Whoops, error back from Speechmatics: #{response.status}"
else
raise "Unrecongized status code: #{response.status}"
end
def check_for_error
raise Error.classify(self) unless (200..299).include?(raw.status)
end

def body
Expand Down
46 changes: 46 additions & 0 deletions lib/speechmatics/response/error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# -*- encoding: utf-8 -*-

module Speechmatics
class Response
class Error < StandardError
TYPES = []

class << self
def inherited(subclass)
TYPES << subclass
end

def classify(response)
klass = TYPES.find { |t| t.matches?(response) } || Unknown
klass.new(response)
end

def matches?(response)
matches_status?(response.raw.status) &&
matches_message?(error_message(response))
end

def matches_status?(status)
self::ERROR_STATUS == status
end

def matches_message?(message)
self::ERROR_MESSAGE == message
end

def error_message(response)
response['error']
rescue
''
end
end

attr_reader :response

def initialize(response)
@response = response
super self.class.error_message(response)
end
end
end
end
8 changes: 8 additions & 0 deletions lib/speechmatics/response/error/bad_gateway.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- encoding: utf-8 -*-

class Speechmatics::Response::Error
class BadGateway < self
ERROR_STATUS = 502
def self.matches_message?(_message); true; end
end
end
8 changes: 8 additions & 0 deletions lib/speechmatics/response/error/cannot_align.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- encoding: utf-8 -*-

class Speechmatics::Response::Error
class CannotAlign < self
ERROR_STATUS = 404
ERROR_MESSAGE = 'Text could not be aligned to audio'
end
end
8 changes: 8 additions & 0 deletions lib/speechmatics/response/error/insufficient_credit.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- encoding: utf-8 -*-

class Speechmatics::Response::Error
class InsufficientCredit < self
ERROR_STATUS = 403
ERROR_MESSAGE = 'Insufficient Credit'
end
end
8 changes: 8 additions & 0 deletions lib/speechmatics/response/error/internal_server_error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- encoding: utf-8 -*-

class Speechmatics::Response::Error
class InternalServerError < self
ERROR_STATUS = 500
def self.matches_message?(_message); true; end
end
end
8 changes: 8 additions & 0 deletions lib/speechmatics/response/error/invalid_audio.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- encoding: utf-8 -*-

class Speechmatics::Response::Error
class InvalidAudio < self
ERROR_STATUS = 403
ERROR_MESSAGE = 'Job rejected due to invalid audio'
end
end
8 changes: 8 additions & 0 deletions lib/speechmatics/response/error/job_in_progress.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- encoding: utf-8 -*-

class Speechmatics::Response::Error
class JobInProgress < self
ERROR_STATUS = 404
ERROR_MESSAGE = 'Job In Progress'
end
end
8 changes: 8 additions & 0 deletions lib/speechmatics/response/error/job_not_found.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- encoding: utf-8 -*-

class Speechmatics::Response::Error
class JobNotFound < self
ERROR_STATUS = 404
ERROR_MESSAGE = 'Job not found'
end
end
8 changes: 8 additions & 0 deletions lib/speechmatics/response/error/malformed_request.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- encoding: utf-8 -*-

class Speechmatics::Response::Error
class MalformedRequest < self
ERROR_STATUS = 400
ERROR_MESSAGE = 'Malformed request'
end
end
8 changes: 8 additions & 0 deletions lib/speechmatics/response/error/missing_callback.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- encoding: utf-8 -*-

class Speechmatics::Response::Error
class MissingCallback < self
ERROR_STATUS = 400
ERROR_MESSAGE = 'Missing callback'
end
end
8 changes: 8 additions & 0 deletions lib/speechmatics/response/error/missing_data_file.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- encoding: utf-8 -*-

class Speechmatics::Response::Error
class MissingDataFile < self
ERROR_STATUS = 400
ERROR_MESSAGE = 'Missing data_file'
end
end
8 changes: 8 additions & 0 deletions lib/speechmatics/response/error/missing_language.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- encoding: utf-8 -*-

class Speechmatics::Response::Error
class MissingLanguage < self
ERROR_STATUS = 400
ERROR_MESSAGE = 'No language selected'
end
end
8 changes: 8 additions & 0 deletions lib/speechmatics/response/error/not_alignment_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- encoding: utf-8 -*-

class Speechmatics::Response::Error
class NotAlignmentJob < self
ERROR_STATUS = 404
ERROR_MESSAGE = 'Job is not of type alignment'
end
end
8 changes: 8 additions & 0 deletions lib/speechmatics/response/error/product_not_available.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- encoding: utf-8 -*-

class Speechmatics::Response::Error
class ProductNotAvailable < self
ERROR_STATUS = 400
ERROR_MESSAGE = 'Requested product not available'
end
end
8 changes: 8 additions & 0 deletions lib/speechmatics/response/error/rejected_on_submission.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- encoding: utf-8 -*-

class Speechmatics::Response::Error
class RejectedOnSubmission < self
ERROR_STATUS = 404
ERROR_MESSAGE = 'Job was rejected on submission'
end
end
8 changes: 8 additions & 0 deletions lib/speechmatics/response/error/service_unavailable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- encoding: utf-8 -*-

class Speechmatics::Response::Error
class ServiceUnavailable < self
ERROR_STATUS = 503
def self.matches_message?(_message); true; end
end
end
8 changes: 8 additions & 0 deletions lib/speechmatics/response/error/too_many_requests.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- encoding: utf-8 -*-

class Speechmatics::Response::Error
class TooManyRequests < self
ERROR_STATUS = 429
def self.matches_message?(_message); true; end
end
end
8 changes: 8 additions & 0 deletions lib/speechmatics/response/error/unauthorized.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- encoding: utf-8 -*-

class Speechmatics::Response::Error
class Unauthorized < self
ERROR_STATUS = 401
def self.matches_message?(_message); true; end
end
end
7 changes: 7 additions & 0 deletions lib/speechmatics/response/error/unknown.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# -*- encoding: utf-8 -*-

class Speechmatics::Response::Error
class Unknown < self
def self.matches?(_response); false; end
end
end
8 changes: 8 additions & 0 deletions lib/speechmatics/response/error/unsupported_output.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- encoding: utf-8 -*-

class Speechmatics::Response::Error
class UnsupportedOutputFormat < self
ERROR_STATUS = 404
ERROR_MESSAGE = 'Output format Not Supported'
end
end
8 changes: 8 additions & 0 deletions lib/speechmatics/response/error/unsupported_output_format.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- encoding: utf-8 -*-

class Speechmatics::Response::Error
class UnsupportedOutputFormat < self
ERROR_STATUS = 404
ERROR_MESSAGE = 'Output format Not Supported'
end
end
1 change: 1 addition & 0 deletions speechmatics.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Gem::Specification.new do |spec|

spec.add_development_dependency "bundler", "~> 1.3"
spec.add_development_dependency "rake"
spec.add_development_dependency "byebug"

spec.add_development_dependency('minitest')
spec.add_development_dependency('simplecov')
Expand Down
94 changes: 94 additions & 0 deletions test/response/error_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# -*- encoding: utf-8 -*-

require File.expand_path(File.dirname(__FILE__) + '/../test_helper')

describe Speechmatics::Response::Error do
described_class = Speechmatics::Response::Error

describe '::classify' do
# see error table at https://app.speechmatics.com/api-details#errorCodes

describe '4xx errors' do
tests = [{
response: {status: 400, error: 'Malformed request'},
error_class: described_class::MalformedRequest
},{
response: {status: 400, error: 'Missing data_file'},
error_class: described_class::MissingDataFile
},{
response: {status: 400, error: 'No language selected'},
error_class: described_class::MissingLanguage
},{
response: {status: 400, error: 'Missing callback'},
error_class: described_class::MissingCallback
},{
response: {status: 400, error: 'Requested product not available'},
error_class: described_class::ProductNotAvailable
},{
response: {status: 401, error: 'Invalid User Id or Token'},
error_class: described_class::Unauthorized
},{
response: {status: 403, error: 'Job rejected due to invalid audio'},
error_class: described_class::InvalidAudio
},{
response: {status: 403, error: 'Insufficient Credit'},
error_class: described_class::InsufficientCredit
},{
response: {status: 404, error: 'Job not found'},
error_class: described_class::JobNotFound
},{
response: {status: 404, error: 'Text could not be aligned to audio'},
error_class: described_class::CannotAlign
},{
response: {status: 404, error: 'Job was rejected on submission'},
error_class: described_class::RejectedOnSubmission
},{
response: {status: 404, error: 'Job is not of type alignment'},
error_class: described_class::NotAlignmentJob
},{
response: {status: 404, error: 'Job In Progress'},
error_class: described_class::JobInProgress
},{
response: {status: 404, error: 'Output format Not Supported'},
error_class: described_class::UnsupportedOutputFormat
},{
response: {status: 429, error: 'Too Many requests'},
error_class: described_class::TooManyRequests
}]

tests.each do |t|
it "correctly classifies #{t[:response][:status]}: #{t[:response][:error]}" do
raw_response = raw_response_stub(
t[:response][:status],
{ 'error' => t[:response][:error] }
)
response = Speechmatics::Response.new(raw_response)
err = described_class.classify(response)
err.class.must_equal t[:error_class]
end
end
end

describe '5xx errors' do
tests = [{
status: 500,
error_class: described_class::InternalServerError
},{
status: 502,
error_class: described_class::BadGateway
},{
status: 503,
error_class: described_class::ServiceUnavailable
}]

tests.each do |t|
it "correctly classifies status #{t[:status]}" do
raw_response = raw_response_stub(t[:status])
response = Speechmatics::Response.new(raw_response)
err = described_class.classify(response)
err.class.must_equal t[:error_class]
end
end
end
end
end
Loading

0 comments on commit 7798bee

Please sign in to comment.