diff --git a/generators/java/generator-utils/src/main/java/com/fern/java/AbstractPoetClassNameFactory.java b/generators/java/generator-utils/src/main/java/com/fern/java/AbstractPoetClassNameFactory.java index 3cde09df856..f3d07c626df 100644 --- a/generators/java/generator-utils/src/main/java/com/fern/java/AbstractPoetClassNameFactory.java +++ b/generators/java/generator-utils/src/main/java/com/fern/java/AbstractPoetClassNameFactory.java @@ -4,6 +4,8 @@ import com.fern.ir.model.types.DeclaredTypeName; import com.fern.java.utils.KeyWordUtils; import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.TypeVariableName; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -69,6 +71,14 @@ public final ClassName getStreamClassName() { return ClassName.get(getCorePackage(), "Stream"); } + public final ClassName getStreamOptionsClassName() { + return ClassName.get(getCorePackage(), "Stream.Options"); + } + + public final ClassName getStreamTypeClassName() { + return ClassName.get(getCorePackage(), "Stream.StreamType"); + } + public final ClassName getQueryStringMapperClassName() { return ClassName.get(getCorePackage(), "QueryStringMapper"); } diff --git a/generators/java/generator-utils/src/main/resources/Stream.java b/generators/java/generator-utils/src/main/resources/Stream.java index 554736ee768..c05e1019d49 100644 --- a/generators/java/generator-utils/src/main/resources/Stream.java +++ b/generators/java/generator-utils/src/main/resources/Stream.java @@ -1,6 +1,9 @@ +import org.jetbrains.annotations.NotNull; + +import java.io.Reader; import java.util.Iterator; import java.util.NoSuchElementException; -import java.io.Reader; +import java.util.Objects; import java.util.Scanner; /** @@ -22,6 +25,21 @@ public final class Stream implements Iterable { */ private final Scanner scanner; + private Options options; + + private StreamType streamType; + + /** + * The type of stream to be interpreted. + */ + public enum StreamType { + SSE, NDJSON + } + + private String DEFAULT_VALUE_PREFIX = "data: "; + private String DEFAULT_VALUE_TERMINATOR = "[DONE]"; + private String DEFAULT_VALUE_DELIMITER = "\n"; + /** * Constructs a new {@code Stream} with the specified value type, reader, and delimiter. * @@ -34,6 +52,36 @@ public Stream(Class valueType, Reader reader, String delimiter) { this.valueType = valueType; } + /** + * Constructs a new {@code Stream} with the specified value type, reader, and delimiter. + * + * @param valueType The class of the objects in the stream. + * @param reader The reader that provides the streamed data. + * @param options The options that determine how the stream is to be interpreted and contains prefix, delimiter, and terminator values. + * @param streamType The type of stream to be interpreted. The value here should come from {@link StreamType} + * @see StreamType + * @see Options + */ + public Stream(Class valueType, Reader reader, Options options, StreamType streamType) { + this.streamType = Objects.requireNonNullElse(streamType, StreamType.NDJSON); + this.options = Objects.requireNonNullElse(options, new Options(DEFAULT_VALUE_PREFIX, DEFAULT_VALUE_DELIMITER, DEFAULT_VALUE_TERMINATOR)); + this.scanner = new Scanner(reader).useDelimiter(this.options.delimiter); + this.valueType = valueType; + + } + + public static class Options { + public @NotNull String prefix; + public @NotNull String delimiter; + public @NotNull String terminator; + + public Options(@NotNull String prefix, @NotNull String delimiter, @NotNull String terminator) { + this.prefix = prefix; + this.delimiter = delimiter; + this.terminator = terminator; + } + } + /** * Returns an iterator over the elements in this stream that blocks during iteration when the next object is * not yet available. @@ -65,16 +113,20 @@ public boolean hasNext() { */ @Override public T next() { - if (!scanner.hasNext()) { - throw new NoSuchElementException(); - } else { - try { - T parsedResponse = - ObjectMappers.JSON_MAPPER.readValue(scanner.next().trim(), valueType); - return parsedResponse; - } catch (Exception e) { - throw new RuntimeException(e); + if (!scanner.hasNext()) throw new NoSuchElementException(); + + try { + String line = scanner.next(); + if (streamType == StreamType.SSE) { + while (!line.startsWith(options.prefix)) line = scanner.next(); + if (line.equals(options.terminator)) throw new NoSuchElementException(); + line = line.substring(options.prefix.length()); + } else { + line = line.trim(); } + return ObjectMappers.JSON_MAPPER.readValue(line, valueType); + } catch (Exception e) { + throw new RuntimeException(e); } } @@ -89,4 +141,4 @@ public void remove() { } }; } -} \ No newline at end of file +} diff --git a/generators/java/sdk/src/main/java/com/fern/java/client/generators/endpoint/AbstractHttpResponseParserGenerator.java b/generators/java/sdk/src/main/java/com/fern/java/client/generators/endpoint/AbstractHttpResponseParserGenerator.java index ec3fb2d1c46..5f706e3b7ba 100644 --- a/generators/java/sdk/src/main/java/com/fern/java/client/generators/endpoint/AbstractHttpResponseParserGenerator.java +++ b/generators/java/sdk/src/main/java/com/fern/java/client/generators/endpoint/AbstractHttpResponseParserGenerator.java @@ -610,8 +610,11 @@ public com.fern.ir.model.types.TypeReference _visitUnknown(Object unknownType) { clientGeneratorContext.getPoetTypeNameMapper().convertToTypeName(true, bodyType); endpointMethodBuilder.returns(ParameterizedTypeName.get(ClassName.get(Iterable.class), bodyTypeName)); - String terminator = + String delimiter = streaming.visit(new GetStreamingResponseTerminator()).orElse("\n"); + String prefix = ":data "; + String terminator = "[DONE]"; + Object streamType = streaming.isSse()?0:1; handleSuccessfulResult( httpResponseBuilder, @@ -622,8 +625,9 @@ public com.fern.ir.model.types.TypeReference _visitUnknown(Object unknownType) { bodyTypeName, clientGeneratorContext.getPoetClassNameFactory().getResponseBodyReaderClassName(), variables.getResponseName(), - terminator)); - + clientGeneratorContext.getPoetClassNameFactory().getStreamOptionsClassName(), + prefix,delimiter,terminator, streamType + )); return null; }