|
15 | 15 | import java.util.regex.Pattern; |
16 | 16 |
|
17 | 17 | import jakarta.annotation.Priority; |
18 | | -import jakarta.ws.rs.ConstrainedTo; |
19 | 18 | import jakarta.ws.rs.Consumes; |
20 | 19 | import jakarta.ws.rs.POST; |
21 | 20 | import jakarta.ws.rs.Path; |
22 | 21 | import jakarta.ws.rs.Priorities; |
23 | 22 | import jakarta.ws.rs.Produces; |
24 | | -import jakarta.ws.rs.RuntimeType; |
25 | 23 | import jakarta.ws.rs.WebApplicationException; |
26 | 24 | import jakarta.ws.rs.core.HttpHeaders; |
27 | 25 | import jakarta.ws.rs.core.MediaType; |
28 | 26 | import jakarta.ws.rs.core.MultivaluedMap; |
29 | 27 | import jakarta.ws.rs.core.Response; |
30 | 28 | import jakarta.ws.rs.ext.MessageBodyWriter; |
31 | | -import jakarta.ws.rs.ext.Provider; |
32 | 29 | import jakarta.ws.rs.ext.ReaderInterceptor; |
33 | 30 | import jakarta.ws.rs.ext.ReaderInterceptorContext; |
34 | 31 | import jakarta.ws.rs.ext.WriterInterceptor; |
35 | 32 | import jakarta.ws.rs.ext.WriterInterceptorContext; |
36 | 33 |
|
37 | 34 | import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam; |
| 35 | +import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; |
38 | 36 | import org.jboss.logging.Logger; |
39 | 37 | import org.jboss.resteasy.reactive.RestQuery; |
40 | 38 | import org.jboss.resteasy.reactive.RestStreamElementType; |
|
76 | 74 | @ClientHeaderParam(name = "api-key", value = "{apiKey}") // used by AzureAI |
77 | 75 | @Consumes(MediaType.APPLICATION_JSON) |
78 | 76 | @Produces(MediaType.APPLICATION_JSON) |
| 77 | +@RegisterProvider(OpenAiRestApi.OpenAiRestApiJacksonReader.class) |
| 78 | +@RegisterProvider(OpenAiRestApi.OpenAiRestApiJacksonWriter.class) |
| 79 | +@RegisterProvider(OpenAiRestApi.OpenAiRestApiReaderInterceptor.class) |
| 80 | +@RegisterProvider(OpenAiRestApi.OpenAiRestApiWriterInterceptor.class) |
79 | 81 | public interface OpenAiRestApi { |
80 | 82 |
|
81 | 83 | /** |
@@ -181,57 +183,58 @@ public boolean test(SseEvent<String> event) { |
181 | 183 | } |
182 | 184 | } |
183 | 185 |
|
184 | | - /** |
185 | | - * We need a custom version of the Jackson provider because reading SSE values does not work properly with |
186 | | - * {@code @ClientObjectMapper} due to the lack of a complete context in those requests |
187 | | - */ |
188 | | - @Provider |
189 | | - @ConstrainedTo(RuntimeType.CLIENT) |
190 | | - @Priority(Priorities.USER + 100) |
191 | | - class OpenAiRestApiJacksonProvider extends AbstractJsonMessageBodyReader implements MessageBodyWriter<Object> { |
| 186 | + @Priority(Priorities.USER + 100) // this priority ensures that our Writer has priority over the standard Jackson one |
| 187 | + class OpenAiRestApiJacksonWriter implements MessageBodyWriter<Object> { |
192 | 188 |
|
193 | | - /** |
194 | | - * Normally this is not necessary, but if one uses the 'demo' key, then the response comes back as type text/html |
195 | | - * but the content is still JSON. Go figure... |
196 | | - */ |
197 | 189 | @Override |
198 | | - public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { |
| 190 | + public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { |
199 | 191 | return true; |
200 | 192 | } |
201 | 193 |
|
202 | 194 | @Override |
203 | | - public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType, |
204 | | - MultivaluedMap<String, String> httpHeaders, InputStream entityStream) |
| 195 | + public void writeTo(Object o, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, |
| 196 | + MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) |
205 | 197 | throws IOException, WebApplicationException { |
206 | | - return ObjectMapperHolder.READER |
207 | | - .forType(ObjectMapperHolder.READER.getTypeFactory().constructType(genericType != null ? genericType : type)) |
208 | | - .readValue(entityStream); |
| 198 | + entityStream.write(ObjectMapperHolder.MAPPER.writeValueAsString(o).getBytes(StandardCharsets.UTF_8)); |
209 | 199 | } |
| 200 | + } |
| 201 | + |
| 202 | + @Priority(Priorities.USER - 100) // this priority ensures that our Reader has priority over the standard Jackson one |
| 203 | + class OpenAiRestApiJacksonReader extends AbstractJsonMessageBodyReader { |
210 | 204 |
|
| 205 | + /** |
| 206 | + * Normally this is not necessary, but if one uses the 'demo' Langchain4j key, then the response comes back as type |
| 207 | + * text/html |
| 208 | + * but the content is still JSON. |
| 209 | + */ |
211 | 210 | @Override |
212 | | - public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { |
| 211 | + public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { |
213 | 212 | return true; |
214 | 213 | } |
215 | 214 |
|
| 215 | + /** |
| 216 | + * We need a custom version of the Jackson provider because reading SSE values does not work properly with |
| 217 | + * {@code @ClientObjectMapper} due to the lack of a complete context in those requests |
| 218 | + */ |
216 | 219 | @Override |
217 | | - public void writeTo(Object o, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, |
218 | | - MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) |
| 220 | + public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType, |
| 221 | + MultivaluedMap<String, String> httpHeaders, InputStream entityStream) |
219 | 222 | throws IOException, WebApplicationException { |
220 | | - entityStream.write(ObjectMapperHolder.MAPPER.writeValueAsString(o).getBytes(StandardCharsets.UTF_8)); |
| 223 | + return ObjectMapperHolder.READER |
| 224 | + .forType(ObjectMapperHolder.READER.getTypeFactory().constructType(genericType != null ? genericType : type)) |
| 225 | + .readValue(entityStream); |
221 | 226 | } |
| 227 | + } |
222 | 228 |
|
223 | | - public static class ObjectMapperHolder { |
224 | | - public static final ObjectMapper MAPPER = QuarkusJsonCodecFactory.SnakeCaseObjectMapperHolder.MAPPER; |
| 229 | + public class ObjectMapperHolder { |
| 230 | + public static final ObjectMapper MAPPER = QuarkusJsonCodecFactory.SnakeCaseObjectMapperHolder.MAPPER; |
225 | 231 |
|
226 | | - private static final ObjectReader READER = MAPPER.reader(); |
227 | | - } |
| 232 | + private static final ObjectReader READER = MAPPER.reader(); |
228 | 233 | } |
229 | 234 |
|
230 | 235 | /** |
231 | 236 | * This method validates that the response is not empty, which happens when the API returns an error object |
232 | 237 | */ |
233 | | - @Provider |
234 | | - @ConstrainedTo(RuntimeType.CLIENT) |
235 | 238 | class OpenAiRestApiReaderInterceptor implements ReaderInterceptor { |
236 | 239 |
|
237 | 240 | @Override |
@@ -272,8 +275,6 @@ private Object validateResponse(Object result) { |
272 | 275 | * The point of this is to properly set the {@code stream} value of the request |
273 | 276 | * so users don't have to remember to set it manually |
274 | 277 | */ |
275 | | - @Provider |
276 | | - @ConstrainedTo(RuntimeType.CLIENT) |
277 | 278 | class OpenAiRestApiWriterInterceptor implements WriterInterceptor { |
278 | 279 | @Override |
279 | 280 | public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException { |
|
0 commit comments