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