Skip to content

Commit

Permalink
Refactor Connection class, QueryFailure and FaunaException, etc. (#23)
Browse files Browse the repository at this point in the history
* Commit the things

* Connection.DoPostAsync return QueryResponse
  • Loading branch information
adambollen authored Dec 12, 2023
1 parent 7deec7d commit 2e26622
Show file tree
Hide file tree
Showing 10 changed files with 316 additions and 117 deletions.
150 changes: 148 additions & 2 deletions Fauna.Test/Client.Tests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
using System.Buffers;
using System.Net;
using System.Text;
using Fauna.Constants;
using Fauna.Serialization;
using NUnit.Framework;
using Telerik.JustMock;

namespace Fauna.Test;

Expand All @@ -19,6 +22,7 @@ private void Write(string json)
}

[Test]
[Ignore("connected test")]
public async Task CreateClient()
{
var t = new { data = new { data = Array.Empty<object>() } };
Expand All @@ -33,8 +37,150 @@ public async Task CreateClient()
});
var r = await c.QueryAsync<string>(
"let x = 123; x",
new QueryOptions { QueryTags = new Dictionary<string, string> { { "foo", "bar" }, {"baz", "luhrmann"} }});
new QueryOptions { QueryTags = new Dictionary<string, string> { { "foo", "bar" }, { "baz", "luhrmann" } } });
Write(r.Data);
Console.WriteLine(string.Join(',',r.QueryTags!.Select(kv => $"{kv.Key}={kv.Value}")));
Console.WriteLine(string.Join(',', r.QueryTags!.Select(kv => $"{kv.Key}={kv.Value}")));
}

[Test]
[Ignore("connected test")]
public async Task CreateClientError()
{
var t = new { data = new { data = Array.Empty<object>() } };
var c = new Client(
new ClientConfig("secret")
{
Endpoint = Endpoints.Local,
DefaultQueryOptions = new QueryOptions
{
QueryTags = new Dictionary<string, string> { { "lorem", "ipsum" } }
}
});

try
{
var r = await c.QueryAsync<string>(
"let x = 123; abort(x)",
new QueryOptions { QueryTags = new Dictionary<string, string> { { "foo", "bar" }, { "baz", "luhrmann" } } });
}
catch (FaunaException ex)
{
Assert.AreEqual("abort", ex.QueryFailure.ErrorInfo.Code);
var abortNum = GetIntFromReader(ex.QueryFailure.ErrorInfo.Abort.ToString()!);
Assert.AreEqual(123, abortNum);
Console.WriteLine(ex.QueryFailure.Summary);
}
}

[Test]
public async Task AbortReturnsQueryFailureAndThrows()
{
var responseBody = @"{
""error"": {
""code"": ""testAbort"",
""message"": ""Query aborted."",
""abort"": ""123""
},
""summary"": ""error: Query aborted.\nat *query*:1:19\n |\n1 | let x = 123; abort(x)\n | ^^^\n |"",
""txn_ts"": 1702346199930000,
""stats"": {
""compute_ops"": 1,
""read_ops"": 0,
""write_ops"": 0,
""query_time_ms"": 105,
""contention_retries"": 0,
""storage_bytes_read"": 261,
""storage_bytes_write"": 0,
""rate_limits_hit"": []
},
""schema_version"": 0
}";
var testMessage = new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(responseBody)
};
var qr = await QueryResponse.GetFromHttpResponseAsync<string>(testMessage);
var conn = Mock.Create<IConnection>();
Mock.Arrange(() =>
conn.DoPostAsync<string>(
Arg.IsAny<string>(),
Arg.IsAny<string>(),
Arg.IsAny<Dictionary<string, string>>()
)
).Returns(Task.FromResult(qr));

var c = new Client(new ClientConfig("secret"), conn);

try
{
var r = await c.QueryAsync<string>("let x = 123; abort(x)");
}
catch (FaunaException ex)
{
Assert.AreEqual("testAbort", ex.QueryFailure.ErrorInfo.Code);
}
}

[Test]
public async Task LastSeenTxnPropagatesToSubsequentQueries()
{
var responseBody = @"{
""data"": ""123"",
""static_type"": ""123"",
""summary"": ""All good"",
""txn_ts"": 1702346199930000,
""stats"": {
""compute_ops"": 1,
""read_ops"": 0,
""write_ops"": 0,
""query_time_ms"": 105,
""contention_retries"": 0,
""storage_bytes_read"": 261,
""storage_bytes_write"": 0,
""rate_limits_hit"": []
},
""schema_version"": 0
}";
var testMessage = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(responseBody)
};
var qr = await QueryResponse.GetFromHttpResponseAsync<string>(testMessage);
var conn = Mock.Create<IConnection>();
Mock.Arrange(() =>
conn.DoPostAsync<string>(
Arg.IsAny<string>(),
Arg.IsAny<string>(),
Arg.IsAny<Dictionary<string, string>>()
)
).Returns(Task.FromResult(qr));

var c = new Client(new ClientConfig("secret"), conn);
var r = await c.QueryAsync<string>("let x = 123; x");

bool check = false;

Mock.Arrange(() =>
conn.DoPostAsync<string>(
Arg.IsAny<string>(),
Arg.IsAny<string>(),
Arg.IsAny<Dictionary<string, string>>()
)
).DoInstead((string path, string body, Dictionary<string, string> headers) =>
{
Assert.AreEqual(1702346199930000.ToString(), headers[Headers.LastTxnTs]);
check = true;
}).Returns(Task.FromResult(qr));

var r2 = await c.QueryAsync<string>("let x = 123; x");

Assert.IsTrue(check);
}

private static int GetIntFromReader(string input)
{
var reader = new Utf8FaunaReader(input);
reader.Read();
return reader.GetInt();
}
}
84 changes: 63 additions & 21 deletions Fauna/Client/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,27 @@ namespace Fauna;

public class Client
{
private const string QueryUriPath = "/query/1";

private readonly ClientConfig _config;
private readonly IConnection _connection;

public long LastSeenTxn { get; private set; }

public Client(string secret) :
this(new ClientConfig(secret))
{
}

public Client(ClientConfig config, HttpClient? httpClient = null)
public Client(ClientConfig config) :
this(config, new Connection(config.Endpoint, config.ConnectionTimeout))
{
var connectionHttpClient = httpClient ?? new HttpClient();
connectionHttpClient.Timeout = config.ConnectionTimeout;
}

// Initialize the connection
_connection = new Connection(config, connectionHttpClient);
public Client(ClientConfig config, IConnection connection)
{
_config = config;
_connection = connection;
}

public async Task<QuerySuccess<T>> QueryAsync<T>(
Expand All @@ -26,38 +33,73 @@ public async Task<QuerySuccess<T>> QueryAsync<T>(
{
if (string.IsNullOrEmpty(fql)) throw new ArgumentException("The provided FQL query is null.");

var response = await _connection.DoRequestAsync(fql, queryOptions);
var finalOptions = QueryOptions.GetFinalQueryOptions(_config.DefaultQueryOptions, queryOptions);
var headers = GetRequestHeaders(finalOptions);

var queryResponse = await GetQueryResponseAsync<T>(response);
var queryResponse = await _connection.DoPostAsync<T>(QueryUriPath, fql, headers);

if (queryResponse is QueryFailure)
if (queryResponse is QueryFailure failure)
{
throw new Exception("Query failure");
throw new FaunaException(failure, "Query failure");
}
else
{
LastSeenTxn = queryResponse.LastSeenTxn;
}

return (QuerySuccess<T>)queryResponse;
}

// ProcessResponse method
private async Task<QueryResponse> GetQueryResponseAsync<T>(HttpResponseMessage response) where T : class
private Dictionary<string, string> GetRequestHeaders(QueryOptions? queryOptions)
{
QueryResponse queryResponse;
var headers = new Dictionary<string, string>
{

var statusCode = response.StatusCode;
var body = await response.Content.ReadAsStringAsync();
var headers = response.Headers;
{ Headers.Authorization, $"Bearer {_config.Secret}"},
{ Headers.Format, "tagged" },
{ Headers.Driver, "C#" }
};

if (!response.IsSuccessStatusCode)
if (LastSeenTxn > long.MinValue)
{
queryResponse = new QueryFailure(body);
headers.Add(Headers.LastTxnTs, LastSeenTxn.ToString());
}
else

if (queryOptions != null)
{
queryResponse = new QuerySuccess<T>(body);
if (queryOptions.QueryTimeout.HasValue)
{
headers.Add(
Headers.QueryTimeoutMs,
queryOptions.QueryTimeout.Value.TotalMilliseconds.ToString());
}

if (queryOptions.QueryTags != null)
{
headers.Add(Headers.QueryTags, EncodeQueryTags(queryOptions.QueryTags));
}

if (!string.IsNullOrEmpty(queryOptions.TraceParent))
{
headers.Add(Headers.TraceParent, queryOptions.TraceParent);
}

_connection.LastSeenTxn = queryResponse.LastSeenTxn;
if (queryOptions.Linearized != null)
{
headers.Add(Headers.Linearized, queryOptions.Linearized.ToString()!);
}

if (queryOptions.TypeCheck != null)
{
headers.Add(Headers.TypeCheck, queryOptions.TypeCheck.ToString()!);
}
}

return queryResponse;
return headers;
}

private static string EncodeQueryTags(Dictionary<string, string> tags)
{
return string.Join(",", tags.Select(entry => entry.Key + "=" + entry.Value));
}
}
Loading

0 comments on commit 2e26622

Please sign in to comment.