Skip to content

Commit 381b108

Browse files
authored
(#32) Basic client and options. (#45)
1 parent f5ee077 commit 381b108

14 files changed

+619
-4
lines changed

Datasync.Toolkit.sln

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Datasync.S
5555
EndProject
5656
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Datasync.Server.NSwag.Test", "tests\CommunityToolkit.Datasync.Server.NSwag.Test\CommunityToolkit.Datasync.Server.NSwag.Test.csproj", "{983FB40E-BA00-4055-9A8A-24E1A351FB5B}"
5757
EndProject
58-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Datasync.Server.Swashbuckle.Test", "tests\CommunityToolkit.Datasync.Server.Swashbuckle.Test\CommunityToolkit.Datasync.Server.Swashbuckle.Test.csproj", "{F578EC54-454F-4114-AC37-C83A7831E783}"
58+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Datasync.Server.Swashbuckle.Test", "tests\CommunityToolkit.Datasync.Server.Swashbuckle.Test\CommunityToolkit.Datasync.Server.Swashbuckle.Test.csproj", "{F578EC54-454F-4114-AC37-C83A7831E783}"
5959
EndProject
60-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Datasync.Server.Swashbuckle", "src\CommunityToolkit.Datasync.Server.Swashbuckle\CommunityToolkit.Datasync.Server.Swashbuckle.csproj", "{45D47A4E-AD58-40C8-B4CC-95BC888C47A7}"
60+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Datasync.Server.Swashbuckle", "src\CommunityToolkit.Datasync.Server.Swashbuckle\CommunityToolkit.Datasync.Server.Swashbuckle.csproj", "{45D47A4E-AD58-40C8-B4CC-95BC888C47A7}"
61+
EndProject
62+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Datasync.Client", "src\CommunityToolkit.Datasync.Client\CommunityToolkit.Datasync.Client.csproj", "{D3B72031-D4BD-44D3-973C-2752AB1570F6}"
63+
EndProject
64+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Datasync.Client.Test", "tests\CommunityToolkit.Datasync.Client.Test\CommunityToolkit.Datasync.Client.Test.csproj", "{2889E6B2-9CD1-437C-A43C-98CFAFF68B99}"
6165
EndProject
6266
Global
6367
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -145,6 +149,14 @@ Global
145149
{45D47A4E-AD58-40C8-B4CC-95BC888C47A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
146150
{45D47A4E-AD58-40C8-B4CC-95BC888C47A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
147151
{45D47A4E-AD58-40C8-B4CC-95BC888C47A7}.Release|Any CPU.Build.0 = Release|Any CPU
152+
{D3B72031-D4BD-44D3-973C-2752AB1570F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
153+
{D3B72031-D4BD-44D3-973C-2752AB1570F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
154+
{D3B72031-D4BD-44D3-973C-2752AB1570F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
155+
{D3B72031-D4BD-44D3-973C-2752AB1570F6}.Release|Any CPU.Build.0 = Release|Any CPU
156+
{2889E6B2-9CD1-437C-A43C-98CFAFF68B99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
157+
{2889E6B2-9CD1-437C-A43C-98CFAFF68B99}.Debug|Any CPU.Build.0 = Debug|Any CPU
158+
{2889E6B2-9CD1-437C-A43C-98CFAFF68B99}.Release|Any CPU.ActiveCfg = Release|Any CPU
159+
{2889E6B2-9CD1-437C-A43C-98CFAFF68B99}.Release|Any CPU.Build.0 = Release|Any CPU
148160
EndGlobalSection
149161
GlobalSection(SolutionProperties) = preSolution
150162
HideSolutionNode = FALSE
@@ -170,6 +182,8 @@ Global
170182
{983FB40E-BA00-4055-9A8A-24E1A351FB5B} = {D59F1489-5D74-4F52-B78B-88037EAB2838}
171183
{F578EC54-454F-4114-AC37-C83A7831E783} = {D59F1489-5D74-4F52-B78B-88037EAB2838}
172184
{45D47A4E-AD58-40C8-B4CC-95BC888C47A7} = {84AD662A-4B9E-4E64-834D-72529FB7FCE5}
185+
{D3B72031-D4BD-44D3-973C-2752AB1570F6} = {84AD662A-4B9E-4E64-834D-72529FB7FCE5}
186+
{2889E6B2-9CD1-437C-A43C-98CFAFF68B99} = {D59F1489-5D74-4F52-B78B-88037EAB2838}
173187
EndGlobalSection
174188
GlobalSection(ExtensibilityGlobals) = postSolution
175189
SolutionGuid = {78A935E9-8F14-448A-BEDF-360FB742F14E}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<Description>The client capabilities for developing applications using the Datasync Toolkit.</Description>
4+
</PropertyGroup>
5+
6+
<Import Project="..\Shared.Build.props" />
7+
8+
<ItemGroup>
9+
<InternalsVisibleTo Include="CommunityToolkit.Datasync.Client.Test"/>
10+
</ItemGroup>
11+
12+
<ItemGroup>
13+
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
14+
</ItemGroup>
15+
16+
<ItemGroup>
17+
<ProjectReference Include="..\CommunityToolkit.Datasync.Common\CommunityToolkit.Datasync.Common.csproj" />
18+
</ItemGroup>
19+
20+
</Project>
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
#pragma warning disable IDE0058 // Expression value is never used
6+
7+
using CommunityToolkit.Datasync.Client.Http;
8+
using CommunityToolkit.Datasync.Common;
9+
using System.Diagnostics.CodeAnalysis;
10+
11+
namespace CommunityToolkit.Datasync.Client;
12+
13+
/// <summary>
14+
/// A client that provides access to a datasync service.
15+
/// </summary>
16+
public class DatasyncClient : IDisposable
17+
{
18+
/// <summary>
19+
/// This is for unit testing only.
20+
/// </summary>
21+
[ExcludeFromCodeCoverage]
22+
protected DatasyncClient()
23+
{
24+
ClientOptions = new DatasyncClientOptions();
25+
Endpoint = new Uri("http://localhost/");
26+
HttpClientFactory = new DefaultHttpClientFactory(Endpoint, new HttpClientOptions());
27+
}
28+
29+
/// <summary>
30+
/// Creates a new <see cref="DatasyncClient"/> that connects to the specified endpoint for datasync operations.
31+
/// </summary>
32+
/// <param name="endpoint">The endpoint of the datasync service.</param>
33+
/// <exception cref="UriFormatException">if the endpoint is not a valid datasync Uri.</exception>
34+
public DatasyncClient(string endpoint) : this(new Uri(endpoint, UriKind.Absolute), new DatasyncClientOptions())
35+
{
36+
}
37+
38+
/// <summary>
39+
/// Creates a new <see cref="DatasyncClient"/> that connects to the specified endpoint for datasync operations.
40+
/// </summary>
41+
/// <param name="endpoint">The endpoint of the datasync service.</param>
42+
/// <exception cref="UriFormatException">if the endpoint is not a valid datasync Uri.</exception>
43+
public DatasyncClient(Uri endpoint) : this(endpoint, new DatasyncClientOptions())
44+
{
45+
}
46+
47+
/// <summary>
48+
/// Creates a new <see cref="DatasyncClient"/> that connects to the specified endpoint for datasync operations.
49+
/// </summary>
50+
/// <param name="endpoint">The endpoint of the datasync service.</param>
51+
/// <param name="options">The options to use for handling the connection to the datasync service.</param>
52+
/// <exception cref="UriFormatException">if the endpoint is not a valid datasync Uri.</exception>
53+
public DatasyncClient(string endpoint, DatasyncClientOptions options) : this(new Uri(endpoint, UriKind.Absolute), options)
54+
{
55+
}
56+
57+
/// <summary>
58+
/// Creates a new <see cref="DatasyncClient"/> that connects to the specified endpoint for datasync operations.
59+
/// </summary>
60+
/// <param name="endpoint">The endpoint of the datasync service.</param>
61+
/// <param name="options">The options to use for handling the connection to the datasync service.</param>
62+
/// <exception cref="UriFormatException">if the endpoint is not a valid datasync Uri.</exception>
63+
public DatasyncClient(Uri endpoint, DatasyncClientOptions options)
64+
{
65+
Ensure.That(endpoint, nameof(endpoint)).IsNotNull().And.IsDatasyncEndpoint();
66+
Ensure.That(options, nameof(options)).IsNotNull();
67+
68+
ClientOptions = options;
69+
ServiceOptions = options.DatasyncServiceOptions;
70+
Endpoint = NormalizeEndpoint(endpoint);
71+
HttpClientFactory = options.HttpClientFactory ?? new DefaultHttpClientFactory(endpoint, new HttpClientOptions());
72+
}
73+
74+
/// <summary>
75+
/// The client options for communicating with the datasync service.
76+
/// </summary>
77+
public DatasyncClientOptions ClientOptions { get; }
78+
79+
/// <summary>
80+
/// Absolute URI of the datasync service.
81+
/// </summary>
82+
public Uri Endpoint { get; }
83+
84+
/// <summary>
85+
/// The HTTP client factory to use to retrieve a <see cref="HttpClient"/> for
86+
/// communicating with the datasync service.
87+
/// </summary>
88+
public IHttpClientFactory HttpClientFactory { get; }
89+
90+
/// <summary>
91+
/// The service options (for serializer / shared settings).
92+
/// </summary>
93+
public IDatasyncServiceOptions ServiceOptions { get; }
94+
95+
#region IDisposable
96+
/// <summary>
97+
/// Implementation of the <see cref="IDisposable"/> pattern for disposing
98+
/// this client.
99+
/// </summary>
100+
[ExcludeFromCodeCoverage]
101+
public void Dispose()
102+
{
103+
Dispose(true);
104+
GC.SuppressFinalize(this);
105+
}
106+
107+
/// <summary>
108+
/// Normalizes an endpoint by removing any query and fragment, then ensuring that the
109+
/// path has a trailing slash.
110+
/// </summary>
111+
/// <param name="endpoint">The endpoint to normalizer.</param>
112+
/// <returns>The normalized endpoint.</returns>
113+
internal static Uri NormalizeEndpoint(Uri endpoint)
114+
{
115+
UriBuilder builder = new(endpoint) { Query = string.Empty, Fragment = string.Empty };
116+
builder.Path = builder.Path.TrimEnd('/') + "/";
117+
return builder.Uri;
118+
}
119+
120+
/// <summary>
121+
/// Implementation of the <see cref="IDisposable"/> pattern for disposing
122+
/// this client.
123+
/// </summary>
124+
/// <param name="disposing">Indicates if being called from the Dispose() method or the finalizer.</param>
125+
[ExcludeFromCodeCoverage]
126+
protected virtual void Dispose(bool disposing)
127+
{
128+
if (disposing)
129+
{
130+
if (HttpClientFactory is IDisposable disposable)
131+
{
132+
disposable.Dispose();
133+
}
134+
}
135+
}
136+
#endregion
137+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
namespace CommunityToolkit.Datasync.Client;
6+
7+
/// <summary>
8+
/// The default values for various settings within the Datasync client.
9+
/// </summary>
10+
public static class DatasyncClientDefaults
11+
{
12+
/// <summary>
13+
/// The default number of parallel operations allowed during a push/pull operation.
14+
/// </summary>
15+
public const int ParallelOperations = 1;
16+
17+
/// <summary>
18+
/// The maximum number of parallel operations allowed during a push/pull operation.
19+
/// </summary>
20+
public const int MaxParallelOperations = 8;
21+
22+
/// <summary>
23+
/// The default entity ID generator.
24+
/// </summary>
25+
public static string EntityIdGenerator(string _) => Guid.NewGuid().ToString();
26+
27+
/// <summary>
28+
/// The function to use for turning a table name into a relative path for accessing
29+
/// the table endpoint on the remote service.
30+
/// </summary>
31+
public static string TableEndpointResolver(string tableName) => $"/tables/{tableName.ToLowerInvariant()}";
32+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
#pragma warning disable IDE0058 // Expression value is never used
6+
7+
using CommunityToolkit.Datasync.Common;
8+
using Defaults = CommunityToolkit.Datasync.Client.DatasyncClientDefaults;
9+
10+
namespace CommunityToolkit.Datasync.Client;
11+
12+
/// <summary>
13+
/// Options to use for configuring the <see cref="DatasyncClient"/> object.
14+
/// </summary>
15+
public class DatasyncClientOptions
16+
{
17+
private int _parallelOperations = Defaults.ParallelOperations;
18+
private string _clientName = string.Empty;
19+
20+
/// <summary>
21+
/// The <see cref="IDatasyncServiceOptions"/> to use when communicating with the
22+
/// datasync service.
23+
/// </summary>
24+
public IDatasyncServiceOptions DatasyncServiceOptions { get; set; } = new DatasyncServiceOptions();
25+
26+
/// <summary>
27+
/// If set, the <see cref="IHttpClientFactory"/> to use for generating
28+
/// <see cref="HttpClient"/> objects for communicating with the datasync
29+
/// server.
30+
/// </summary>
31+
public IHttpClientFactory? HttpClientFactory { get; set; }
32+
33+
/// <summary>
34+
/// The name of the client to use when creating a new <see cref="HttpClient"/>
35+
/// via the <see cref="HttpClientFactory"/>.
36+
/// </summary>
37+
public string HttpClientName
38+
{
39+
get => this._clientName;
40+
set
41+
{
42+
Ensure.That(value, nameof(HttpClientName)).IsNotNullOrWhiteSpace();
43+
this._clientName = value;
44+
}
45+
}
46+
47+
/// <summary>
48+
/// The number of parallel operations that can be executed within the scope of
49+
/// a push or pull operation.
50+
/// </summary>
51+
public int ParallelOperations
52+
{
53+
get => this._parallelOperations;
54+
set
55+
{
56+
Ensure.That(value, nameof(ParallelOperations)).IsInRange(1, Defaults.MaxParallelOperations);
57+
this._parallelOperations = value;
58+
}
59+
}
60+
61+
/// <summary>
62+
/// The function to use for generating a globally unique ID for an entity being
63+
/// synchronized to the datasync service.
64+
/// </summary>
65+
public Func<string, string> EntityIdGenerator { get; set; } = Defaults.EntityIdGenerator;
66+
67+
/// <summary>
68+
/// The function to use for turning a table name into a relative path for accessing
69+
/// the table endpoint on the remote service.
70+
/// </summary>
71+
public Func<string, string> TableEndpointResolver { get; set; } = Defaults.TableEndpointResolver;
72+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
6+
namespace CommunityToolkit.Datasync.Client.Http;
7+
8+
/// <summary>
9+
/// An implementation of the <see cref="IHttpClientFactory"/> that is used
10+
/// for systems that don't have a default implementation of the factory.
11+
/// </summary>
12+
/// <param name="endpoint">The base endpoint.</param>
13+
/// <param name="options">The options to use in creating new <see cref="HttpClient"/> objects.</param>
14+
public class DefaultHttpClientFactory(Uri endpoint, IHttpClientOptions options) : IHttpClientFactory
15+
{
16+
/// <summary>
17+
/// The base endpoint for the <see cref="HttpClient"/> that is produced.
18+
/// </summary>
19+
internal Uri Endpoint { get; } = endpoint;
20+
21+
/// <summary>
22+
/// The options to use in creating new <see cref="HttpClient"/> objects.
23+
/// </summary>
24+
internal IHttpClientOptions Options { get; } = options;
25+
26+
/// <inheritdoc />
27+
public HttpClient CreateClient(string name)
28+
{
29+
throw new NotImplementedException();
30+
}
31+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
namespace CommunityToolkit.Datasync.Client.Http;
6+
7+
/// <summary>
8+
/// A concrete implementation of the <see cref="IHttpClientOptions"/> interface.
9+
/// </summary>
10+
public class HttpClientOptions : IHttpClientOptions
11+
{
12+
/// <inheritdoc />
13+
public IEnumerable<HttpMessageHandler> HttpPipeline { get; set; } = [];
14+
15+
/// <inheritdoc />
16+
public TimeSpan? HttpTimeout { get; set; } = TimeSpan.FromSeconds(60);
17+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
namespace CommunityToolkit.Datasync.Client.Http;
6+
7+
/// <summary>
8+
/// The options required for configuring a new <see cref="HttpClient"/> for
9+
/// use with the Datasync version of the <see cref="IHttpClientFactory"/>.
10+
/// </summary>
11+
public interface IHttpClientOptions
12+
{
13+
/// <summary>
14+
/// The HTTP Pipeline to use. This can be null. If set, it must
15+
/// be an ordered set of <see cref="DelegatingHandler"/> objects,
16+
/// potentially followed by a <see cref="HttpClientHandler"/> for
17+
/// a transport.
18+
/// </summary>
19+
IEnumerable<HttpMessageHandler> HttpPipeline { get; }
20+
21+
/// <summary>
22+
/// If set, the timeout to use with <see cref="HttpClient"/> connections.
23+
/// If not set, the default of 100,000ms (100 seconds) will be used.
24+
/// </summary>
25+
TimeSpan? HttpTimeout { get; }
26+
}

src/CommunityToolkit.Datasync.Common/Guards/ParamExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ public static Param<int> IsGt(this Param<int> param, int value, string? because
9292
if (param.Value <= value)
9393
{
9494
because ??= $"The parameter '{param.Name}' must be greater than {value}";
95-
throw new ArgumentException(because, param.Name);
95+
throw new ArgumentOutOfRangeException(param.Name, because);
9696
}
9797

9898
return param;
@@ -111,7 +111,7 @@ public static Param<int> IsGte(this Param<int> param, int value, string? because
111111
if (param.Value < value)
112112
{
113113
because ??= $"The parameter '{param.Name}' must be greater than or equal to {value}";
114-
throw new ArgumentException(because, param.Name);
114+
throw new ArgumentOutOfRangeException(param.Name, because);
115115
}
116116

117117
return param;

0 commit comments

Comments
 (0)