diff --git a/src/DagCid.cs b/src/DagCid.cs index b1ade6de..2ed3ac5e 100644 --- a/src/DagCid.cs +++ b/src/DagCid.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using System; +using Newtonsoft.Json; namespace Ipfs { @@ -10,11 +11,32 @@ namespace Ipfs /// public record DagCid { + private Cid _value = null!; + /// /// The value of this DAG link. /// + /// + /// 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. + /// [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; + } + } /// /// Implicit casting of a to a . @@ -25,8 +47,23 @@ public record DagCid /// /// Explicit casting of a to a . /// - /// The to cast." - public static explicit operator DagCid(Cid cid) => new DagCid { Value = cid, }; + /// The to cast. + /// + /// 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. + /// + 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, }; + } /// /// Returns the string representation of the . diff --git a/test/DagCidTest.cs b/test/DagCidTest.cs new file mode 100644 index 00000000..44cf472e --- /dev/null +++ b/test/DagCidTest.cs @@ -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(() => + 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(() => + 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(() => + (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); + } + } +}