@@ -1309,6 +1309,160 @@ pub const AnyTransport = struct {
1309
1309
}
1310
1310
};
1311
1311
1312
+ pub const minimum_logging_buffer_size : usize = 128 ;
1313
+
1314
+ /// Creates a `window/logMessage` notification.
1315
+ /// Returns a slice that points into `buffer`.
1316
+ pub fn bufPrintLogMessage (
1317
+ /// A temporary buffer which will be used to store the json message.
1318
+ /// The formatted message will be truncated to fit into the buffer.
1319
+ ///
1320
+ /// Must be at least `minimum_logging_buffer_size` bytes long.
1321
+ buffer : []u8 ,
1322
+ message_type : types.MessageType ,
1323
+ comptime fmt : []const u8 ,
1324
+ args : anytype ,
1325
+ ) []u8 {
1326
+ return bufPrintLogMessageTypeErased (
1327
+ buffer ,
1328
+ message_type ,
1329
+ struct {
1330
+ fn format (writer : std.io.AnyWriter , opaque_params : * const anyopaque ) void {
1331
+ std .fmt .format (writer , fmt , @as (* const @TypeOf (args ), @alignCast (@ptrCast (opaque_params ))).* ) catch {};
1332
+ }
1333
+ }.format ,
1334
+ & args ,
1335
+ );
1336
+ }
1337
+
1338
+ fn bufPrintLogMessageTypeErased (
1339
+ buffer : []u8 ,
1340
+ message_type : types.MessageType ,
1341
+ format_fn : * const fn (std.io.AnyWriter , opaque_params : * const anyopaque ) void ,
1342
+ opaque_params : * const anyopaque ,
1343
+ ) []u8 {
1344
+ std .debug .assert (buffer .len >= minimum_logging_buffer_size );
1345
+ const json_message_suffix : []const u8 = "\" }}" ;
1346
+ var fbs = std .io .fixedBufferStream (buffer [0 .. buffer .len - json_message_suffix .len ]);
1347
+
1348
+ const writer = fbs .writer ();
1349
+ writer .print (
1350
+ \\{{"jsonrpc":"2.0","method":"window/logMessage","params":{{"type":{},"message":"
1351
+ , .{std .json .fmt (message_type , .{})}) catch unreachable ;
1352
+
1353
+ const json_writer : std .io .Writer (* std .io .FixedBufferStream ([]u8 ), error {NoSpaceLeft }, jsonWrite ) = .{
1354
+ .context = & fbs ,
1355
+ };
1356
+
1357
+ format_fn (json_writer .any (), opaque_params );
1358
+
1359
+ fbs .buffer = buffer ;
1360
+ fbs .writer ().writeAll (json_message_suffix ) catch unreachable ;
1361
+
1362
+ return fbs .getWritten ();
1363
+ }
1364
+
1365
+ fn jsonWrite (fbs : * std .io .FixedBufferStream ([]u8 ), bytes : []const u8 ) error {NoSpaceLeft }! usize {
1366
+ var write_cursor : usize = 0 ;
1367
+ var i : usize = 0 ;
1368
+ while (i < bytes .len ) : (i += 1 ) {
1369
+ switch (bytes [i ]) {
1370
+ 0x20... 0x21, 0x23... 0x5B, 0x5D... 0xFF = > {},
1371
+ 0x00... 0x1F, '\\ ' , '\" ' = > {
1372
+ try fbsWriteOrEllipses (fbs , bytes [write_cursor .. i ]);
1373
+
1374
+ // either write an escape code in its entirety or don't at all
1375
+ const pos = fbs .pos ;
1376
+ errdefer fbs .pos = pos ;
1377
+
1378
+ const writer = fbs .writer ();
1379
+
1380
+ switch (bytes [i ]) {
1381
+ '\\ ' = > try writer .writeAll ("\\\\ " ),
1382
+ '\" ' = > try writer .writeAll ("\\ \" " ),
1383
+ 0x08 = > try writer .writeAll ("\\ b" ),
1384
+ 0x0C = > try writer .writeAll ("\\ f" ),
1385
+ '\n ' = > try writer .writeAll ("\\ n" ),
1386
+ '\r ' = > try writer .writeAll ("\\ r" ),
1387
+ '\t ' = > try writer .writeAll ("\\ t" ),
1388
+ else = > {
1389
+ try fbs .writer ().writeAll ("\\ u" );
1390
+ try std .fmt .formatIntValue (bytes [i ], "x" , std.fmt.FormatOptions { .width = 4 , .fill = '0' }, fbs .writer ());
1391
+ },
1392
+ }
1393
+
1394
+ write_cursor = i + 1 ;
1395
+ },
1396
+ }
1397
+ }
1398
+
1399
+ try fbsWriteOrEllipses (fbs , bytes [write_cursor .. ]);
1400
+ return bytes .len ;
1401
+ }
1402
+
1403
+ fn fbsWriteOrEllipses (
1404
+ fbs : * std .io .FixedBufferStream ([]u8 ),
1405
+ bytes : []const u8 ,
1406
+ ) error {NoSpaceLeft }! void {
1407
+ const ellipses : []const u8 = "..." ;
1408
+
1409
+ const pos_before_write = fbs .pos ;
1410
+ const amt = fbs .write (bytes ) catch 0 ;
1411
+ if (amt == bytes .len ) return ;
1412
+
1413
+ // try to move the buffer position back so that we have space for the ellipses
1414
+ fbs .pos = @max (
1415
+ pos_before_write , // make sure that we don't backtrack beyond an escape code
1416
+ @min (fbs .pos , fbs .buffer .len - ellipses .len ),
1417
+ );
1418
+ if (fbs .buffer .len - fbs .pos >= ellipses .len ) {
1419
+ fbs .writer ().writeAll (ellipses ) catch unreachable ;
1420
+ }
1421
+ return error .NoSpaceLeft ;
1422
+ }
1423
+
1424
+ test bufPrintLogMessage {
1425
+ var buffer : [1024 ]u8 = undefined ;
1426
+ const json_message = bufPrintLogMessage (
1427
+ & buffer ,
1428
+ .Warning ,
1429
+ "Hello {s} '\\ foo{s}bar'" ,
1430
+ .{ "World" , "\" " },
1431
+ );
1432
+
1433
+ try std .testing .expectEqualStrings (
1434
+ \\{"jsonrpc":"2.0","method":"window/logMessage","params":{"type":2,"message":"Hello World '\\foo\"bar'"}}
1435
+ , json_message );
1436
+ }
1437
+
1438
+ test "bufPrintLogMessage - avoid buffer overflow" {
1439
+ var buffer : [128 ]u8 = undefined ;
1440
+ const json_message = bufPrintLogMessage (
1441
+ & buffer ,
1442
+ @enumFromInt (42 ),
1443
+ "01234567890123456789012345678901234567890123456789" ,
1444
+ .{},
1445
+ );
1446
+
1447
+ try std .testing .expectEqualStrings (
1448
+ \\{"jsonrpc":"2.0","method":"window/logMessage","params":{"type":42,"message":"012345678901234567890123456789012345678901234..."}}
1449
+ , json_message );
1450
+ }
1451
+
1452
+ test "bufPrintLogMessage - avoid buffer overflow with escape codes" {
1453
+ var buffer : [128 ]u8 = undefined ;
1454
+ const json_message = bufPrintLogMessage (
1455
+ & buffer ,
1456
+ @enumFromInt (42 ),
1457
+ "\x00 " ** 128 ,
1458
+ .{},
1459
+ );
1460
+
1461
+ try std .testing .expectEqualStrings (
1462
+ \\{"jsonrpc":"2.0","method":"window/logMessage","params":{"type":42,"message":"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"}}
1463
+ , json_message );
1464
+ }
1465
+
1312
1466
pub const MethodWithParams = struct {
1313
1467
method : []const u8 ,
1314
1468
/// The `std.json.Value` can only be `.null`, `.array` or `.object`.
0 commit comments