diff --git a/lib/speechmatics.rb b/lib/speechmatics.rb index 2e193a7..8e8909e 100644 --- a/lib/speechmatics.rb +++ b/lib/speechmatics.rb @@ -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' diff --git a/lib/speechmatics/api.rb b/lib/speechmatics/api.rb index f7ce758..f2dc4fd 100644 --- a/lib/speechmatics/api.rb +++ b/lib/speechmatics/api.rb @@ -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 diff --git a/lib/speechmatics/response.rb b/lib/speechmatics/response.rb index b6228df..bfd0cfa 100644 --- a/lib/speechmatics/response.rb +++ b/lib/speechmatics/response.rb @@ -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 diff --git a/lib/speechmatics/response/error.rb b/lib/speechmatics/response/error.rb new file mode 100644 index 0000000..9651024 --- /dev/null +++ b/lib/speechmatics/response/error.rb @@ -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 diff --git a/lib/speechmatics/response/error/bad_gateway.rb b/lib/speechmatics/response/error/bad_gateway.rb new file mode 100644 index 0000000..cacb566 --- /dev/null +++ b/lib/speechmatics/response/error/bad_gateway.rb @@ -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 diff --git a/lib/speechmatics/response/error/cannot_align.rb b/lib/speechmatics/response/error/cannot_align.rb new file mode 100644 index 0000000..57438b1 --- /dev/null +++ b/lib/speechmatics/response/error/cannot_align.rb @@ -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 diff --git a/lib/speechmatics/response/error/insufficient_credit.rb b/lib/speechmatics/response/error/insufficient_credit.rb new file mode 100644 index 0000000..32236c5 --- /dev/null +++ b/lib/speechmatics/response/error/insufficient_credit.rb @@ -0,0 +1,8 @@ +# -*- encoding: utf-8 -*- + +class Speechmatics::Response::Error + class InsufficientCredit < self + ERROR_STATUS = 403 + ERROR_MESSAGE = 'Insufficient Credit' + end +end diff --git a/lib/speechmatics/response/error/internal_server_error.rb b/lib/speechmatics/response/error/internal_server_error.rb new file mode 100644 index 0000000..b6edb8a --- /dev/null +++ b/lib/speechmatics/response/error/internal_server_error.rb @@ -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 diff --git a/lib/speechmatics/response/error/invalid_audio.rb b/lib/speechmatics/response/error/invalid_audio.rb new file mode 100644 index 0000000..b1f6458 --- /dev/null +++ b/lib/speechmatics/response/error/invalid_audio.rb @@ -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 diff --git a/lib/speechmatics/response/error/job_in_progress.rb b/lib/speechmatics/response/error/job_in_progress.rb new file mode 100644 index 0000000..1b51eb7 --- /dev/null +++ b/lib/speechmatics/response/error/job_in_progress.rb @@ -0,0 +1,8 @@ +# -*- encoding: utf-8 -*- + +class Speechmatics::Response::Error + class JobInProgress < self + ERROR_STATUS = 404 + ERROR_MESSAGE = 'Job In Progress' + end +end diff --git a/lib/speechmatics/response/error/job_not_found.rb b/lib/speechmatics/response/error/job_not_found.rb new file mode 100644 index 0000000..58571b6 --- /dev/null +++ b/lib/speechmatics/response/error/job_not_found.rb @@ -0,0 +1,8 @@ +# -*- encoding: utf-8 -*- + +class Speechmatics::Response::Error + class JobNotFound < self + ERROR_STATUS = 404 + ERROR_MESSAGE = 'Job not found' + end +end diff --git a/lib/speechmatics/response/error/malformed_request.rb b/lib/speechmatics/response/error/malformed_request.rb new file mode 100644 index 0000000..e98d3c5 --- /dev/null +++ b/lib/speechmatics/response/error/malformed_request.rb @@ -0,0 +1,8 @@ +# -*- encoding: utf-8 -*- + +class Speechmatics::Response::Error + class MalformedRequest < self + ERROR_STATUS = 400 + ERROR_MESSAGE = 'Malformed request' + end +end diff --git a/lib/speechmatics/response/error/missing_callback.rb b/lib/speechmatics/response/error/missing_callback.rb new file mode 100644 index 0000000..39d1020 --- /dev/null +++ b/lib/speechmatics/response/error/missing_callback.rb @@ -0,0 +1,8 @@ +# -*- encoding: utf-8 -*- + +class Speechmatics::Response::Error + class MissingCallback < self + ERROR_STATUS = 400 + ERROR_MESSAGE = 'Missing callback' + end +end diff --git a/lib/speechmatics/response/error/missing_data_file.rb b/lib/speechmatics/response/error/missing_data_file.rb new file mode 100644 index 0000000..92c5779 --- /dev/null +++ b/lib/speechmatics/response/error/missing_data_file.rb @@ -0,0 +1,8 @@ +# -*- encoding: utf-8 -*- + +class Speechmatics::Response::Error + class MissingDataFile < self + ERROR_STATUS = 400 + ERROR_MESSAGE = 'Missing data_file' + end +end diff --git a/lib/speechmatics/response/error/missing_language.rb b/lib/speechmatics/response/error/missing_language.rb new file mode 100644 index 0000000..c28cf2c --- /dev/null +++ b/lib/speechmatics/response/error/missing_language.rb @@ -0,0 +1,8 @@ +# -*- encoding: utf-8 -*- + +class Speechmatics::Response::Error + class MissingLanguage < self + ERROR_STATUS = 400 + ERROR_MESSAGE = 'No language selected' + end +end diff --git a/lib/speechmatics/response/error/not_alignment_job.rb b/lib/speechmatics/response/error/not_alignment_job.rb new file mode 100644 index 0000000..ca909f1 --- /dev/null +++ b/lib/speechmatics/response/error/not_alignment_job.rb @@ -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 diff --git a/lib/speechmatics/response/error/product_not_available.rb b/lib/speechmatics/response/error/product_not_available.rb new file mode 100644 index 0000000..b9a8323 --- /dev/null +++ b/lib/speechmatics/response/error/product_not_available.rb @@ -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 diff --git a/lib/speechmatics/response/error/rejected_on_submission.rb b/lib/speechmatics/response/error/rejected_on_submission.rb new file mode 100644 index 0000000..d8e075d --- /dev/null +++ b/lib/speechmatics/response/error/rejected_on_submission.rb @@ -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 diff --git a/lib/speechmatics/response/error/service_unavailable.rb b/lib/speechmatics/response/error/service_unavailable.rb new file mode 100644 index 0000000..9df8248 --- /dev/null +++ b/lib/speechmatics/response/error/service_unavailable.rb @@ -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 diff --git a/lib/speechmatics/response/error/too_many_requests.rb b/lib/speechmatics/response/error/too_many_requests.rb new file mode 100644 index 0000000..b2f4b2d --- /dev/null +++ b/lib/speechmatics/response/error/too_many_requests.rb @@ -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 diff --git a/lib/speechmatics/response/error/unauthorized.rb b/lib/speechmatics/response/error/unauthorized.rb new file mode 100644 index 0000000..d7ba4a1 --- /dev/null +++ b/lib/speechmatics/response/error/unauthorized.rb @@ -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 diff --git a/lib/speechmatics/response/error/unknown.rb b/lib/speechmatics/response/error/unknown.rb new file mode 100644 index 0000000..454af40 --- /dev/null +++ b/lib/speechmatics/response/error/unknown.rb @@ -0,0 +1,7 @@ +# -*- encoding: utf-8 -*- + +class Speechmatics::Response::Error + class Unknown < self + def self.matches?(_response); false; end + end +end diff --git a/lib/speechmatics/response/error/unsupported_output.rb b/lib/speechmatics/response/error/unsupported_output.rb new file mode 100644 index 0000000..4e9beac --- /dev/null +++ b/lib/speechmatics/response/error/unsupported_output.rb @@ -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 diff --git a/lib/speechmatics/response/error/unsupported_output_format.rb b/lib/speechmatics/response/error/unsupported_output_format.rb new file mode 100644 index 0000000..4e9beac --- /dev/null +++ b/lib/speechmatics/response/error/unsupported_output_format.rb @@ -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 diff --git a/speechmatics.gemspec b/speechmatics.gemspec index 1747321..61d9394 100644 --- a/speechmatics.gemspec +++ b/speechmatics.gemspec @@ -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') diff --git a/test/response/error_test.rb b/test/response/error_test.rb new file mode 100644 index 0000000..0619807 --- /dev/null +++ b/test/response/error_test.rb @@ -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 diff --git a/test/response_test.rb b/test/response_test.rb new file mode 100644 index 0000000..83dbbbb --- /dev/null +++ b/test/response_test.rb @@ -0,0 +1,27 @@ +# -*- encoding: utf-8 -*- + +require File.expand_path(File.dirname(__FILE__) + '/test_helper') + +describe Speechmatics::Response do + describe '::parse' do + describe 'with an error response' do + let(:response) { raw_response_stub(422) } + + it 'raises a Speechmatics::Response::Error' do + lambda { + Speechmatics::Response.parse(response) + }.must_raise Speechmatics::Response::Error + end + end + + describe 'with an successful response' do + let(:response) { raw_response_stub(200, 'good stuff') } + + it 'returns a Response object' do + res = Speechmatics::Response.parse(response) + res.class.must_equal Speechmatics::Response + res.body.must_equal 'good stuff' + end + end + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 8af2801..a9cfc26 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -9,3 +9,7 @@ require 'minitest/spec' require 'speechmatics' + +def raw_response_stub(status, body = '') + Struct.new(:status, :body).new(status, body) +end