diff --git a/CLAUDE.md b/CLAUDE.md index 10cab81..efb5c79 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -38,7 +38,10 @@ The SDK has two independent subsystems sharing a common error hierarchy: Error ├── HttpRequestError (has status_code, response_body) │ ├── ValidationError (422, has error_type) -│ └── ClientError (400) +│ ├── ClientError (400) +│ ├── AuthenticationError (401/403) +│ └── RateLimitError (429, has retry_after) +├── ConnectionError (has original_exception) ├── TimeoutError └── WebhookVerificationError ├── InvalidSignatureError diff --git a/lib/lettermint/http_client.rb b/lib/lettermint/http_client.rb index b148a2e..6863bb7 100644 --- a/lib/lettermint/http_client.rb +++ b/lib/lettermint/http_client.rb @@ -62,8 +62,12 @@ def with_error_handling handle_response(response) rescue Faraday::TimeoutError, Timeout::Error raise Lettermint::TimeoutError, "Request timeout after #{@connection.options.timeout}s" + rescue Faraday::SSLError => e + raise Lettermint::ConnectionError.new(message: "SSL error: #{e.message}", original_exception: e) rescue Faraday::ConnectionFailed => e raise Lettermint::ConnectionError.new(message: e.message, original_exception: e) + rescue Faraday::ParsingError => e + raise Lettermint::Error, "Failed to parse API response: #{e.message}" end def handle_response(response) diff --git a/spec/lettermint/http_client_spec.rb b/spec/lettermint/http_client_spec.rb index d887ba3..2b2f398 100644 --- a/spec/lettermint/http_client_spec.rb +++ b/spec/lettermint/http_client_spec.rb @@ -195,6 +195,38 @@ expect(e.original_exception).to be_a(Faraday::ConnectionFailed) } end + + it 'raises HttpRequestError on non-JSON error response body' do + stub_request(:post, "#{base_url}/send") + .to_return( + status: 500, + body: 'Internal Server Error', + headers: { 'Content-Type' => 'text/plain' } + ) + + expect { client.post(path: '/send', data: {}) } + .to raise_error(Lettermint::HttpRequestError) { |e| + expect(e.status_code).to eq(500) + expect(e.response_body).to be_nil + expect(e.message).to eq('HTTP 500') + } + end + + it 'raises ConnectionError on SSL failure' do + stub_request(:post, "#{base_url}/send").to_raise(Faraday::SSLError.new('certificate verify failed')) + + expect { client.post(path: '/send', data: {}) } + .to raise_error(Lettermint::ConnectionError, /SSL error:.*certificate verify failed/) { |e| + expect(e.original_exception).to be_a(Faraday::SSLError) + } + end + + it 'raises Error on JSON parsing failure' do + stub_request(:post, "#{base_url}/send").to_raise(Faraday::ParsingError.new('unexpected token')) + + expect { client.post(path: '/send', data: {}) } + .to raise_error(Lettermint::Error, /unexpected token|parsing/i) + end end describe 'custom base URL' do