| File: ProjectDataBuildDiagnosticProtocol.cs | Web Access |
| Project: Microsoft.NET.ProjectData.csproj (Microsoft.NET.ProjectData) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Globalization; using System.Text; namespace Microsoft.NET.ProjectData; /// <summary> /// Private, versioned stderr framing for provisional ProjectDataBuild diagnostics. /// Frames are diagnostics-only and never provide terminal-state evidence. /// </summary> public static class ProjectDataBuildDiagnosticProtocol { public const int Version = 1; public const string Prefix = "@@CSDEVKIT_PROJECTDATA_DIAGNOSTIC@@"; private static readonly Encoding Utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); public static string Encode(string attemptId, ProjectDataBuildDiagnosticRecord diagnostic) { if (string.IsNullOrWhiteSpace(attemptId)) { throw new ArgumentException("Value cannot be null or whitespace.", nameof(attemptId)); } if (diagnostic is null) { throw new ArgumentNullException(nameof(diagnostic)); } return string.Join( "|", Prefix + Version.ToString(CultureInfo.InvariantCulture), EncodeField(attemptId), EncodeField(diagnostic.Phase), EncodeField(diagnostic.Severity), EncodeField(diagnostic.ProjectFilePath), EncodeField(diagnostic.FilePath), EncodeField(diagnostic.Code), diagnostic.Line.ToString(CultureInfo.InvariantCulture), diagnostic.Column.ToString(CultureInfo.InvariantCulture), EncodeField(diagnostic.Message)); } public static bool TryDecode(string line, string expectedAttemptId, out ProjectDataBuildDiagnosticRecord diagnostic) { diagnostic = new ProjectDataBuildDiagnosticRecord(); if (string.IsNullOrEmpty(line) || string.IsNullOrEmpty(expectedAttemptId) || !line.StartsWith(Prefix, StringComparison.Ordinal)) { return false; } try { string[] fields = line.Split('|'); if (fields.Length != 10 || !string.Equals(fields[0], Prefix + Version.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal)) { return false; } string attemptId = DecodeField(fields[1]); if (!string.Equals(attemptId, expectedAttemptId, StringComparison.Ordinal) || !int.TryParse(fields[7], NumberStyles.Integer, CultureInfo.InvariantCulture, out int lineNumber) || !int.TryParse(fields[8], NumberStyles.Integer, CultureInfo.InvariantCulture, out int columnNumber)) { return false; } diagnostic = new ProjectDataBuildDiagnosticRecord { Phase = DecodeField(fields[2]), Severity = DecodeField(fields[3]), ProjectFilePath = DecodeField(fields[4]), FilePath = DecodeField(fields[5]), Code = DecodeField(fields[6]), Line = lineNumber, Column = columnNumber, Message = DecodeField(fields[9]), }; return true; } catch (Exception ex) when (ex is FormatException or ArgumentException) { return false; } } private static string EncodeField(string? value) => Convert.ToBase64String(Utf8NoBom.GetBytes(value ?? string.Empty)); private static string DecodeField(string value) => Utf8NoBom.GetString(Convert.FromBase64String(value)); }