Skip to content

Commit 3c5784c

Browse files
authored
Merge pull request #3 from hyparam/protobuf
Add OTLP/HTTP protobuf support
2 parents 79a907c + bfe3ac6 commit 3c5784c

12 files changed

Lines changed: 2135 additions & 7 deletions

File tree

src/otlp/common.js

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
/**
2+
* Decoders for OTLP common message types (AnyValue, KeyValue, Resource,
3+
* InstrumentationScope). Output matches OTLP/JSON: camelCase field names,
4+
* int64 as string, bytes as base64, trace/span ids as lowercase hex.
5+
*
6+
* @import {DataReader} from '../types.js'
7+
*/
8+
9+
import {
10+
readBytes,
11+
readDouble,
12+
readTag,
13+
readVarBigInt,
14+
readVarint,
15+
skipField,
16+
} from '../protobuf.js'
17+
18+
const textDecoder = new TextDecoder()
19+
20+
/**
21+
* Wrap a byte slice in a DataReader for field-by-field decoding.
22+
*
23+
* @param {Uint8Array} bytes
24+
* @returns {DataReader}
25+
*/
26+
export function makeReader(bytes) {
27+
return {
28+
view: new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength),
29+
offset: 0,
30+
}
31+
}
32+
33+
/**
34+
* Lowercase hex encoding for trace_id/span_id/parent_span_id fields.
35+
*
36+
* @param {Uint8Array} bytes
37+
* @returns {string}
38+
*/
39+
export function toHex(bytes) {
40+
return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString('hex')
41+
}
42+
43+
/**
44+
* Standard base64 for generic bytes fields.
45+
*
46+
* @param {Uint8Array} bytes
47+
* @returns {string}
48+
*/
49+
export function toBase64(bytes) {
50+
return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString('base64')
51+
}
52+
53+
/**
54+
* @param {Uint8Array} bytes
55+
* @returns {string}
56+
*/
57+
export function decodeString(bytes) {
58+
return textDecoder.decode(bytes)
59+
}
60+
61+
/**
62+
* @param {Uint8Array} bytes
63+
* @returns {object}
64+
*/
65+
export function decodeAnyValue(bytes) {
66+
const r = makeReader(bytes)
67+
const end = bytes.byteLength
68+
/** @type {Record<string, unknown>} */
69+
const out = {}
70+
while (r.offset < end) {
71+
const { fieldNumber, wireType } = readTag(r)
72+
switch (fieldNumber) {
73+
case 1: out.stringValue = decodeString(readBytes(r)); break
74+
case 2: out.boolValue = readVarint(r) !== 0; break
75+
case 3: out.intValue = readVarBigInt(r).toString(); break
76+
case 4: out.doubleValue = readDouble(r); break
77+
case 5: out.arrayValue = decodeArrayValue(readBytes(r)); break
78+
case 6: out.kvlistValue = decodeKeyValueList(readBytes(r)); break
79+
case 7: out.bytesValue = toBase64(readBytes(r)); break
80+
default: skipField(r, wireType)
81+
}
82+
}
83+
return out
84+
}
85+
86+
/**
87+
* @param {Uint8Array} bytes
88+
* @returns {{ values: object[] }}
89+
*/
90+
export function decodeArrayValue(bytes) {
91+
const r = makeReader(bytes)
92+
const end = bytes.byteLength
93+
/** @type {object[]} */
94+
const values = []
95+
while (r.offset < end) {
96+
const { fieldNumber, wireType } = readTag(r)
97+
if (fieldNumber === 1) {
98+
values.push(decodeAnyValue(readBytes(r)))
99+
} else {
100+
skipField(r, wireType)
101+
}
102+
}
103+
return { values }
104+
}
105+
106+
/**
107+
* @param {Uint8Array} bytes
108+
* @returns {{ values: object[] }}
109+
*/
110+
export function decodeKeyValueList(bytes) {
111+
const r = makeReader(bytes)
112+
const end = bytes.byteLength
113+
/** @type {object[]} */
114+
const values = []
115+
while (r.offset < end) {
116+
const { fieldNumber, wireType } = readTag(r)
117+
if (fieldNumber === 1) {
118+
values.push(decodeKeyValue(readBytes(r)))
119+
} else {
120+
skipField(r, wireType)
121+
}
122+
}
123+
return { values }
124+
}
125+
126+
/**
127+
* @param {Uint8Array} bytes
128+
* @returns {object}
129+
*/
130+
export function decodeKeyValue(bytes) {
131+
const r = makeReader(bytes)
132+
const end = bytes.byteLength
133+
/** @type {Record<string, unknown>} */
134+
const out = {}
135+
while (r.offset < end) {
136+
const { fieldNumber, wireType } = readTag(r)
137+
switch (fieldNumber) {
138+
case 1: out.key = decodeString(readBytes(r)); break
139+
case 2: out.value = decodeAnyValue(readBytes(r)); break
140+
default: skipField(r, wireType)
141+
}
142+
}
143+
return out
144+
}
145+
146+
/**
147+
* @param {Uint8Array} bytes
148+
* @returns {object}
149+
*/
150+
export function decodeInstrumentationScope(bytes) {
151+
const r = makeReader(bytes)
152+
const end = bytes.byteLength
153+
/** @type {Record<string, unknown>} */
154+
const out = {}
155+
/** @type {object[]} */
156+
const attributes = []
157+
while (r.offset < end) {
158+
const { fieldNumber, wireType } = readTag(r)
159+
switch (fieldNumber) {
160+
case 1: out.name = decodeString(readBytes(r)); break
161+
case 2: out.version = decodeString(readBytes(r)); break
162+
case 3: attributes.push(decodeKeyValue(readBytes(r))); break
163+
case 4: out.droppedAttributesCount = readVarint(r); break
164+
default: skipField(r, wireType)
165+
}
166+
}
167+
if (attributes.length) out.attributes = attributes
168+
return out
169+
}
170+
171+
/**
172+
* @param {Uint8Array} bytes
173+
* @returns {object}
174+
*/
175+
export function decodeResource(bytes) {
176+
const r = makeReader(bytes)
177+
const end = bytes.byteLength
178+
/** @type {Record<string, unknown>} */
179+
const out = {}
180+
/** @type {object[]} */
181+
const attributes = []
182+
while (r.offset < end) {
183+
const { fieldNumber, wireType } = readTag(r)
184+
switch (fieldNumber) {
185+
case 1: attributes.push(decodeKeyValue(readBytes(r))); break
186+
case 2: out.droppedAttributesCount = readVarint(r); break
187+
default: skipField(r, wireType)
188+
}
189+
}
190+
if (attributes.length) out.attributes = attributes
191+
return out
192+
}

0 commit comments

Comments
 (0)