blob: d86b99da2ecaed5649c52ea3bbe6beb6f9c71722 [file] [edit]
package internal
import (
"encoding/json"
"io"
"slices"
"github.com/moby/moby/api/types"
)
const rs = 0x1E
type DecoderFn func(v any) error
// NewJSONStreamDecoder builds a DecoderFn to read a stream of JSON records
// formatted with the specified content-type.
func NewJSONStreamDecoder(r io.Reader, contentType types.MediaType) DecoderFn {
switch contentType {
case types.MediaTypeJSONSequence:
return json.NewDecoder(NewRSFilterReader(r)).Decode
case types.MediaTypeJSON, types.MediaTypeNDJSON, types.MediaTypeJSONLines:
fallthrough
default:
return json.NewDecoder(r).Decode
}
}
type rsFilterReader struct {
reader io.Reader
}
// NewRSFilterReader creates an [io.Reader] that filters out ASCII Record Separators (RS).
func NewRSFilterReader(r io.Reader) io.Reader {
return &rsFilterReader{reader: r}
}
func (r *rsFilterReader) Read(p []byte) (int, error) {
if len(p) == 0 {
return 0, nil
}
for {
n, err := r.reader.Read(p)
if n == 0 {
return 0, err
}
filtered := slices.DeleteFunc(p[:n], func(b byte) bool { return b == rs })
n = len(filtered)
if err != nil {
if err == io.EOF && n > 0 {
return n, nil
}
return n, err
}
if n == 0 {
// Avoid returning (0, nil) after consuming input; keep reading until data or an error (e.g., EOF).
continue
}
return n, nil
}
}