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
84 changes: 42 additions & 42 deletions src/Agent/API.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 27 additions & 5 deletions src/Candid/CandidConverter.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);
}
}

/// <summary>
Expand All @@ -64,7 +72,7 @@ public CandidTypedValue FromTypedObject<T>(T obj)
where T : notnull
{
CandidValue value = this.FromObjectInternal<T>(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);
}

Expand All @@ -73,7 +81,14 @@ private CandidValue FromObjectInternal<T>(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);
}
}

/// <summary>
Expand Down Expand Up @@ -108,7 +123,14 @@ public OptionalValue<T> ToOptionalObject<T>(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);
}
}

/// <summary>
Expand Down Expand Up @@ -158,5 +180,5 @@ private ICandidValueMapper ResolveUncached(Type type)

}


}
53 changes: 37 additions & 16 deletions src/Candid/Models/Values/CandidValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,16 @@ public CandidPrimitive AsPrimitive()
return this.As<CandidPrimitive>(CandidValueType.Primitive);
}

private CandidPrimitive AsPrimitiveSafeError(PrimitiveType primitiveType)
{
CandidPrimitive? p = this.AsSafe<CandidPrimitive>(CandidValueType.Primitive);
if (p is null)
{
throw new InvalidOperationException($"Cannot convert candid type '{this.Type}' to candid primitive type '{primitiveType}'");
}
return p;
}

/// <summary>
/// Casts the candid value to a vector type. If the type is not a vector, will throw an exception
/// </summary>
Expand Down Expand Up @@ -254,7 +264,7 @@ public OptionalValue<T> AsOptional<T>(Func<CandidValue, T> valueConverter)
/// <returns>A text value</returns>
public string? AsText()
{
return this.AsPrimitive().AsText();
return this.AsPrimitiveSafeError(PrimitiveType.Text).AsText();
}

/// <summary>
Expand All @@ -264,7 +274,7 @@ public OptionalValue<T> AsOptional<T>(Func<CandidValue, T> valueConverter)
/// <returns>An unbounded nat value</returns>
public UnboundedUInt AsNat()
{
return this.AsPrimitive().AsNat();
return this.AsPrimitiveSafeError(PrimitiveType.Nat).AsNat();
}

/// <summary>
Expand All @@ -274,7 +284,7 @@ public UnboundedUInt AsNat()
/// <returns>A nat8 value</returns>
public byte AsNat8()
{
return this.AsPrimitive().AsNat8();
return this.AsPrimitiveSafeError(PrimitiveType.Nat8).AsNat8();
}

/// <summary>
Expand All @@ -284,7 +294,7 @@ public byte AsNat8()
/// <returns>A nat16 value</returns>
public ushort AsNat16()
{
return this.AsPrimitive().AsNat16();
return this.AsPrimitiveSafeError(PrimitiveType.Nat16).AsNat16();
}

/// <summary>
Expand All @@ -294,7 +304,7 @@ public ushort AsNat16()
/// <returns>A nat32 value</returns>
public uint AsNat32()
{
return this.AsPrimitive().AsNat32();
return this.AsPrimitiveSafeError(PrimitiveType.Nat32).AsNat32();
}

/// <summary>
Expand All @@ -304,7 +314,7 @@ public uint AsNat32()
/// <returns>A nat64 value</returns>
public ulong AsNat64()
{
return this.AsPrimitive().AsNat64();
return this.AsPrimitiveSafeError(PrimitiveType.Nat64).AsNat64();
}

/// <summary>
Expand All @@ -314,7 +324,7 @@ public ulong AsNat64()
/// <returns>An unbounded int value</returns>
public UnboundedInt AsInt()
{
return this.AsPrimitive().AsInt();
return this.AsPrimitiveSafeError(PrimitiveType.Int).AsInt();
}

/// <summary>
Expand All @@ -324,7 +334,7 @@ public UnboundedInt AsInt()
/// <returns>An int8 value</returns>
public sbyte AsInt8()
{
return this.AsPrimitive().AsInt8();
return this.AsPrimitiveSafeError(PrimitiveType.Int8).AsInt8();
}


Expand All @@ -335,7 +345,7 @@ public sbyte AsInt8()
/// <returns>An int16 value</returns>
public short AsInt16()
{
return this.AsPrimitive().AsInt16();
return this.AsPrimitiveSafeError(PrimitiveType.Int16).AsInt16();
}


Expand All @@ -346,7 +356,7 @@ public short AsInt16()
/// <returns>An int32 value</returns>
public int AsInt32()
{
return this.AsPrimitive().AsInt32();
return this.AsPrimitiveSafeError(PrimitiveType.Int32).AsInt32();
}


Expand All @@ -357,7 +367,7 @@ public int AsInt32()
/// <returns>An int64 value</returns>
public long AsInt64()
{
return this.AsPrimitive().AsInt64();
return this.AsPrimitiveSafeError(PrimitiveType.Int64).AsInt64();
}

/// <summary>
Expand All @@ -367,7 +377,7 @@ public long AsInt64()
/// <returns>A float32 value</returns>
public float AsFloat32()
{
return this.AsPrimitive().AsFloat32();
return this.AsPrimitiveSafeError(PrimitiveType.Float32).AsFloat32();
}

/// <summary>
Expand All @@ -377,7 +387,7 @@ public float AsFloat32()
/// <returns>A float64 value</returns>
public double AsFloat64()
{
return this.AsPrimitive().AsFloat64();
return this.AsPrimitiveSafeError(PrimitiveType.Float64).AsFloat64();
}

/// <summary>
Expand All @@ -387,7 +397,7 @@ public double AsFloat64()
/// <returns>A bool value</returns>
public bool AsBool()
{
return this.AsPrimitive().AsBool();
return this.AsPrimitiveSafeError(PrimitiveType.Bool).AsBool();
}

/// <summary>
Expand All @@ -397,7 +407,7 @@ public bool AsBool()
/// <returns>A principal value</returns>
public Principal? AsPrincipal()
{
return this.AsPrimitive().AsPrincipal();
return this.AsPrimitiveSafeError(PrimitiveType.Principal).AsPrincipal();
}

/// <summary>
Expand Down Expand Up @@ -610,6 +620,17 @@ internal static T DereferenceType<T>(

private T As<T>(CandidValueType type)
where T : CandidValue
{
T? value = this.AsSafe<T>(type);
if (value is null)
{
throw new InvalidOperationException($"Cannot convert candid type '{this.Type}' to candid type '{type}'");
}
return value;
}

private T? AsSafe<T>(CandidValueType type)
where T : CandidValue
{
if (this.Type != type)
{
Expand All @@ -620,7 +641,7 @@ private T As<T>(CandidValueType type)

return o.Value.As<T>(type);
}
throw new InvalidOperationException($"Cannot convert candid type '{this.Type}' to candid type '{type}'");
return null;
}
return (T)this;
}
Expand Down
26 changes: 26 additions & 0 deletions test/Candid.Tests/CandidConverterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<CandidTag, CandidValue>
{
["test"] = CandidValue.Text("test")
});
Assert.Throws<Exception>(() => CandidConverter.Default.ToObject<string>(recordValue));
}

[Fact]
public void ToObject__Invalid_Map_Record_Field__Throws()
{
CandidTag stringFieldName = CandidTag.FromName("StringField");
CandidTag intFieldName = CandidTag.FromName("IntField");
var fields = new Dictionary<CandidTag, CandidValue>
{
{stringFieldName, new CandidOptional(CandidValue.Int64(1))}, // Wrong type/value
{intFieldName, CandidValue.Int32(2)}
};
CandidRecord recordValue = new (fields);


Assert.Throws<Exception>(() => CandidConverter.Default.ToObject<RecordClass>(recordValue));
}


private void Test<T>(T raw, CandidTypedValue candid, Func<T, T, bool> areEqual)
where T : notnull
Expand Down