| 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 | |
| } | |
| } |