Skip to content

Commit 4add8bc

Browse files
committed
Move Uuid => CoderSdk
1 parent b31dd2e commit 4add8bc

20 files changed

+277
-170
lines changed

App/Utils/ModelUpdate.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public static void ApplyLists<T>(IList<T> target, IEnumerable<T> update, Compari
7575
// space before the semicolon or not.
7676
#pragma warning disable format
7777
OuterLoopEnd: ;
78-
#pragma warning enable format
78+
#pragma warning restore format
7979
}
8080

8181
// Add any items that were missing into their correct sorted place.
@@ -99,7 +99,7 @@ public static void ApplyLists<T>(IList<T> target, IEnumerable<T> update, Compari
9999
// space before the semicolon or not.
100100
#pragma warning disable format
101101
OuterLoopEnd: ;
102-
#pragma warning enable format
102+
#pragma warning restore format
103103
}
104104
}
105105
}

App/ViewModels/AgentAppViewModel.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using Coder.Desktop.App.Models;
55
using Coder.Desktop.App.Services;
66
using Coder.Desktop.App.Utils;
7+
using Coder.Desktop.CoderSdk;
78
using Coder.Desktop.Vpn.Proto;
89
using CommunityToolkit.Mvvm.ComponentModel;
910
using CommunityToolkit.Mvvm.Input;

App/ViewModels/AgentViewModel.cs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Windows.ApplicationModel.DataTransfer;
99
using Coder.Desktop.App.Services;
1010
using Coder.Desktop.App.Utils;
11+
using Coder.Desktop.CoderSdk;
1112
using Coder.Desktop.CoderSdk.Coder;
1213
using Coder.Desktop.Vpn.Proto;
1314
using CommunityToolkit.Mvvm.ComponentModel;
@@ -292,13 +293,6 @@ private void ContinueFetchApps(Task<WorkspaceAgent> task)
292293
{
293294
if (!app.External || !string.IsNullOrEmpty(app.Command)) continue;
294295

295-
if (!Uuid.TryParse(app.Id, out var uuid))
296-
{
297-
_logger.LogWarning("Could not parse app UUID '{Id}' for '{DisplayName}', app will not appear in list",
298-
app.Id, app.DisplayName);
299-
continue;
300-
}
301-
302296
if (!Uri.TryCreate(app.Url, UriKind.Absolute, out var appUri))
303297
{
304298
_logger.LogWarning("Could not parse app URI '{Url}' for '{DisplayName}', app will not appear in list",
@@ -316,7 +310,7 @@ private void ContinueFetchApps(Task<WorkspaceAgent> task)
316310
// icon.
317311
_ = Uri.TryCreate(DashboardBaseUrl, app.Icon, out var iconUrl);
318312

319-
apps.Add(_agentAppViewModelFactory.Create(uuid, app.DisplayName, appUri, iconUrl));
313+
apps.Add(_agentAppViewModelFactory.Create(app.Id, app.DisplayName, appUri, iconUrl));
320314
}
321315

322316
foreach (var displayApp in workspaceAgent.DisplayApps)
@@ -348,7 +342,7 @@ private void ContinueFetchApps(Task<WorkspaceAgent> task)
348342
}
349343
catch (Exception e)
350344
{
351-
_logger.LogWarning("Could not craft app URI for display app {displayApp}, app will not appear in list",
345+
_logger.LogWarning(e, "Could not craft app URI for display app {displayApp}, app will not appear in list",
352346
displayApp);
353347
continue;
354348
}

App/ViewModels/TrayWindowViewModel.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Coder.Desktop.App.Services;
99
using Coder.Desktop.App.Utils;
1010
using Coder.Desktop.App.Views;
11+
using Coder.Desktop.CoderSdk;
1112
using Coder.Desktop.Vpn.Proto;
1213
using CommunityToolkit.Mvvm.ComponentModel;
1314
using CommunityToolkit.Mvvm.Input;

App/packages.lock.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,7 @@
550550
"Coder.Desktop.Vpn.Proto": {
551551
"type": "Project",
552552
"dependencies": {
553+
"Coder.Desktop.CoderSdk": "[1.0.0, )",
553554
"Google.Protobuf": "[3.29.3, )"
554555
}
555556
}

Coder.Desktop.sln

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.App", "Tests.App\Test
2727
EndProject
2828
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MutagenSdk", "MutagenSdk\MutagenSdk.csproj", "{E2477ADC-03DA-490D-9369-79A4CC4A58D2}"
2929
EndProject
30+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.CoderSdk", "Tests.CoderSdk\Tests.CoderSdk.csproj", "{2BDEA023-FE75-476F-81DE-8EF90806C27C}"
31+
EndProject
3032
Global
3133
GlobalSection(SolutionConfigurationPlatforms) = preSolution
3234
Debug|Any CPU = Debug|Any CPU
@@ -239,6 +241,22 @@ Global
239241
{E2477ADC-03DA-490D-9369-79A4CC4A58D2}.Release|x64.Build.0 = Release|Any CPU
240242
{E2477ADC-03DA-490D-9369-79A4CC4A58D2}.Release|x86.ActiveCfg = Release|Any CPU
241243
{E2477ADC-03DA-490D-9369-79A4CC4A58D2}.Release|x86.Build.0 = Release|Any CPU
244+
{2BDEA023-FE75-476F-81DE-8EF90806C27C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
245+
{2BDEA023-FE75-476F-81DE-8EF90806C27C}.Debug|Any CPU.Build.0 = Debug|Any CPU
246+
{2BDEA023-FE75-476F-81DE-8EF90806C27C}.Debug|ARM64.ActiveCfg = Debug|Any CPU
247+
{2BDEA023-FE75-476F-81DE-8EF90806C27C}.Debug|ARM64.Build.0 = Debug|Any CPU
248+
{2BDEA023-FE75-476F-81DE-8EF90806C27C}.Debug|x64.ActiveCfg = Debug|Any CPU
249+
{2BDEA023-FE75-476F-81DE-8EF90806C27C}.Debug|x64.Build.0 = Debug|Any CPU
250+
{2BDEA023-FE75-476F-81DE-8EF90806C27C}.Debug|x86.ActiveCfg = Debug|Any CPU
251+
{2BDEA023-FE75-476F-81DE-8EF90806C27C}.Debug|x86.Build.0 = Debug|Any CPU
252+
{2BDEA023-FE75-476F-81DE-8EF90806C27C}.Release|Any CPU.ActiveCfg = Release|Any CPU
253+
{2BDEA023-FE75-476F-81DE-8EF90806C27C}.Release|Any CPU.Build.0 = Release|Any CPU
254+
{2BDEA023-FE75-476F-81DE-8EF90806C27C}.Release|ARM64.ActiveCfg = Release|Any CPU
255+
{2BDEA023-FE75-476F-81DE-8EF90806C27C}.Release|ARM64.Build.0 = Release|Any CPU
256+
{2BDEA023-FE75-476F-81DE-8EF90806C27C}.Release|x64.ActiveCfg = Release|Any CPU
257+
{2BDEA023-FE75-476F-81DE-8EF90806C27C}.Release|x64.Build.0 = Release|Any CPU
258+
{2BDEA023-FE75-476F-81DE-8EF90806C27C}.Release|x86.ActiveCfg = Release|Any CPU
259+
{2BDEA023-FE75-476F-81DE-8EF90806C27C}.Release|x86.Build.0 = Release|Any CPU
242260
EndGlobalSection
243261
GlobalSection(SolutionProperties) = preSolution
244262
HideSolutionNode = FALSE

CoderSdk/Coder/WorkspaceAgents.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public class WorkspaceAgent
2121

2222
public class WorkspaceApp
2323
{
24-
public string Id { get; set; } = string.Empty;
24+
public Uuid Id { get; set; } = Uuid.Zero;
2525
public string Url { get; set; } = string.Empty;
2626
public bool External { get; set; } = false;
2727
public string DisplayName { get; set; } = string.Empty;

CoderSdk/Uuid.cs

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
using System.Globalization;
2+
using System.Text;
3+
using System.Text.Json;
4+
using System.Text.Json.Serialization;
5+
6+
namespace Coder.Desktop.CoderSdk;
7+
8+
/// <summary>
9+
/// A simplistic UUIDv4 class that wraps a 16-byte array. We don't use the
10+
/// native Guid class because it has some weird reordering behavior due to
11+
/// legacy Windows stuff. This class is not guaranteed to provide full RFC
12+
/// 4122 compliance, but it should provide enough coverage for Coder
13+
/// Desktop.
14+
/// </summary>
15+
[JsonConverter(typeof(UuidJsonConverter))]
16+
public class Uuid
17+
{
18+
private readonly byte[] _bytes;
19+
20+
/// <summary>
21+
/// The (invalid) zero UUID.
22+
/// </summary>
23+
public static Uuid Zero { get; } = new();
24+
25+
public ReadOnlySpan<byte> Bytes => _bytes;
26+
27+
private Uuid()
28+
{
29+
_bytes = new byte[16];
30+
}
31+
32+
public Uuid(ReadOnlySpan<byte> bytes)
33+
{
34+
if (bytes.Length != 16)
35+
throw new ArgumentException($"UUID must be 16 bytes, but was {bytes.Length} bytes", nameof(bytes));
36+
if (bytes[6] >> 4 != 0x4)
37+
throw new ArgumentException("ID does not seem like a valid UUIDv4", nameof(bytes));
38+
_bytes = bytes.ToArray();
39+
}
40+
41+
public Uuid(string str)
42+
{
43+
if (str.Length != 36)
44+
throw new ArgumentException($"UUID string must be 36 characters, but was {str.Length} characters",
45+
nameof(str));
46+
47+
var currentIndex = 0;
48+
_bytes = new byte[16];
49+
50+
for (var i = 0; i < 36; i++)
51+
{
52+
if (i is 8 or 13 or 18 or 23)
53+
{
54+
if (str[i] != '-')
55+
throw new ArgumentException("UUID string must have dashes at positions 8, 13, 18, and 23",
56+
nameof(str));
57+
continue;
58+
}
59+
60+
// Take two hex digits and convert them to a byte.
61+
var hex = str[i..(i + 2)];
62+
if (!byte.TryParse(hex, NumberStyles.HexNumber, null, out var b))
63+
throw new ArgumentException($"UUID string has invalid hex digits at position {i}", nameof(str));
64+
_bytes[currentIndex] = b;
65+
currentIndex++;
66+
67+
// Advance the loop index by 1 as we processed two characters.
68+
i++;
69+
}
70+
71+
if (currentIndex != 16)
72+
throw new ArgumentException($"UUID string must have 16 bytes, but was {currentIndex} bytes", nameof(str));
73+
if (_bytes[6] >> 4 != 0x4)
74+
throw new ArgumentException("ID does not seem like a valid UUIDv4", nameof(str));
75+
}
76+
77+
public static bool TryFrom(ReadOnlySpan<byte> bytes, out Uuid uuid)
78+
{
79+
try
80+
{
81+
uuid = new Uuid(bytes);
82+
return true;
83+
}
84+
catch
85+
{
86+
uuid = Zero;
87+
return false;
88+
}
89+
}
90+
91+
public static bool TryParse(string str, out Uuid uuid)
92+
{
93+
try
94+
{
95+
uuid = new Uuid(str);
96+
return true;
97+
}
98+
catch
99+
{
100+
uuid = Zero;
101+
return false;
102+
}
103+
}
104+
105+
public override string ToString()
106+
{
107+
if (_bytes.Length != 16)
108+
throw new ArgumentException($"UUID must be 16 bytes, but was {_bytes.Length} bytes", nameof(_bytes));
109+
110+
// Print every byte as hex, with dashes in the right places.
111+
var sb = new StringBuilder(36);
112+
for (var i = 0; i < 16; i++)
113+
{
114+
if (i is 4 or 6 or 8 or 10)
115+
sb.Append('-');
116+
sb.Append(_bytes[i].ToString("x2"));
117+
}
118+
119+
return sb.ToString();
120+
}
121+
122+
#region Uuid equality
123+
124+
public override bool Equals(object? obj)
125+
{
126+
return obj is Uuid other && Equals(other);
127+
}
128+
129+
public bool Equals(Uuid? other)
130+
{
131+
return other is not null && _bytes.SequenceEqual(other._bytes);
132+
}
133+
134+
public override int GetHashCode()
135+
{
136+
return _bytes.GetHashCode();
137+
}
138+
139+
public static bool operator ==(Uuid left, Uuid right)
140+
{
141+
return left.Equals(right);
142+
}
143+
144+
public static bool operator !=(Uuid left, Uuid right)
145+
{
146+
return !left.Equals(right);
147+
}
148+
149+
#endregion
150+
}
151+
152+
public class UuidJsonConverter : JsonConverter<Uuid>
153+
{
154+
public override Uuid? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
155+
{
156+
if (reader.TokenType == JsonTokenType.Null)
157+
return null;
158+
159+
if (reader.TokenType != JsonTokenType.String)
160+
throw new JsonException("Expected string token type for UUID");
161+
162+
var str = reader.GetString();
163+
if (str == null)
164+
return null;
165+
166+
try
167+
{
168+
return new Uuid(str);
169+
}
170+
catch (Exception ex)
171+
{
172+
throw new JsonException($"Invalid UUID string '{str}'", ex);
173+
}
174+
}
175+
176+
public override void Write(Utf8JsonWriter writer, Uuid value, JsonSerializerOptions options)
177+
{
178+
writer.WriteStringValue(value.ToString());
179+
}
180+
}

Tests.App/Converters/FriendlyByteConverterTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public void EndToEnd()
2929
var converter = new FriendlyByteConverter();
3030
foreach (var (input, expected) in cases)
3131
{
32-
var actual = converter.Convert(input, typeof(string), null, null);
32+
var actual = converter.Convert(input, typeof(string), null!, null!);
3333
Assert.That(actual, Is.EqualTo(expected), $"case ({input.GetType()}){input}");
3434
}
3535
}

Tests.CoderSdk/Tests.CoderSdk.csproj

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<AssemblyName>Coder.Desktop.Tests.CoderSdk</AssemblyName>
5+
<RootNamespace>Coder.Desktop.Tests.CoderSdk</RootNamespace>
6+
<TargetFramework>net8.0</TargetFramework>
7+
<ImplicitUsings>enable</ImplicitUsings>
8+
<Nullable>enable</Nullable>
9+
10+
<IsPackable>false</IsPackable>
11+
<IsTestProject>true</IsTestProject>
12+
</PropertyGroup>
13+
14+
<ItemGroup>
15+
<PackageReference Include="coverlet.collector" Version="6.0.4">
16+
<PrivateAssets>all</PrivateAssets>
17+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
18+
</PackageReference>
19+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
20+
<PackageReference Include="NUnit" Version="4.3.2" />
21+
<PackageReference Include="NUnit.Analyzers" Version="4.6.0">
22+
<PrivateAssets>all</PrivateAssets>
23+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
24+
</PackageReference>
25+
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
26+
</ItemGroup>
27+
28+
<ItemGroup>
29+
<Using Include="NUnit.Framework" />
30+
</ItemGroup>
31+
32+
<ItemGroup>
33+
<ProjectReference Include="..\CoderSdk\CoderSdk.csproj" />
34+
</ItemGroup>
35+
36+
</Project>

Tests.Vpn.Proto/UuidTest.cs renamed to Tests.CoderSdk/UuidTest.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
using Coder.Desktop.Vpn.Proto;
1+
using Coder.Desktop.CoderSdk;
22

3-
namespace Coder.Desktop.Tests.Vpn.Proto;
3+
namespace Coder.Desktop.Tests.CoderSdk;
44

55
[TestFixture]
66
public class UuidTest
@@ -111,10 +111,12 @@ public void Equality()
111111

112112
#pragma warning disable CS1718 // Comparison made to same variable
113113
#pragma warning disable NUnit2010 // Use Is.EqualTo constraint instead of direct comparison
114+
// ReSharper disable EqualExpressionComparison
114115
Assert.That(uuid1 == uuid1, Is.True);
115116
Assert.That(uuid1 != uuid1, Is.False);
116117
Assert.That(Uuid.Zero == Uuid.Zero, Is.True);
117118
Assert.That(Uuid.Zero != Uuid.Zero, Is.False);
119+
// ReSharper restore EqualExpressionComparison
118120
#pragma warning restore NUnit2010
119121
#pragma warning restore CS1718
120122

Tests.Vpn.Proto/packages.lock.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,13 @@
7373
"resolved": "1.6.0",
7474
"contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ=="
7575
},
76+
"Coder.Desktop.CoderSdk": {
77+
"type": "Project"
78+
},
7679
"Coder.Desktop.Vpn.Proto": {
7780
"type": "Project",
7881
"dependencies": {
82+
"Coder.Desktop.CoderSdk": "[1.0.0, )",
7983
"Google.Protobuf": "[3.29.3, )"
8084
}
8185
}

Tests.Vpn.Service/packages.lock.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,7 @@
501501
"Coder.Desktop.Vpn.Proto": {
502502
"type": "Project",
503503
"dependencies": {
504+
"Coder.Desktop.CoderSdk": "[1.0.0, )",
504505
"Google.Protobuf": "[3.29.3, )"
505506
}
506507
},

0 commit comments

Comments
 (0)