Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 41 additions & 4 deletions src/DagCid.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Newtonsoft.Json;
using System;
using Newtonsoft.Json;

namespace Ipfs
{
Expand All @@ -10,11 +11,32 @@ namespace Ipfs
/// </remarks>
public record DagCid
{
private Cid _value = null!;

/// <summary>
/// The <see cref="Cid"/> value of this DAG link.
/// </summary>
/// <exception cref="ArgumentException">
/// Thrown when attempting to set a CID with ContentType "libp2p-key",
/// as IPLD links must be immutable and libp2p-key CIDs represent mutable IPNS addresses.
/// </exception>
[JsonProperty("/")]
public required Cid Value { get; set; }
public required Cid Value
{
get => _value;
set
{
if (value.ContentType == "libp2p-key")
{
throw new ArgumentException(
"Cannot store CID-encoded libp2p key as DagCid link. " +
"IPLD links must be immutable, but libp2p-key CIDs represent mutable IPNS addresses. " +
"Use the resolved content CID instead.",
nameof(value));
}
_value = value;
}
}

/// <summary>
/// Implicit casting of a <see cref="DagCid"/> to a <see cref="Cid"/>.
Expand All @@ -25,8 +47,23 @@ public record DagCid
/// <summary>
/// Explicit casting of a <see cref="Cid"/> to a <see cref="DagCid"/>.
/// </summary>
/// <param name="cid">The <see cref="Cid"/> to cast.</param>"
public static explicit operator DagCid(Cid cid) => new DagCid { Value = cid, };
/// <param name="cid">The <see cref="Cid"/> to cast.</param>
/// <exception cref="ArgumentException">
/// Thrown when attempting to cast a CID with ContentType "libp2p-key",
/// as IPLD links must be immutable and libp2p-key CIDs represent mutable IPNS addresses.
/// </exception>
public static explicit operator DagCid(Cid cid)
{
if (cid.ContentType == "libp2p-key")
{
throw new ArgumentException(
"Cannot cast CID-encoded libp2p key to DagCid. " +
"IPLD links must be immutable, but libp2p-key CIDs represent mutable IPNS addresses. " +
"Use the resolved content CID instead.",
nameof(cid));
}
return new DagCid { Value = cid, };
}

/// <summary>
/// Returns the string representation of the <see cref="DagCid"/>.
Expand Down
112 changes: 112 additions & 0 deletions test/DagCidTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;

namespace Ipfs
{
[TestClass]
public class DagCidTest
{
[TestMethod]
public void Value_ValidCid_SetsSuccessfully()
{
// Arrange
Cid validCid = "QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V";

// Act & Assert
var dagCid = new DagCid { Value = validCid };
Assert.AreEqual(validCid, dagCid.Value);
}

[TestMethod]
public void Value_LibP2pKeyCid_ThrowsArgumentException()
{
// Arrange - using real IPNS key CID that should have libp2p-key content type
Cid libp2pKeyCid = "k51qzi5uqu5dlvj2baxnqndepeb86cbk3ng7n3i46uzyxzyqj2xjonzllnv0v8";

// Verify this CID actually has libp2p-key content type
Assert.AreEqual("libp2p-key", libp2pKeyCid.ContentType);

// Act & Assert
var exception = Assert.ThrowsException<ArgumentException>(() =>
new DagCid { Value = libp2pKeyCid });

Assert.IsTrue(exception.Message.Contains("Cannot store CID-encoded libp2p key as DagCid link"));
Assert.IsTrue(exception.Message.Contains("IPLD links must be immutable"));
Assert.AreEqual("value", exception.ParamName);
}

[TestMethod]
public void Value_LibP2pKeyCid_SetAfterConstruction_ThrowsArgumentException()
{
// Arrange
Cid validCid = "QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V";
// Using another real IPNS key CID
Cid libp2pKeyCid = "k51qzi5uqu5dlvj2baxnqndepeb86cbk3ng7n3i46uzyxzyqj2xjonzllnv0v8";

var dagCid = new DagCid { Value = validCid };

// Act & Assert
var exception = Assert.ThrowsException<ArgumentException>(() =>
dagCid.Value = libp2pKeyCid);

Assert.IsTrue(exception.Message.Contains("Cannot store CID-encoded libp2p key as DagCid link"));
Assert.AreEqual("value", exception.ParamName);
}

[TestMethod]
public void ExplicitCast_ValidCid_CastsSuccessfully()
{
// Arrange
Cid validCid = "QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V";

// Act
var dagCid = (DagCid)validCid;

// Assert
Assert.AreEqual(validCid, dagCid.Value);
}

[TestMethod]
public void ExplicitCast_LibP2pKeyCid_ThrowsArgumentException()
{
// Arrange - using real IPNS key CID that should have libp2p-key content type
Cid libp2pKeyCid = "k51qzi5uqu5dlvj2baxnqndepeb86cbk3ng7n3i46uzyxzyqj2xjonzllnv0v8";

// Act & Assert
var exception = Assert.ThrowsException<ArgumentException>(() =>
(DagCid)libp2pKeyCid);

Assert.IsTrue(exception.Message.Contains("Cannot cast CID-encoded libp2p key to DagCid"));
Assert.IsTrue(exception.Message.Contains("IPLD links must be immutable"));
Assert.AreEqual("cid", exception.ParamName);
}

[TestMethod]
public void ImplicitCast_DagCidToCid_WorksCorrectly()
{
// Arrange
Cid originalCid = "QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V";
var dagCid = new DagCid { Value = originalCid };

// Act
Cid convertedCid = dagCid;

// Assert
Assert.AreEqual(originalCid, convertedCid);
}

[TestMethod]
public void ToString_ReturnsValueToString()
{
// Arrange
Cid cid = "QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V";
var dagCid = new DagCid { Value = cid };

// Act
var result = dagCid.ToString();

// Assert
Assert.AreEqual(cid.ToString(), result);
}
}
}