Skip to content

Commit 67bb1e0

Browse files
authored
Handle V3 error response (#144)
1 parent d9c8ef0 commit 67bb1e0

3 files changed

Lines changed: 98 additions & 31 deletions

File tree

src/Agent/API.xml

Lines changed: 12 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Agent/Agents/HttpAgent.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,25 @@ public async Task<CandidArg> CallAsync(
103103
byte[] cborBytes = await httpResponse.GetContentAsync();
104104
var reader = new CborReader(cborBytes);
105105
V3CallResponse v3CallResponse = V3CallResponse.ReadCbor(reader);
106+
Certificate certificate;
107+
switch (v3CallResponse.Status)
108+
{
109+
case V3CallResponse.StatusType.Replied:
110+
certificate = v3CallResponse.AsReplied();
111+
break;
112+
case V3CallResponse.StatusType.NonReplicatedRejection:
113+
CallRejectedResponse callRejectedResponse = v3CallResponse.AsNonReplicatedRejection();
114+
throw new CallRejectedException(callRejectedResponse.Code, callRejectedResponse.Message, callRejectedResponse.ErrorCode);
115+
default:
116+
throw new NotImplementedException($"Invalid v3 call response status '{v3CallResponse.Status}'");
117+
}
106118

107119
SubjectPublicKeyInfo rootPublicKey = await this.GetRootKeyAsync(cancellationToken);
108-
if (!v3CallResponse.Certificate.IsValid(this.bls, rootPublicKey))
120+
if (!certificate.IsValid(this.bls, rootPublicKey))
109121
{
110122
throw new InvalidCertificateException("Certificate signature does not match the IC public key");
111123
}
112-
HashTree? requestStatusData = v3CallResponse.Certificate.Tree.GetValueOrDefault(StatePath.FromSegments("request_status", requestId.RawValue));
124+
HashTree? requestStatusData = certificate.Tree.GetValueOrDefault(StatePath.FromSegments("request_status", requestId.RawValue));
113125
RequestStatus? requestStatus = ParseRequestStatus(requestStatusData);
114126
switch (requestStatus?.Type)
115127
{
Lines changed: 72 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Diagnostics;
34
using System.Formats.Cbor;
45
using EdjCase.ICP.Agent.Models;
@@ -9,35 +10,63 @@ namespace EdjCase.ICP.Agent.Responses
910
/// <summary>
1011
/// Model for a reponse to a read state request
1112
/// </summary>
12-
public class V3CallResponse
13+
internal class V3CallResponse
1314
{
15+
public enum StatusType
16+
{
17+
/// <summary>
18+
/// The request has been processed and it has reply data
19+
/// </summary>
20+
Replied,
21+
NonReplicatedRejection
22+
}
23+
24+
1425
/// <summary>
15-
/// The status of the call ('replied', 'rejected', 'done)
26+
/// The status of the call ('replied', 'non_replicated_rejection')
1627
/// </summary>
17-
public string Status { get; }
28+
public StatusType Status { get; }
29+
30+
private object? value { get; }
31+
32+
/// <param name="status">The status of the call ('replied', 'non_replicated_rejection')</param>
33+
/// <param name="value">The certificate data of the current canister state</param>
34+
private V3CallResponse(StatusType status, object? value)
35+
{
36+
this.Status = status;
37+
this.value = value;
38+
}
39+
1840
/// <summary>
19-
/// The certificate data of the current canister state
41+
/// Returns the 'replied' certificate IF the status is 'replied', otherwise throws exception
2042
/// </summary>
21-
public Certificate Certificate { get; }
43+
public Certificate AsReplied()
44+
{
45+
this.ValidateType(StatusType.Replied);
46+
return (Certificate)this.value!;
47+
}
2248

23-
/// <param name="status">The status of the call ('replied', 'rejected', 'done)</param>
24-
/// <param name="certificate">The certificate data of the current canister state</param>
25-
/// <exception cref="ArgumentNullException"></exception>
26-
public V3CallResponse(string status, Certificate certificate)
49+
public CallRejectedResponse AsNonReplicatedRejection()
2750
{
28-
this.Status = status ?? throw new ArgumentNullException(nameof(status));
29-
this.Certificate = certificate ?? throw new ArgumentNullException(nameof(certificate));
51+
this.ValidateType(StatusType.NonReplicatedRejection);
52+
return (CallRejectedResponse)this.value!;
3053
}
3154

55+
private void ValidateType(StatusType type)
56+
{
57+
if (this.Status != type)
58+
{
59+
throw new InvalidOperationException($"Expected status '{type}' but was '{this.Status}'");
60+
}
61+
}
3262

3363
internal static V3CallResponse ReadCbor(CborReader reader)
3464
{
3565
if (reader.ReadTag() != CborTag.SelfDescribeCbor)
3666
{
3767
throw new CborContentException("Expected self describe tag");
3868
}
39-
Certificate? certificate = null;
40-
string? status = null;
69+
Dictionary<string, object> map = new ();
4170
reader.ReadStartMap();
4271
while (reader.PeekState() != CborReaderState.EndMap)
4372
{
@@ -46,10 +75,19 @@ internal static V3CallResponse ReadCbor(CborReader reader)
4675
{
4776
case "certificate":
4877
var certReader = new CborReader(reader.ReadByteString());
49-
certificate = Certificate.FromCbor(certReader);
78+
map["certificate"] = Certificate.FromCbor(certReader);
5079
break;
5180
case "status":
52-
status = reader.ReadTextString();
81+
map["status"] = reader.ReadTextString();
82+
break;
83+
case "reject_code":
84+
map["reject_code"] = (RejectCode)reader.ReadUInt64();
85+
break;
86+
case "reject_message":
87+
map["reject_message"] = reader.ReadTextString();
88+
break;
89+
case "error_code":
90+
map["error_code"] = reader.ReadTextString();
5391
break;
5492
default:
5593
Debug.WriteLine($"Unknown field '{field}' in v3 call response");
@@ -59,17 +97,30 @@ internal static V3CallResponse ReadCbor(CborReader reader)
5997
}
6098
reader.ReadEndMap();
6199

62-
if (status == null)
100+
if (map["status"] == null)
63101
{
64102
throw new CborContentException("Missing field: status");
65103
}
104+
StatusType status = Enum.Parse<StatusType>((string)map["status"], true);
66105

67-
if (certificate == null)
106+
switch (status)
68107
{
69-
throw new CborContentException("Missing field: certificate");
108+
case StatusType.Replied:
109+
Certificate? certificate = map["certificate"] as Certificate;
110+
if (certificate == null)
111+
{
112+
throw new CborContentException("Missing field: certificate");
113+
}
114+
return new V3CallResponse(status, certificate);
115+
case StatusType.NonReplicatedRejection:
116+
RejectCode rejectCode = (RejectCode)(map["reject_code"] ?? throw new CborContentException("Missing field: reject_code"));
117+
string message = (string)(map["reject_message"] ?? throw new CborContentException("Missing field: reject_message"));
118+
string? errorCode = (string?)map["error_code"];
119+
CallRejectedResponse? rejectedResponse = new CallRejectedResponse(rejectCode, message, errorCode);
120+
return new V3CallResponse(status, rejectedResponse);
121+
default:
122+
throw new NotImplementedException($"Unknown status '{status}' in v3 call response");
70123
}
71-
72-
return new V3CallResponse(status, certificate);
73124
}
74125
}
75-
}
126+
}

0 commit comments

Comments
 (0)