File: Classification\DataClassificationTypeConverter.cs
Web Access
Project: src\src\Libraries\Microsoft.Extensions.Compliance.Abstractions\Microsoft.Extensions.Compliance.Abstractions.csproj (Microsoft.Extensions.Compliance.Abstractions)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using Microsoft.Shared.DiagnosticIds;
 
namespace Microsoft.Extensions.Compliance.Classification;
 
/// <summary>
/// Provides a way to convert a <see cref="DataClassification"/> to and from a string.
/// </summary>
[Experimental(DiagnosticIds.Experiments.Compliance, UrlFormat = DiagnosticIds.UrlFormat)]
public class DataClassificationTypeConverter : TypeConverter
{
    private const char Delimiter = ':';
 
    /// <inheritdoc/>
    public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
    {
        return sourceType == typeof(string);
    }
 
    /// <inheritdoc/>
    public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType)
    {
        return destinationType == typeof(DataClassification);
    }
 
    /// <inheritdoc/>
    [SuppressMessage("Performance",
        "LA0001:Use the 'Microsoft.Shared.Diagnostics.Throws' class instead of explicitly throwing exception for improved performance",
        Justification = "Using the Throws class causes static analysis to incorrectly assume that code after the throw is reachable.")]
    public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
    {
        if (value is not string stringValue)
        {
            throw new ArgumentException("Value must be a string.", nameof(value));
        }
 
        if (stringValue == nameof(DataClassification.None))
        {
            return DataClassification.None;
        }
 
        if (stringValue == nameof(DataClassification.Unknown))
        {
            return DataClassification.Unknown;
        }
 
        if (TryParse(stringValue, out var taxonomyName, out var taxonomyValue))
        {
            return new DataClassification(taxonomyName, taxonomyValue);
        }
 
        throw new FormatException($"Invalid data classification format: '{stringValue}'.");
    }
 
    /// <inheritdoc/>
    public override bool IsValid(ITypeDescriptorContext? context, object? value)
    {
        if (value is not string stringValue)
        {
            return false;
        }
 
        if (stringValue == nameof(DataClassification.None) ||
            stringValue == nameof(DataClassification.Unknown))
        {
            return true;
        }
 
        return TryParse(stringValue, out var taxonomyName, out var taxonomyValue);
    }
 
    /// <summary>
    /// Attempts to parse a string in the format "TaxonomyName:Value".
    /// </summary>
    /// <param name="value">The input string to parse.</param>
    /// <param name="taxonomyName">When this method returns, contains the parsed taxonomy name if the parsing succeeded, or an empty string if it failed.</param>
    /// <param name="taxonomyValue">When this method returns, contains the parsed taxonomy value if the parsing succeeded, or the original input string if it failed.</param>
    /// <returns><see langword="true"/> if the string was successfully parsed; otherwise, <see langword="false"/>.</returns>
    private static bool TryParse(string value, out string taxonomyName, out string taxonomyValue)
    {
        taxonomyName = string.Empty;
        taxonomyValue = value;
 
        if (value.Length <= 1)
        {
            return false;
        }
 
        ReadOnlySpan<char> valueSpan = value.AsSpan();
        int index = valueSpan.IndexOf(Delimiter);
 
        if (index <= 0 || index >= (value.Length - 1))
        {
            return false;
        }
 
        taxonomyName = valueSpan.Slice(0, index).ToString();
        taxonomyValue = valueSpan.Slice(index + 1).ToString();
 
        return true;
    }
}