diff --git a/src/Agent/API.xml b/src/Agent/API.xml
index b32fd7c..65fbf36 100644
--- a/src/Agent/API.xml
+++ b/src/Agent/API.xml
@@ -4,6 +4,48 @@
EdjCase.ICP.Agent
+
+
+ An `IAgent` implementation using HTTP to make requests to the IC
+
+
+
+
+ The identity that will be used on each request unless overriden
+ This identity can be anonymous
+
+
+
+ Optional. Identity to use for each request. If unspecified, will use anonymous identity
+ Optional. Bls crypto implementation to validate signatures. If unspecified, will use default implementation
+ Optional. Sets the http client to use, otherwise will use the default http client
+
+
+ Optional. Identity to use for each request. If unspecified, will use anonymous identity
+ Optional. Bls crypto implementation to validate signatures. If unspecified, will use default implementation
+ Url to the boundry node to connect to. Defaults to `https://ic0.app/`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
The default http client to use with the built in `HttpClient`
@@ -72,48 +114,6 @@
-
-
- An `IAgent` implementation using HTTP to make requests to the IC
-
-
-
-
- The identity that will be used on each request unless overriden
- This identity can be anonymous
-
-
-
- Optional. Identity to use for each request. If unspecified, will use anonymous identity
- Optional. Bls crypto implementation to validate signatures. If unspecified, will use default implementation
- Optional. Sets the http client to use, otherwise will use the default http client
-
-
- Optional. Identity to use for each request. If unspecified, will use anonymous identity
- Optional. Bls crypto implementation to validate signatures. If unspecified, will use default implementation
- Url to the boundry node to connect to. Defaults to `https://ic0.app/`
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
An agent is used to communicate with the Internet Computer with certain protocols that
diff --git a/src/Candid/CandidConverter.cs b/src/Candid/CandidConverter.cs
index 039b6e7..111e48b 100644
--- a/src/Candid/CandidConverter.cs
+++ b/src/Candid/CandidConverter.cs
@@ -1,3 +1,4 @@
+using EdjCase.ICP.Candid.Exceptions;
using EdjCase.ICP.Candid.Mapping;
using EdjCase.ICP.Candid.Mapping.Mappers;
using EdjCase.ICP.Candid.Models;
@@ -51,7 +52,14 @@ public CandidValue FromObject(object obj)
}
ICandidValueMapper mapper = this.ResolveMapper(obj.GetType());
- return mapper!.Map(obj, this);
+ try
+ {
+ return mapper!.Map(obj, this);
+ }
+ catch (Exception e)
+ {
+ throw new Exception($"Failed to convert object '{obj.GetType().FullName}' to candid value", e);
+ }
}
///
@@ -64,7 +72,7 @@ public CandidTypedValue FromTypedObject(T obj)
where T : notnull
{
CandidValue value = this.FromObjectInternal(obj, out ICandidValueMapper mapper);
- CandidType type = mapper.GetMappedCandidType(typeof(T)) ?? throw new InvalidOperationException("Type does not map");
+ CandidType type = mapper.GetMappedCandidType(typeof(T)) ?? throw new InvalidOperationException($"Type '{typeof(T).FullName}' does not map to a candid type");
return new CandidTypedValue(value, type);
}
@@ -73,7 +81,14 @@ private CandidValue FromObjectInternal(object obj, out ICandidValueMapper map
{
mapper = this.ResolveMapper(typeof(T));
- return mapper!.Map(obj, this);
+ try
+ {
+ return mapper.Map(obj, this);
+ }
+ catch (Exception e)
+ {
+ throw new Exception($"Failed to convert object '{typeof(T).FullName}' to candid value", e);
+ }
}
///
@@ -108,7 +123,14 @@ public OptionalValue ToOptionalObject(CandidOptional value)
public object ToObject(Type objType, CandidValue value)
{
ICandidValueMapper mapper = this.ResolveMapper(objType);
- return mapper!.Map(value, this);
+ try
+ {
+ return mapper!.Map(value, this);
+ }
+ catch (Exception e)
+ {
+ throw new Exception($"Failed to convert candid value '{value}' to object of type {objType.FullName}", e);
+ }
}
///
@@ -158,5 +180,5 @@ private ICandidValueMapper ResolveUncached(Type type)
}
-
+
}
diff --git a/src/Candid/Models/Values/CandidValue.cs b/src/Candid/Models/Values/CandidValue.cs
index 11eab83..44584b3 100644
--- a/src/Candid/Models/Values/CandidValue.cs
+++ b/src/Candid/Models/Values/CandidValue.cs
@@ -99,6 +99,16 @@ public CandidPrimitive AsPrimitive()
return this.As(CandidValueType.Primitive);
}
+ private CandidPrimitive AsPrimitiveSafeError(PrimitiveType primitiveType)
+ {
+ CandidPrimitive? p = this.AsSafe(CandidValueType.Primitive);
+ if (p is null)
+ {
+ throw new InvalidOperationException($"Cannot convert candid type '{this.Type}' to candid primitive type '{primitiveType}'");
+ }
+ return p;
+ }
+
///
/// Casts the candid value to a vector type. If the type is not a vector, will throw an exception
///
@@ -254,7 +264,7 @@ public OptionalValue AsOptional(Func valueConverter)
/// A text value
public string? AsText()
{
- return this.AsPrimitive().AsText();
+ return this.AsPrimitiveSafeError(PrimitiveType.Text).AsText();
}
///
@@ -264,7 +274,7 @@ public OptionalValue AsOptional(Func valueConverter)
/// An unbounded nat value
public UnboundedUInt AsNat()
{
- return this.AsPrimitive().AsNat();
+ return this.AsPrimitiveSafeError(PrimitiveType.Nat).AsNat();
}
///
@@ -274,7 +284,7 @@ public UnboundedUInt AsNat()
/// A nat8 value
public byte AsNat8()
{
- return this.AsPrimitive().AsNat8();
+ return this.AsPrimitiveSafeError(PrimitiveType.Nat8).AsNat8();
}
///
@@ -284,7 +294,7 @@ public byte AsNat8()
/// A nat16 value
public ushort AsNat16()
{
- return this.AsPrimitive().AsNat16();
+ return this.AsPrimitiveSafeError(PrimitiveType.Nat16).AsNat16();
}
///
@@ -294,7 +304,7 @@ public ushort AsNat16()
/// A nat32 value
public uint AsNat32()
{
- return this.AsPrimitive().AsNat32();
+ return this.AsPrimitiveSafeError(PrimitiveType.Nat32).AsNat32();
}
///
@@ -304,7 +314,7 @@ public uint AsNat32()
/// A nat64 value
public ulong AsNat64()
{
- return this.AsPrimitive().AsNat64();
+ return this.AsPrimitiveSafeError(PrimitiveType.Nat64).AsNat64();
}
///
@@ -314,7 +324,7 @@ public ulong AsNat64()
/// An unbounded int value
public UnboundedInt AsInt()
{
- return this.AsPrimitive().AsInt();
+ return this.AsPrimitiveSafeError(PrimitiveType.Int).AsInt();
}
///
@@ -324,7 +334,7 @@ public UnboundedInt AsInt()
/// An int8 value
public sbyte AsInt8()
{
- return this.AsPrimitive().AsInt8();
+ return this.AsPrimitiveSafeError(PrimitiveType.Int8).AsInt8();
}
@@ -335,7 +345,7 @@ public sbyte AsInt8()
/// An int16 value
public short AsInt16()
{
- return this.AsPrimitive().AsInt16();
+ return this.AsPrimitiveSafeError(PrimitiveType.Int16).AsInt16();
}
@@ -346,7 +356,7 @@ public short AsInt16()
/// An int32 value
public int AsInt32()
{
- return this.AsPrimitive().AsInt32();
+ return this.AsPrimitiveSafeError(PrimitiveType.Int32).AsInt32();
}
@@ -357,7 +367,7 @@ public int AsInt32()
/// An int64 value
public long AsInt64()
{
- return this.AsPrimitive().AsInt64();
+ return this.AsPrimitiveSafeError(PrimitiveType.Int64).AsInt64();
}
///
@@ -367,7 +377,7 @@ public long AsInt64()
/// A float32 value
public float AsFloat32()
{
- return this.AsPrimitive().AsFloat32();
+ return this.AsPrimitiveSafeError(PrimitiveType.Float32).AsFloat32();
}
///
@@ -377,7 +387,7 @@ public float AsFloat32()
/// A float64 value
public double AsFloat64()
{
- return this.AsPrimitive().AsFloat64();
+ return this.AsPrimitiveSafeError(PrimitiveType.Float64).AsFloat64();
}
///
@@ -387,7 +397,7 @@ public double AsFloat64()
/// A bool value
public bool AsBool()
{
- return this.AsPrimitive().AsBool();
+ return this.AsPrimitiveSafeError(PrimitiveType.Bool).AsBool();
}
///
@@ -397,7 +407,7 @@ public bool AsBool()
/// A principal value
public Principal? AsPrincipal()
{
- return this.AsPrimitive().AsPrincipal();
+ return this.AsPrimitiveSafeError(PrimitiveType.Principal).AsPrincipal();
}
///
@@ -610,6 +620,17 @@ internal static T DereferenceType(
private T As(CandidValueType type)
where T : CandidValue
+ {
+ T? value = this.AsSafe(type);
+ if (value is null)
+ {
+ throw new InvalidOperationException($"Cannot convert candid type '{this.Type}' to candid type '{type}'");
+ }
+ return value;
+ }
+
+ private T? AsSafe(CandidValueType type)
+ where T : CandidValue
{
if (this.Type != type)
{
@@ -620,7 +641,7 @@ private T As(CandidValueType type)
return o.Value.As(type);
}
- throw new InvalidOperationException($"Cannot convert candid type '{this.Type}' to candid type '{type}'");
+ return null;
}
return (T)this;
}
diff --git a/test/Candid.Tests/CandidConverterTests.cs b/test/Candid.Tests/CandidConverterTests.cs
index 870746f..845dd88 100644
--- a/test/Candid.Tests/CandidConverterTests.cs
+++ b/test/Candid.Tests/CandidConverterTests.cs
@@ -802,6 +802,32 @@ public void RawVariant__NoMapping()
Assert.Equal(raw2.Value, actual2.Value);
}
+ [Fact]
+ public void ToObject__Invalid_Map_Text_Record__Throws()
+ {
+ CandidRecord recordValue = new (new Dictionary
+ {
+ ["test"] = CandidValue.Text("test")
+ });
+ Assert.Throws(() => CandidConverter.Default.ToObject(recordValue));
+ }
+
+ [Fact]
+ public void ToObject__Invalid_Map_Record_Field__Throws()
+ {
+ CandidTag stringFieldName = CandidTag.FromName("StringField");
+ CandidTag intFieldName = CandidTag.FromName("IntField");
+ var fields = new Dictionary
+ {
+ {stringFieldName, new CandidOptional(CandidValue.Int64(1))}, // Wrong type/value
+ {intFieldName, CandidValue.Int32(2)}
+ };
+ CandidRecord recordValue = new (fields);
+
+
+ Assert.Throws(() => CandidConverter.Default.ToObject(recordValue));
+ }
+
private void Test(T raw, CandidTypedValue candid, Func areEqual)
where T : notnull