-
-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathcodec.go
More file actions
159 lines (143 loc) · 5.64 KB
/
Copy pathcodec.go
File metadata and controls
159 lines (143 loc) · 5.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
// Copyright 2026 The Go Language Server Authors. All rights reserved.
// SPDX-License-Identifier: BSD-3-Clause
package jsonrpc2
import (
stdjson "encoding/json"
json "github.com/go-json-experiment/json"
"github.com/go-json-experiment/json/jsontext"
)
// Codec marshals and unmarshals the user payloads of a JSON-RPC message: the
// "params" of a request and the "result" of a response. The envelope itself is
// never routed through a Codec; only the user-controlled payload is.
//
// Implementations must be safe for concurrent use, must not escape HTML-sensitive
// characters (to match a json.Encoder with SetEscapeHTML(false)), and must treat
// a [RawMessage] (or encoding/json.RawMessage) as an opaque, already-encoded value
// that is passed through verbatim without re-encoding.
type Codec interface {
// Marshal encodes v into its JSON representation. A nil v encodes to the JSON
// null literal. A [RawMessage] value is returned verbatim.
Marshal(v any) ([]byte, error)
// Unmarshal decodes the JSON in data into the value pointed to by v. When v is
// a *RawMessage the bytes are copied verbatim into a slice the caller owns.
Unmarshal(data []byte, v any) error
}
// DefaultCodec is the [Codec] used for payloads when a connection is not given an
// explicit codec. It is backed by the encoding/json/v2 experiment
// (github.com/go-json-experiment/json) and is overridable by assigning a
// different [Codec]; the opt-in codec/sonic and codec/goccy packages provide
// drop-in alternatives.
var DefaultCodec Codec = JSONCodec{}
// jsonV2Options is the fixed option set for the default codec. HTML-sensitive
// characters are left unescaped so that payloads match the byte output of a
// json.Encoder configured with SetEscapeHTML(false); json/v2 already defaults to
// not escaping, but the option is set explicitly to make the contract clear and
// stable against future default changes.
var jsonV2Options = json.JoinOptions(jsontext.EscapeForHTML(false))
// JSONCodec is the default [Codec] implementation, backed by encoding/json/v2
// (github.com/go-json-experiment/json). It produces compact output with no
// trailing newline and no HTML escaping, consistent with json.Marshal, and
// passes [RawMessage] values through verbatim.
type JSONCodec struct{}
// compile-time check that JSONCodec satisfies Codec.
var _ Codec = JSONCodec{}
// Marshal implements [Codec] using encoding/json/v2. A [RawMessage] or
// encoding/json.RawMessage value is returned verbatim (with nil mapped to the
// null literal); every other value is encoded through json/v2.
func (JSONCodec) Marshal(v any) ([]byte, error) {
if raw, ok := rawBytes(v); ok {
return rawMarshal(raw), nil
}
return json.Marshal(v, jsonV2Options)
}
// Unmarshal implements [Codec] using encoding/json/v2. When v is a *RawMessage or
// *encoding/json.RawMessage the bytes are copied verbatim; otherwise the value is
// decoded through json/v2.
func (JSONCodec) Unmarshal(data []byte, v any) error {
if rawUnmarshal(data, v) {
return nil
}
return json.Unmarshal(data, v, jsonV2Options)
}
// rawBytes reports whether v is one of the raw, already-encoded JSON types and,
// if so, returns its bytes. Both this package's [RawMessage] and
// encoding/json.RawMessage are recognized so that either may be passed through
// verbatim.
func rawBytes(v any) (raw []byte, ok bool) {
switch m := v.(type) {
case RawMessage:
return m, true
case stdjson.RawMessage:
return m, true
default:
return nil, false
}
}
// rawMarshal returns the verbatim encoding of a raw JSON value: the bytes as-is
// when present, or the null literal when nil, matching the convention that a nil
// raw value encodes to JSON null.
func rawMarshal(raw []byte) []byte {
if raw == nil {
return []byte("null")
}
return raw
}
// rawUnmarshal copies data verbatim into v when v is a pointer to one of the raw
// JSON types, and reports whether it handled v. A nil data slice yields a nil
// destination so that "absent" stays distinguishable from "present but empty".
func rawUnmarshal(data []byte, v any) (handled bool) {
switch p := v.(type) {
case *RawMessage:
*p = cloneBytes(data)
return true
case *stdjson.RawMessage:
*p = cloneBytes(data)
return true
default:
return false
}
}
// marshalParams encodes the request parameters v with codec c into a
// [RawMessage] suitable for the wire envelope. A nil or empty payload yields a
// nil RawMessage so that the encoder omits the "params" member entirely (the
// convention that null parameters mean "no parameters"); a [RawMessage] is
// passed through verbatim. When c is nil the [DefaultCodec] is used.
func marshalParams(c Codec, v any) (RawMessage, error) {
if v == nil {
return nil, nil
}
if c == nil {
c = DefaultCodec
}
b, err := c.Marshal(v)
if err != nil {
return nil, err
}
// An explicit null payload is treated as "no parameters" so it is omitted
// from the envelope, consistent with the decode-side classification.
if len(b) == 0 || isNullLiteral(b) {
return nil, nil
}
return RawMessage(b), nil
}
// unmarshalResult decodes the response result data with codec c into v. An empty
// or null result is a no-op so that a success response carrying no payload leaves
// v at its zero value rather than failing. A *RawMessage destination receives a
// verbatim copy of data. When c is nil the [DefaultCodec] is used.
func unmarshalResult(c Codec, data RawMessage, v any) error {
if v == nil {
return nil
}
if len(data) == 0 || isNullLiteral(data) {
// Still honor a RawMessage destination so the caller can observe an
// explicit null or empty result distinctly.
if rawUnmarshal(data, v) {
return nil
}
return nil
}
if c == nil {
c = DefaultCodec
}
return c.Unmarshal(data, v)
}