ronantakizawa/codereview-bench huggingface dataset:
Repository: moby/moby
File: client/image_pull.go (Go)

Code before:
	rc    io.ReadCloser
	close *sync.Once
}

// Read implements io.ReadCloser
func (r ImagePullResponse) Read(p []byte) (n int, err error) {
	return r.rc.Read(p)
}

// Close implements io.ReadCloser
func (r ImagePullResponse) Close() error {
	if r.close == nil {
		return nil
	}
	var err error
	r.close.Do(func() {
		if r.rc != nil {
			err = r.rc.Close()
		}
	})
	return err
}

// Messages decodes the response stream as a sequence of JSONMessages.
// if stream ends ir context is cancelled, the underlying [io.Reader] is closed.
func (r ImagePullResponse) JSONMessages(ctx context.Context) iter.Seq2[jsonmessage.JSONMessage, error] {
	context.AfterFunc(ctx, func() {
		r.Close()
	})
	dec := json.NewDecoder(r)
	return func(yield func(jsonmessage.JSONMessage, error) bool) {
		defer r.Close()
		for {
			var jm jsonmessage.JSONMessage
			err := dec.Decode(&jm)
			if errors.Is(err, io.EOF) {
				break
			}
			if ctx.Err() != nil {
				yield(jm, ctx.Err())
				return
			}
			if !yield(jm, err) {
				return
			}
		}
	}
}

// ImagePull requests the docker host to pull an image from a remote registry.
// It executes the privileged function if the operation is unauthorized

Review comment (bug):
Oh - this was wrong before, but I don't think the comment will be picked up by godoc as the function name doesn't match. Maybe worth fixing, now it has more info?

```suggestion
// JSONMessages decodes the response stream as a sequence of
// [jsonmessage.JSONMessage].
// If the stream ends or context is cancelled, the underlying [io.Reader] is closed.
func (r ImagePullResponse) JSONMessages(ctx context.Context) iter.Seq2[jsonmessage.JSONMessage, error] {
```

Code after:
	rc    io.ReadCloser
	close *sync.Once
}

// Read implements io.ReadCloser
func (r ImagePullResponse) Read(p []byte) (n int, err error) {
	return r.rc.Read(p)
}

// Close implements io.ReadCloser
func (r ImagePullResponse) Close() error {
	if r.close == nil {
		return nil
	}
	var err error
	r.close.Do(func() {
		if r.rc != nil {
			err = r.rc.Close()
		}
	})
	return err
}

// JSONMessages decodes the response stream as a sequence of JSONMessages.
// if stream ends or context is cancelled, the underlying [io.Reader] is closed.
func (r ImagePullResponse) JSONMessages(ctx context.Context) iter.Seq2[jsonmessage.JSONMessage, error] {
	context.AfterFunc(ctx, func() {
		r.Close()
	})
	dec := json.NewDecoder(r)
	return func(yield func(jsonmessage.JSONMessage, error) bool) {
		defer r.Close()
		for {
			var jm jsonmessage.JSONMessage
			err := dec.Decode(&jm)
			if errors.Is(err, io.EOF) {
				break
			}
			if ctx.Err() != nil {
				yield(jm, ctx.Err())
				return
			}
			if !yield(jm, err) {
				return
			}
		}
	}
}

// ImagePull requests the docker host to pull an image from a remote registry.
// It executes the privileged function if the operation is unauthorized
Repository: moby/moby
File: daemon/internal/builder-next/executor_linux.go (Go)

Code before:
package buildkit

import (
	"context"
	"os"
	"path/filepath"
	"strconv"

	containerdoci "github.com/containerd/containerd/v2/pkg/oci"
	"github.com/containerd/log"
	"github.com/moby/buildkit/executor"
	"github.com/moby/buildkit/executor/oci"
	"github.com/moby/buildkit/executor/resources"
	"github.com/moby/buildkit/executor/runcexecutor"
	"github.com/moby/buildkit/solver/llbsolver/cdidevices"
	"github.com/moby/buildkit/solver/pb"
	"github.com/moby/buildkit/util/network"
	"github.com/moby/moby/v2/daemon/internal/stringid"
	"github.com/moby/moby/v2/daemon/libnetwork"
	"github.com/moby/sys/user"
	"github.com/opencontainers/runtime-spec/specs-go"
)

const networkName = "bridge"

func newExecutor(root, cgroupParent string, net *libnetwork.Controller, dnsConfig *oci.DNSConfig, rootless bool, idmap user.IdentityMapping, apparmorProfile string, cdiManager *cdidevices.Manager, _, _ string, _ []containerdoci.SpecOpts) (executor.Executor, error) {
	netRoot := filepath.Join(root, "net")
	networkProviders := map[pb.NetMode]network.Provider{
		pb.NetMode_UNSET: &bridgeProvider{Controller: net, Root: netRoot},
		pb.NetMode_HOST:  network.NewHostProvider(),
		pb.NetMode_NONE:  network.NewNoneProvider(),
	}

	// make sure net state directory is cleared from previous state

Review comment (suggestion):
```suggestion
	coci "github.com/containerd/containerd/v2/pkg/oci"
```

We tend to use the `coci` alias for this one

Code after:
package buildkit

import (
	"context"
	"os"
	"path/filepath"
	"strconv"

	"github.com/containerd/log"
	"github.com/moby/buildkit/executor"
	"github.com/moby/buildkit/executor/oci"
	"github.com/moby/buildkit/executor/resources"
	"github.com/moby/buildkit/executor/runcexecutor"
	"github.com/moby/buildkit/solver/llbsolver/cdidevices"
	"github.com/moby/buildkit/solver/pb"
	"github.com/moby/buildkit/util/network"
	"github.com/moby/moby/v2/daemon/internal/stringid"
	"github.com/moby/moby/v2/daemon/libnetwork"
	"github.com/moby/sys/user"
	"github.com/opencontainers/runtime-spec/specs-go"
)

const networkName = "bridge"

func newExecutor(root, cgroupParent string, net *libnetwork.Controller, dnsConfig *oci.DNSConfig, rootless bool, idmap user.IdentityMapping, apparmorProfile string, cdiManager *cdidevices.Manager, _, _ string, _ bool) (executor.Executor, error) {
	netRoot := filepath.Join(root, "net")
	networkProviders := map[pb.NetMode]network.Provider{
		pb.NetMode_UNSET: &bridgeProvider{Controller: net, Root: netRoot},
		pb.NetMode_HOST:  network.NewHostProvider(),
		pb.NetMode_NONE:  network.NewNoneProvider(),
	}

	// make sure net state directory is cleared from previous state
	fis, err := os.ReadDir(netRoot)
Repository: moby/moby
File: client/internal/json-stream_test.go (Go)

Code before:
package internal

import (
	"fmt"
	"strings"
	"testing"

	"github.com/moby/moby/api/types"
	"github.com/stretchr/testify/assert"
)

func Test_JsonSeqDecoder(t *testing.T) {
	separator := string(rune(rs))
	lf := "\n"
	input := fmt.Sprintf(`%s{"hello":"world"}%s%s{ "hello": "again" }%s`, separator, lf, separator, lf)
	decoder := NewJsonStreamDecoder(strings.NewReader(input), types.MediaTypeJsonSequence)
	type Hello struct {
		Hello string `json:"hello"`
	}
	var hello Hello
	err := decoder(&hello)
	assert.NoError(t, err)
	assert.Equal(t, "world", hello.Hello)

	var again Hello
	err = decoder(&again)
	assert.NoError(t, err)
	assert.Equal(t, "again", again.Hello)
}


Review comment (bug):
Oh! Looks like your IDE probably suggested the wrong assertion library;

```suggestion
	"gotest.tools/v3/assert"
	is "gotest.tools/v3/assert/cmp"
```

Code after:
package internal

import (
	"fmt"
	"strings"
	"testing"

	"github.com/moby/moby/api/types"
	"gotest.tools/v3/assert"
)

func Test_JsonSeqDecoder(t *testing.T) {
	separator := string(rune(rs))
	lf := "\n"
	input := fmt.Sprintf(`%s{"hello":"world"}%s%s{ "hello": "again" }%s`, separator, lf, separator, lf)
	decoder := NewJSONStreamDecoder(strings.NewReader(input), types.MediaTypeJSONSequence)
	type Hello struct {
		Hello string `json:"hello"`
	}
	var hello Hello
	err := decoder(&hello)
	assert.NilError(t, err)
	assert.Equal(t, "world", hello.Hello)

	var again Hello
	err = decoder(&again)
	assert.NilError(t, err)
	assert.Equal(t, "again", again.Hello)
}
Repository: moby/moby
File: client/internal/json-stream_test.go (Go)

Code before:
import (
	"fmt"
	"strings"
	"testing"

	"github.com/moby/moby/api/types"
	"github.com/stretchr/testify/assert"
)

func Test_JsonSeqDecoder(t *testing.T) {
	separator := string(rune(rs))
	lf := "\n"
	input := fmt.Sprintf(`%s{"hello":"world"}%s%s{ "hello": "again" }%s`, separator, lf, separator, lf)
	decoder := NewJsonStreamDecoder(strings.NewReader(input), types.MediaTypeJsonSequence)
	type Hello struct {
		Hello string `json:"hello"`
	}
	var hello Hello
	err := decoder(&hello)
	assert.NoError(t, err)
	assert.Equal(t, "world", hello.Hello)

	var again Hello
	err = decoder(&again)
	assert.NoError(t, err)
	assert.Equal(t, "again", again.Hello)
}


Review comment (suggestion):
```suggestion
	assert.NilError(t, err)
	assert.Check(t, is.Equal("again", again.Hello))
```

Code after:
import (
	"fmt"
	"strings"
	"testing"

	"github.com/moby/moby/api/types"
	"gotest.tools/v3/assert"
)

func Test_JsonSeqDecoder(t *testing.T) {
	separator := string(rune(rs))
	lf := "\n"
	input := fmt.Sprintf(`%s{"hello":"world"}%s%s{ "hello": "again" }%s`, separator, lf, separator, lf)
	decoder := NewJSONStreamDecoder(strings.NewReader(input), types.MediaTypeJSONSequence)
	type Hello struct {
		Hello string `json:"hello"`
	}
	var hello Hello
	err := decoder(&hello)
	assert.NilError(t, err)
	assert.Equal(t, "world", hello.Hello)

	var again Hello
	err = decoder(&again)
	assert.NilError(t, err)
	assert.Equal(t, "again", again.Hello)
}
Repository: moby/moby
File: client/internal/json-stream_test.go (Go)

Code before:
package internal

import (
	"fmt"
	"strings"
	"testing"

	"github.com/moby/moby/api/types"
	"github.com/stretchr/testify/assert"
)

func Test_JsonSeqDecoder(t *testing.T) {
	separator := string(rune(rs))
	lf := "\n"
	input := fmt.Sprintf(`%s{"hello":"world"}%s%s{ "hello": "again" }%s`, separator, lf, separator, lf)
	decoder := NewJsonStreamDecoder(strings.NewReader(input), types.MediaTypeJsonSequence)
	type Hello struct {
		Hello string `json:"hello"`
	}
	var hello Hello
	err := decoder(&hello)
	assert.NoError(t, err)
	assert.Equal(t, "world", hello.Hello)

	var again Hello
	err = decoder(&again)
	assert.NoError(t, err)
	assert.Equal(t, "again", again.Hello)
}


Review comment (suggestion):
```suggestion
	assert.NilError(t, err)
	assert.Check(t, is.Equal("world", hello.Hello))
```

Code after:
package internal

import (
	"fmt"
	"strings"
	"testing"

	"github.com/moby/moby/api/types"
	"gotest.tools/v3/assert"
)

func Test_JsonSeqDecoder(t *testing.T) {
	separator := string(rune(rs))
	lf := "\n"
	input := fmt.Sprintf(`%s{"hello":"world"}%s%s{ "hello": "again" }%s`, separator, lf, separator, lf)
	decoder := NewJSONStreamDecoder(strings.NewReader(input), types.MediaTypeJSONSequence)
	type Hello struct {
		Hello string `json:"hello"`
	}
	var hello Hello
	err := decoder(&hello)
	assert.NilError(t, err)
	assert.Equal(t, "world", hello.Hello)

	var again Hello
	err = decoder(&again)
	assert.NilError(t, err)
	assert.Equal(t, "again", again.Hello)
}
Repository: moby/moby
File: client/internal/json-stream.go (Go)

Code before:
package internal

import (
	"encoding/json"
	"io"
	"strings"

	"github.com/moby/moby/api/types"
)

const rs = 0x1E

type DecoderFn func(v any) error

// NewJsonStreamDecoder builds adequate DecoderFn to read json records formatted with specified content-type
func NewJsonStreamDecoder(r io.Reader, contentType string) DecoderFn {
	switch contentType {
	case types.MediaTypeJsonSequence:
		return json.NewDecoder(NewRSFilterReader(r)).Decode
	default:
		return json.NewDecoder(r).Decode
	}
}

// RSFilterReader wraps an io.Reader and filters out ASCII RS characters
type RSFilterReader struct {
	reader io.Reader
	buffer []byte
}

// NewRSFilterReader creates a new RSFilterReader that filters out RS characters
func NewRSFilterReader(r io.Reader) *RSFilterReader {
	return &RSFilterReader{
		reader: r,
		buffer: make([]byte, 4096), // Internal buffer for reading chunks
	}
}

// Read implements the io.Reader interface, filtering out RS characters
func (r *RSFilterReader) Read(p []byte) (n int, err error) {
	if len(p) == 0 {

Review comment (suggestion):
```suggestion
// NewJSONStreamDecoder builds adequate DecoderFn to read json records formatted with specified content-type
func NewJSONStreamDecoder(r io.Reader, contentType string) DecoderFn {
```

Code after:
package internal

import (
	"encoding/json"
	"io"
	"slices"

	"github.com/moby/moby/api/types"
)

const rs = 0x1E

type DecoderFn func(v any) error

// NewJSONStreamDecoder builds adequate DecoderFn to read json records formatted with specified content-type
func NewJSONStreamDecoder(r io.Reader, contentType string) DecoderFn {
	switch contentType {
	case types.MediaTypeJSONSequence:
		return json.NewDecoder(NewRSFilterReader(r)).Decode
	case types.MediaTypeJSON, types.MediaTypeNDJSON:
		fallthrough
	default:
		return json.NewDecoder(r).Decode
	}
}

// RSFilterReader wraps an io.Reader and filters out ASCII RS characters
type RSFilterReader struct {
	reader io.Reader
	buffer []byte
}

// NewRSFilterReader creates a new RSFilterReader that filters out RS characters
func NewRSFilterReader(r io.Reader) *RSFilterReader {
	return &RSFilterReader{
		reader: r,
		buffer: make([]byte, 4096), // Internal buffer for reading chunks
	}
}

// Read implements the io.Reader interface, filtering out RS characters
Repository: moby/moby
File: api/types/types.go (Go)

Code before:
package types

import (
	"github.com/moby/moby/api/types/build"
	"github.com/moby/moby/api/types/swarm"
)

const (
	// MediaTypeRawStream is vendor specific MIME-Type set for raw TTY streams
	MediaTypeRawStream = "application/vnd.docker.raw-stream"

	// MediaTypeMultiplexedStream is vendor specific MIME-Type set for stdin/stdout/stderr multiplexed streams
	MediaTypeMultiplexedStream = "application/vnd.docker.multiplexed-stream"

	// MediaTypeJson is the MIME-Type for JSON objects
	MediaTypeJson = "application/json"

	// MediaTypeNDJson is the MIME-Type for Newline Delimited JSON objects streams
	MediaTypeNDJson = "application/x-ndjson"

	// MediaTypeJsonSequence is the MIME-Type for JSON Text Sequences (RFC7464)
	MediaTypeJsonSequence = "application/json-seq"
)

// Ping contains response of Engine API:
// GET "/_ping"
type Ping struct {
	APIVersion     string
	OSType         string
	Experimental   bool
	BuilderVersion build.BuilderVersion

	// SwarmStatus provides information about the current swarm status of the
	// engine, obtained from the "Swarm" header in the API response.
	//
	// It can be a nil struct if the API version does not provide this header
	// in the ping response, or if an error occurred, in which case the client
	// should use other ways to get the current swarm status, such as the /swarm
	// endpoint.
	SwarmStatus *swarm.Status
}

Review comment (suggestion):
nit (and to prevent linters from complaining);

```suggestion
	MediaTypeJSON = "application/json"
```

Code after:
package types

import (
	"github.com/moby/moby/api/types/build"
	"github.com/moby/moby/api/types/swarm"
)

const (
	// MediaTypeRawStream is vendor specific MIME-Type set for raw TTY streams
	MediaTypeRawStream = "application/vnd.docker.raw-stream"

	// MediaTypeMultiplexedStream is vendor specific MIME-Type set for stdin/stdout/stderr multiplexed streams
	MediaTypeMultiplexedStream = "application/vnd.docker.multiplexed-stream"

	// MediaTypeJSON is the MIME-Type for JSON objects
	MediaTypeJSON = "application/json"

	// MediaTypeNDJson is the MIME-Type for Newline Delimited JSON objects streams
	MediaTypeNDJSON = "application/x-ndjson"

	// MediaTypeJsonSequence is the MIME-Type for JSON Text Sequences (RFC7464)
	MediaTypeJSONSequence = "application/json-seq"
)

// Ping contains response of Engine API:
// GET "/_ping"
type Ping struct {
	APIVersion     string
	OSType         string
	Experimental   bool
	BuilderVersion build.BuilderVersion

	// SwarmStatus provides information about the current swarm status of the
	// engine, obtained from the "Swarm" header in the API response.
	//
	// It can be a nil struct if the API version does not provide this header
	// in the ping response, or if an error occurred, in which case the client
	// should use other ways to get the current swarm status, such as the /swarm
	// endpoint.
	SwarmStatus *swarm.Status
}
Repository: moby/moby
File: client/internal/json-stream.go (Go)

Code before:
package internal

import (
	"encoding/json"
	"io"
	"strings"

	"github.com/moby/moby/api/types"
)

const rs = 0x1E

type DecoderFn func(v any) error

// NewJsonStreamDecoder builds adequate DecoderFn to read json records formatted with specified content-type
func NewJsonStreamDecoder(r io.Reader, contentType string) DecoderFn {
	switch contentType {
	case types.MediaTypeJsonSequence:
		return json.NewDecoder(NewRSFilterReader(r)).Decode
	default:
		return json.NewDecoder(r).Decode
	}
}

// RSFilterReader wraps an io.Reader and filters out ASCII RS characters
type RSFilterReader struct {
	reader io.Reader
	buffer []byte
}

// NewRSFilterReader creates a new RSFilterReader that filters out RS characters
func NewRSFilterReader(r io.Reader) *RSFilterReader {
	return &RSFilterReader{
		reader: r,
		buffer: make([]byte, 4096), // Internal buffer for reading chunks
	}
}

// Read implements the io.Reader interface, filtering out RS characters
func (r *RSFilterReader) Read(p []byte) (n int, err error) {
	if len(p) == 0 {
		return 0, nil
	}

	for n == 0 { // Keep reading until we have some filtered data or hit EOF/error
		// Read from underlying reader
		readN, readErr := r.reader.Read(r.buffer)
		if readErr != nil && readErr != io.EOF {

Review comment (suggestion):
Perhaps we should be explicit, and add `MediaTypeNDJson` and `MediaTypeJson`;



```suggestion
	case MediaTypeNDJson, MediaTypeJson:
		fallthrough
	default:
		return json.NewDecoder(r).Decode
```

Code after:
package internal

import (
	"encoding/json"
	"io"
	"slices"

	"github.com/moby/moby/api/types"
)

const rs = 0x1E

type DecoderFn func(v any) error

// NewJSONStreamDecoder builds adequate DecoderFn to read json records formatted with specified content-type
func NewJSONStreamDecoder(r io.Reader, contentType string) DecoderFn {
	switch contentType {
	case types.MediaTypeJSONSequence:
		return json.NewDecoder(NewRSFilterReader(r)).Decode
	case types.MediaTypeJSON, types.MediaTypeNDJSON:
		fallthrough
	default:
		return json.NewDecoder(r).Decode
	}
}

// RSFilterReader wraps an io.Reader and filters out ASCII RS characters
type RSFilterReader struct {
	reader io.Reader
	buffer []byte
}

// NewRSFilterReader creates a new RSFilterReader that filters out RS characters
func NewRSFilterReader(r io.Reader) *RSFilterReader {
	return &RSFilterReader{
		reader: r,
		buffer: make([]byte, 4096), // Internal buffer for reading chunks
	}
}

// Read implements the io.Reader interface, filtering out RS characters
func (r *RSFilterReader) Read(p []byte) (n int, err error) {
	if len(p) == 0 {
		return 0, nil
	}

	n, err = r.reader.Read(p)
	filtered := slices.DeleteFunc(p[:n], func(b byte) bool { return b == rs })
Repository: moby/moby
File: client/internal/json-stream.go (Go)

Code before:
package internal

import (
	"encoding/json"
	"io"
	"strings"

	"github.com/moby/moby/api/types"
)

const rs = 0x1E

type DecoderFn func(v any) error

// NewJsonStreamDecoder builds adequate DecoderFn to read json records formatted with specified content-type
func NewJsonStreamDecoder(r io.Reader, contentType string) DecoderFn {
	switch contentType {
	case types.MediaTypeJsonSequence:
		return json.NewDecoder(NewRSFilterReader(r)).Decode
	default:
		return json.NewDecoder(r).Decode
	}
}

// RSFilterReader wraps an io.Reader and filters out ASCII RS characters
type RSFilterReader struct {

Review comment (suggestion):
Slightly considering that this could be a separate package (under `internal`), but don't have a good suggestion for the name (as we already have `jsonstream` 😂 )

Code after:
package internal

import (
	"encoding/json"
	"io"
	"slices"

	"github.com/moby/moby/api/types"
)

const rs = 0x1E

type DecoderFn func(v any) error

// NewJSONStreamDecoder builds adequate DecoderFn to read json records formatted with specified content-type
func NewJSONStreamDecoder(r io.Reader, contentType string) DecoderFn {
	switch contentType {
	case types.MediaTypeJSONSequence:
		return json.NewDecoder(NewRSFilterReader(r)).Decode
	case types.MediaTypeJSON, types.MediaTypeNDJSON:
		fallthrough
	default:
		return json.NewDecoder(r).Decode
	}
}
Repository: moby/moby
File: daemon/internal/compat/compat.go (Go)

Code before:
	}
	if len(w.omitFields) == 0 && len(w.extraFields) == 0 {
		return base, nil
	}

	var merged map[string]any
	if err := json.Unmarshal(base, &merged); err != nil {
		return nil, err
	}

	for _, key := range w.omitFields {
		delete(merged, key)
	}
	for key, val := range w.extraFields {
		merged[key] = val
	}

	return toJSON(merged)
}

func toJSON(v any) ([]byte, error) {
	var buf bytes.Buffer
	enc := json.NewEncoder(&buf)
	enc.SetEscapeHTML(false)
	err := enc.Encode(v)
	return buf.Bytes(), err
}

type options struct {
	extraFields map[string]any
	omitFields  []string
}

// Option for Wrapper.
type Option func(*options)

// WithExtraFields adds fields to the marshaled output.
func WithExtraFields(fields map[string]any) Option {
	return func(c *options) {
		if c.extraFields == nil {
			c.extraFields = make(map[string]any)
		}
		for k, v := range fields {
			c.extraFields[k] = v
		}
	}
}

// WithOmittedFields removes fields from the marshaled output.
func WithOmittedFields(fields ...string) Option {
	return func(c *options) {

Review comment (suggestion):
We probably need to trim the trailing newline in this case; `NewEncoder` acts different than `json.Marshal` as it adds a trailing newline; that one would be preserved when the encoder calling the `MarshalJSON` consumes the results (as it just concatenates each JSON document)


```suggestion
	if err := enc.Encode(v); err != nil {
		return nil, err
	}
	out, _, _ := bytes.CutSuffix(buf.Bytes(), []byte{'\n'})
	return out, nil
```


Alternatively, we could stick to `json.Marshal` and undo the encoding

```go
func toJSON(v any) ([]byte, error) {
	out, err := json.Marshal(v)
	if err != nil {
		return nil, err
	}
	out = bytes.ReplaceAll(out, []byte(`\u003c`), []byte("<"))
	out = bytes.ReplaceAll(out, []byte(`\u003e`), []byte(">"))
	out = bytes.ReplaceAll(out, []byte(`\u0026`), []byte("&"))
	return out, nil
}
```

Code after:
	}
	if len(w.omitFields) == 0 && len(w.extraFields) == 0 {
		return base, nil
	}

	var merged map[string]any
	if err := json.Unmarshal(base, &merged); err != nil {
		return nil, err
	}

	for _, key := range w.omitFields {
		delete(merged, key)
	}
	for key, val := range w.extraFields {
		merged[key] = val
	}

	return toJSON(merged)
}

func toJSON(v any) ([]byte, error) {
	var buf bytes.Buffer
	enc := json.NewEncoder(&buf)
	enc.SetEscapeHTML(false)
	if err := enc.Encode(v); err != nil {
		return nil, err
	}
	out, _ := bytes.CutSuffix(buf.Bytes(), []byte{'\n'})
	return out, nil
}

type options struct {
	extraFields map[string]any
	omitFields  []string
}

// Option for Wrapper.
type Option func(*options)

// WithExtraFields adds fields to the marshaled output.
func WithExtraFields(fields map[string]any) Option {
	return func(c *options) {
		if c.extraFields == nil {
			c.extraFields = make(map[string]any)
		}
		for k, v := range fields {
			c.extraFields[k] = v
		}
	}
}