22
33namespace Salient \Curler ;
44
5+ use Psr \Http \Client \ClientExceptionInterface ;
56use Psr \Http \Message \RequestInterface ;
67use Psr \Http \Message \ResponseInterface ;
78use Psr \Http \Message \UriInterface as PsrUriInterface ;
4142use Salient \Core \Utility \Str ;
4243use Salient \Curler \Exception \CurlErrorException ;
4344use Salient \Curler \Exception \HttpErrorException ;
45+ use Salient \Curler \Exception \NetworkException ;
46+ use Salient \Curler \Exception \RequestException ;
4447use Salient \Http \Exception \InvalidHeaderException ;
4548use Salient \Http \Exception \StreamEncapsulationException ;
4649use Salient \Http \HasHttpHeaders ;
5356use CurlHandle ;
5457use Generator ;
5558use Stringable ;
59+ use Throwable ;
5660
5761/**
5862 * An HTTP client optimised for RESTful APIs
@@ -116,6 +120,7 @@ class Curler implements CurlerInterface, Buildable
116120 protected bool $ ThrowHttpErrors ;
117121 protected ?RequestInterface $ LastRequest = null ;
118122 protected ?HttpResponseInterface $ LastResponse = null ;
123+ private ?Curler $ WithoutThrowHttpErrors = null ;
119124 private ?Closure $ Closure = null ;
120125
121126 private static string $ DefaultUserAgent ;
@@ -228,6 +233,7 @@ private function __clone()
228233 {
229234 $ this ->LastRequest = null ;
230235 $ this ->LastResponse = null ;
236+ $ this ->WithoutThrowHttpErrors = null ;
231237 $ this ->Closure = null ;
232238 }
233239
@@ -363,7 +369,7 @@ private function process(string $method, ?array $query, $data = false)
363369 $ request = $ pager ->getFirstRequest ($ request , $ this );
364370 }
365371
366- $ this ->sendRequest ($ request );
372+ $ this ->doSendRequest ($ request );
367373
368374 if ($ method === Method::HEAD ) {
369375 return $ this ->LastResponse ->getHttpHeaders ();
@@ -436,7 +442,7 @@ private function paginate(string $method, ?array $query, $data = false): Generat
436442 $ request = $ this ->Pager ->getFirstRequest ($ request , $ this );
437443 $ prev = null ;
438444 do {
439- $ this ->sendRequest ($ request );
445+ $ this ->doSendRequest ($ request );
440446 $ page = $ this ->Pager ->getPage (
441447 $ this ->getLastResponseBody (),
442448 $ request ,
@@ -494,7 +500,7 @@ private function processRaw(string $method, string $data, string $mediaType, ?ar
494500 {
495501 $ request = $ this ->createRequest ($ method , $ query , $ data );
496502 $ request = $ request ->withHeader (HttpHeader::CONTENT_TYPE , $ mediaType );
497- $ this ->sendRequest ($ request );
503+ $ this ->doSendRequest ($ request );
498504 return $ this ->getLastResponseBody ();
499505 }
500506
@@ -966,15 +972,45 @@ public function withThrowHttpErrors(bool $throw = true)
966972
967973 /**
968974 * @inheritDoc
969- *
975+ */
976+ public function sendRequest (RequestInterface $ request ): HttpResponseInterface
977+ {
978+ // PSR-18: "A Client MUST NOT treat a well-formed HTTP request or HTTP
979+ // response as an error condition. For example, response status codes in
980+ // the 400 and 500 range MUST NOT cause an exception and MUST be
981+ // returned to the Calling Library as normal."
982+ $ curler = $ this ->WithoutThrowHttpErrors
983+ ??= $ this ->withThrowHttpErrors (false );
984+ try {
985+ try {
986+ return $ curler ->doSendRequest ($ request );
987+ } finally {
988+ $ this ->LastRequest = $ curler ->LastRequest ;
989+ $ this ->LastResponse = $ curler ->LastResponse ;
990+ }
991+ } catch (ClientExceptionInterface $ ex ) {
992+ throw $ ex ;
993+ } catch (CurlErrorException $ ex ) {
994+ throw $ ex ->isNetworkError ()
995+ ? new NetworkException ($ ex ->getMessage (), $ this ->LastRequest ?? $ request , [], $ ex )
996+ : new RequestException ($ ex ->getMessage (), $ this ->LastRequest ?? $ request , [], $ ex );
997+ } catch (Throwable $ ex ) {
998+ throw new RequestException ($ ex ->getMessage (), $ this ->LastRequest ?? $ request , [], $ ex );
999+ }
1000+ }
1001+
1002+ /**
9701003 * @phpstan-assert !null $this->LastRequest
9711004 * @phpstan-assert !null $this->LastResponse
9721005 */
973- public function sendRequest (RequestInterface $ request ): HttpResponseInterface
1006+ private function doSendRequest (RequestInterface $ request ): HttpResponseInterface
9741007 {
1008+ $ this ->LastRequest = null ;
1009+ $ this ->LastResponse = null ;
1010+
9751011 return $ this ->Middleware
9761012 ? ($ this ->Closure ??= $ this ->getClosure ())($ request )
977- : $ this ->doSendRequest ($ request );
1013+ : $ this ->getResponse ($ request );
9781014 }
9791015
9801016 /**
@@ -983,7 +1019,7 @@ public function sendRequest(RequestInterface $request): HttpResponseInterface
9831019 private function getClosure (): Closure
9841020 {
9851021 $ closure = fn (RequestInterface $ request ): HttpResponseInterface =>
986- $ this ->doSendRequest ($ request );
1022+ $ this ->getResponse ($ request );
9871023
9881024 foreach (array_reverse ($ this ->Middleware ) as [$ middleware ]) {
9891025 $ closure = $ middleware instanceof CurlerMiddlewareInterface
@@ -999,7 +1035,7 @@ private function getClosure(): Closure
9991035 return $ closure ;
10001036 }
10011037
1002- private function doSendRequest (RequestInterface $ request ): HttpResponseInterface
1038+ private function getResponse (RequestInterface $ request ): HttpResponseInterface
10031039 {
10041040 $ uri = $ request ->getUri ()->withFragment ('' );
10051041 $ request = $ request ->withUri ($ uri );
0 commit comments