Source/Protobuf/ArtifactExtensions.cs
// Copyright (c) Dolittle. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Diagnostics.CodeAnalysis;
using Dolittle.SDK.Artifacts;
using PbArtifact = Dolittle.Artifacts.Contracts.Artifact;
namespace Dolittle.SDK.Protobuf;
/// <summary>
/// Conversion extensions for converting between <see cref="Artifact{TId}"/> and <see cref="PbArtifact"/>.
/// </summary>
public static class ArtifactExtensions
{
/// <summary>
/// Convert a <see cref="Artifact{TId}"/> to a <see cref="PbArtifact"/>.
/// </summary>
/// <param name="artifact"><see cref="Artifact{TId}"/> to convert.</param>
/// <typeparam name="TId">The <see cref="Type" /> of the <see cref="ArtifactId" />.</typeparam>
/// <returns>The converted <see cref="PbArtifact"/>.</returns>
public static PbArtifact ToProtobuf<TId>(this Artifact<TId> artifact)
where TId : ArtifactId
=> new() { Id = artifact.Id.ToProtobuf(), Generation = artifact.Generation.Value };
/// <summary>
/// Convert a <see cref="PbArtifact"/> to a <typeparamref name="TArtifact"/>.
/// </summary>
/// <remarks>
/// The method assumes that the <see cref="Artifact{TId}" /> class has a constructor that takes in
/// <see cref="ArtifactId" /> as first parameter and <see cref="Generation" /> as second parameter.
/// It also assumes that the <see cref="ArtifactId" /> type in the <see cref="Artifact{TId}" /> only has the default parameterless constructor.
/// </remarks>
/// <param name="source"><see cref="PbArtifact"/> to convert.</param>
/// <param name="artifact">When the method returns, the converted <see cref="Artifact{TId}"/> if conversion was successful, otherwise default.</param>
/// <param name="error">When the method returns, null if the conversion was successful, otherwise the error that caused the failure.</param>
/// <typeparam name="TArtifact">The <see cref="Type" /> of the <see cref="Artifact{TId}" />.</typeparam>
/// <typeparam name="TId">The <see cref="Type" /> of the <see cref="ArtifactId" />.</typeparam>
/// <returns>A value indicating whether or not the conversion was successful.</returns>
public static bool TryTo<TArtifact, TId>(this PbArtifact source, [NotNullWhen(true)] out TArtifact? artifact, [NotNullWhen(false)] out Exception? error)
where TArtifact : Artifact<TId>
where TId : ArtifactId
{
artifact = default;
if (source == default)
{
error = new ArgumentNullException(nameof(source));
return false;
}
if (!source.Id.TryTo<TId>(out var artifactId, out var idError))
{
error = new CouldNotConvertProtobufArtifact(typeof(TArtifact), source, idError.Message);
return false;
}
var idType = typeof(TArtifact).GetProperty(nameof(Artifact<TId>.Id)).PropertyType;
var generationType = typeof(TArtifact).GetProperty(nameof(Artifact<TId>.Generation)).PropertyType;
try
{
var constructor = typeof(TArtifact).GetConstructor(new[] { idType, generationType });
if (constructor == default)
{
error = new ArtifactTypeDoesNotHaveConstructorWithIdAndGeneration(typeof(TArtifact), source);
return false;
}
artifact = constructor.Invoke(new object[] { artifactId, (Generation)source.Generation }) as TArtifact;
if (artifact == default)
{
error = new CouldNotConvertProtobufArtifact(typeof(TArtifact), source, "Could not create instance of artifact");
return false;
}
error = null;
return true;
}
catch (Exception ex)
{
error = new CouldNotConvertProtobufArtifact(typeof(TArtifact), source, ex.Message);
return false;
}
}
/// <summary>
/// Convert a <see cref="PbArtifact"/> to a <typeparamref name="TArtifact"/>.
/// </summary>
/// <remarks>
/// The method assumes that the <see cref="Artifact{TId}" /> class has a constructor that takes in
/// <see cref="ArtifactId" /> as first parameter and <see cref="Generation" /> as second parameter.
/// It also assumes that the <see cref="ArtifactId" /> type in the <see cref="Artifact{TId}" /> only has the default parameterless constructor.
/// </remarks>
/// <param name="source"><see cref="PbArtifact"/> to convert.</param>
/// <typeparam name="TArtifact">The <see cref="Type" /> of the <see cref="Artifact{TId}" />.</typeparam>
/// <typeparam name="TId">The <see cref="Type" /> of the <see cref="ArtifactId" />.</typeparam>
/// <returns>The converted <see cref="Artifact{TId}"/>.</returns>
public static TArtifact To<TArtifact, TId>(this PbArtifact source)
where TArtifact : Artifact<TId>
where TId : ArtifactId
=> source.TryTo<TArtifact, TId>(out var artifact, out var error)
? artifact
: throw error;
/// <summary>
/// Convert a <see cref="PbArtifact"/> to a tuple with an <see cref="ArtifactId" /> and a <see cref="Generation" />.
/// </summary>
/// <param name="source"><see cref="PbArtifact"/> to convert.</param>
/// <param name="artifact">When the method returns, a tuple with <see cref="ArtifactId" /> and <see cref="Generation" /> if conversion was successful, otherwise null.</param>
/// <param name="error">When the method returns, null if the conversion was successful, otherwise the error that caused the failure.</param>
/// <returns>A value indicating whether or not the conversion was successful.</returns>
public static bool TryToArtifact(this PbArtifact source, out (ArtifactId Id, Generation Generation) artifact, [NotNullWhen(false)] out Exception? error)
{
artifact = default;
if (!source.Id.TryTo<ArtifactId>(out var id, out error))
{
return false;
}
artifact = (id, source.Generation);
return true;
}
/// <summary>
/// Convert a <see cref="PbArtifact"/> to a tuple with an <see cref="ArtifactId" /> and a <see cref="Generation" />.
/// </summary>
/// <param name="source"><see cref="PbArtifact"/> to convert.</param>
/// <returns>A tuple with <see cref="ArtifactId" /> and <see cref="Generation" />.</returns>
public static (ArtifactId Id, Generation Generation) ToArtifact(this PbArtifact source)
=> source.TryToArtifact(out var artifact, out var error)
? artifact
: throw error;
}