1- package com .sap .ai .sdk .orchestration ;
1+ package com .sap .ai .sdk .core . commons ;
22
3- import static com .sap .ai .sdk .orchestration . OrchestrationClient . JACKSON ;
3+ import static com .sap .ai .sdk .core . JacksonConfiguration . getDefaultObjectMapper ;
44
55import com .fasterxml .jackson .core .JsonProcessingException ;
6- import com .sap .ai .sdk .orchestration .model .ErrorResponse ;
6+ import com .fasterxml .jackson .databind .ObjectMapper ;
7+ import com .google .common .annotations .Beta ;
78import io .vavr .control .Try ;
89import java .io .IOException ;
910import java .nio .charset .StandardCharsets ;
11+ import java .util .Objects ;
12+ import java .util .function .BiFunction ;
1013import javax .annotation .Nonnull ;
1114import lombok .RequiredArgsConstructor ;
1215import lombok .extern .slf4j .Slf4j ;
1821import org .apache .hc .core5 .http .io .HttpClientResponseHandler ;
1922import org .apache .hc .core5 .http .io .entity .EntityUtils ;
2023
24+ /**
25+ * Parse incoming JSON responses and handles any errors. For internal use only.
26+ *
27+ * @param <T> The type of the response.
28+ * @param <E> The type of the exception to throw.
29+ * @since 1.1.0
30+ */
31+ @ Beta
2132@ Slf4j
2233@ RequiredArgsConstructor
23- class OrchestrationResponseHandler <T > implements HttpClientResponseHandler <T > {
34+ public class ClientResponseHandler <T , E extends ClientException >
35+ implements HttpClientResponseHandler <T > {
2436 @ Nonnull private final Class <T > responseType ;
37+ @ Nonnull private final Class <? extends ClientError > errorType ;
38+ @ Nonnull private final BiFunction <String , Throwable , E > exceptionType ;
2539
40+ /** The parses for JSON responses, will be private once we can remove mixins */
41+ @ Nonnull public ObjectMapper JACKSON = getDefaultObjectMapper ();
42+
43+ /**
44+ * Processes a {@link ClassicHttpResponse} and returns some value corresponding to that response.
45+ *
46+ * @param response The response to process
47+ * @return A model class instantiated from the response
48+ * @throws E in case of a problem or the connection was aborted
49+ */
50+ @ Nonnull
51+ @ Override
52+ public T handleResponse (@ Nonnull final ClassicHttpResponse response ) throws E {
53+ if (response .getCode () >= 300 ) {
54+ buildExceptionAndThrow (response );
55+ }
56+ return parseResponse (response );
57+ }
58+
59+ // The InputStream of the HTTP entity is closed by EntityUtils.toString
60+ @ SuppressWarnings ("PMD.CloseResource" )
2661 @ Nonnull
27- private static String getContent (@ Nonnull final HttpEntity entity ) {
62+ private T parseResponse (@ Nonnull final ClassicHttpResponse response ) throws E {
63+ final HttpEntity responseEntity = response .getEntity ();
64+ if (responseEntity == null ) {
65+ throw exceptionType .apply ("Response was empty." , null );
66+ }
67+ val content = getContent (responseEntity );
68+ log .debug ("Parsing response from JSON response: {}" , content );
69+ try {
70+ return JACKSON .readValue (content , responseType );
71+ } catch (final JsonProcessingException e ) {
72+ log .error ("Failed to parse the following response: {}" , content );
73+ throw exceptionType .apply ("Failed to parse response" , e );
74+ }
75+ }
76+
77+ @ Nonnull
78+ private String getContent (@ Nonnull final HttpEntity entity ) {
2879 try {
2980 return EntityUtils .toString (entity , StandardCharsets .UTF_8 );
3081 } catch (IOException | ParseException e ) {
31- throw new OrchestrationClientException ("Failed to read response content." , e );
82+ throw exceptionType . apply ("Failed to read response content." , e );
3283 }
3384 }
3485
35- // The InputStream of the HTTP entity is closed by EntityUtils.toString
86+ /**
87+ * Parse the error response and throw an exception.
88+ *
89+ * @param response The response to process
90+ */
3691 @ SuppressWarnings ("PMD.CloseResource" )
37- static void buildExceptionAndThrow (@ Nonnull final ClassicHttpResponse response )
38- throws OrchestrationClientException {
92+ public void buildExceptionAndThrow (@ Nonnull final ClassicHttpResponse response ) throws E {
3993 val exception =
40- new OrchestrationClientException (
41- "Request to orchestration service failed with status %s %s"
42- .formatted (response .getCode (), response .getReasonPhrase ()));
94+ exceptionType .apply (
95+ "Request failed with status %s %s"
96+ .formatted (response .getCode (), response .getReasonPhrase ()),
97+ null );
4398 val entity = response .getEntity ();
4499 if (entity == null ) {
45100 throw exception ;
@@ -54,9 +109,7 @@ static void buildExceptionAndThrow(@Nonnull final ClassicHttpResponse response)
54109 throw exception ;
55110 }
56111
57- log .error (
58- "The orchestration service responded with an HTTP error and the following content: {}" ,
59- content );
112+ log .error ("The service responded with an HTTP error and the following content: {}" , content );
60113 val contentType = ContentType .parse (entity .getContentType ());
61114 if (!ContentType .APPLICATION_JSON .isSameMimeType (contentType )) {
62115 throw exception ;
@@ -68,59 +121,19 @@ static void buildExceptionAndThrow(@Nonnull final ClassicHttpResponse response)
68121 /**
69122 * Parse the error response and throw an exception.
70123 *
71- * @param errorResponse the error response, most likely a JSON of {@link ErrorResponse} .
124+ * @param errorResponse the error response, most likely a unique JSON class .
72125 * @param baseException a base exception to add the error message to.
73126 */
74- static void parseErrorAndThrow (
75- @ Nonnull final String errorResponse ,
76- @ Nonnull final OrchestrationClientException baseException )
77- throws OrchestrationClientException {
78- val maybeError = Try .of (() -> JACKSON .readValue (errorResponse , ErrorResponse .class ));
127+ public void parseErrorAndThrow (
128+ @ Nonnull final String errorResponse , @ Nonnull final E baseException ) throws E {
129+ val maybeError = Try .of (() -> JACKSON .readValue (errorResponse , errorType ));
79130 if (maybeError .isFailure ()) {
80131 baseException .addSuppressed (maybeError .getCause ());
81132 throw baseException ;
82133 }
83134
84- throw new OrchestrationClientException (
85- "%s and error message: '%s'"
86- .formatted (baseException .getMessage (), maybeError .get ().getMessage ()));
87- }
88-
89- /**
90- * Processes a {@link ClassicHttpResponse} and returns some value corresponding to that response.
91- *
92- * @param response The response to process
93- * @return A model class instantiated from the response
94- * @throws OrchestrationClientException in case of a problem or the connection was aborted
95- */
96- @ Override
97- public T handleResponse (@ Nonnull final ClassicHttpResponse response )
98- throws OrchestrationClientException {
99- if (response .getCode () >= 300 ) {
100- buildExceptionAndThrow (response );
101- }
102- val result = parseResponse (response );
103- log .debug ("Received the following response from orchestration service: {}" , result );
104- return result ;
105- }
106-
107- // The InputStream of the HTTP entity is closed by EntityUtils.toString
108- @ SuppressWarnings ("PMD.CloseResource" )
109- @ Nonnull
110- private T parseResponse (@ Nonnull final ClassicHttpResponse response )
111- throws OrchestrationClientException {
112- final HttpEntity responseEntity = response .getEntity ();
113- if (responseEntity == null ) {
114- throw new OrchestrationClientException ("Response from Orchestration service was empty." );
115- }
116- val content = getContent (responseEntity );
117- log .debug ("Parsing response from JSON response: {}" , content );
118- try {
119- return JACKSON .readValue (content , responseType );
120- } catch (final JsonProcessingException e ) {
121- log .error ("Failed to parse the following response from orchestration service: {}" , content );
122- throw new OrchestrationClientException (
123- "Failed to parse response from orchestration service" , e );
124- }
135+ val error = Objects .requireNonNullElse (maybeError .get ().getMessage (), "" );
136+ val message = "%s and error message: '%s'" .formatted (baseException .getMessage (), error );
137+ throw exceptionType .apply (message , baseException );
125138 }
126139}
0 commit comments