Skip to content

Commit 22921ae

Browse files
feat(client)!: make models immutable
1 parent f1fe13a commit 22921ae

File tree

351 files changed

+31418
-21204
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

351 files changed

+31418
-21204
lines changed

.config/dotnet-tools.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
"isRoot": true,
44
"tools": {
55
"csharpier": {
6-
"version": "1.0.1",
6+
"version": "1.1.2",
77
"commands": [
88
"csharpier"
99
],
1010
"rollForward": false
1111
}
1212
}
13-
}
13+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
using System;
2+
using System.Collections.Frozen;
3+
using System.Collections.Generic;
4+
using System.Diagnostics.CodeAnalysis;
5+
using Collections = System.Collections;
6+
7+
namespace Orb.Core;
8+
9+
/// <summary>
10+
/// A dictionary that can be mutated and then frozen once no more mutations are expected.<br
11+
/// /><br />
12+
///
13+
/// This is useful for allowing a dictionary to be modified by a class's `init` properties,
14+
/// but then preventing it from being modified afterwards.
15+
/// </summary>
16+
class FreezableDictionary<K, V> : IDictionary<K, V>
17+
where K : notnull
18+
{
19+
IDictionary<K, V> _dictionary = new Dictionary<K, V>();
20+
21+
Dictionary<K, V> _mutableDictionary
22+
{
23+
get
24+
{
25+
if (_dictionary is Dictionary<K, V> dict)
26+
{
27+
return dict;
28+
}
29+
30+
throw new InvalidOperationException("Can't mutate after freezing.");
31+
}
32+
}
33+
34+
public FreezableDictionary() { }
35+
36+
public FreezableDictionary(IReadOnlyDictionary<K, V> dictionary)
37+
{
38+
_dictionary = new Dictionary<K, V>(dictionary);
39+
}
40+
41+
public FreezableDictionary(FrozenDictionary<K, V> frozen)
42+
{
43+
_dictionary = frozen;
44+
}
45+
46+
public IReadOnlyDictionary<K, V> Freeze()
47+
{
48+
if (_dictionary is FrozenDictionary<K, V> dict)
49+
{
50+
return dict;
51+
}
52+
53+
var dictionary = FrozenDictionary.ToFrozenDictionary(_dictionary);
54+
_dictionary = dictionary;
55+
56+
return dictionary;
57+
}
58+
59+
public V this[K key]
60+
{
61+
get => _dictionary[key];
62+
set => _mutableDictionary[key] = value;
63+
}
64+
65+
public ICollection<K> Keys
66+
{
67+
get { return _dictionary.Keys; }
68+
}
69+
70+
public ICollection<V> Values
71+
{
72+
get { return _dictionary.Values; }
73+
}
74+
75+
public int Count
76+
{
77+
get { return _dictionary.Count; }
78+
}
79+
80+
public bool IsReadOnly
81+
{
82+
get { return _dictionary.IsReadOnly; }
83+
}
84+
85+
public void Add(K key, V value)
86+
{
87+
_mutableDictionary.Add(key, value);
88+
}
89+
90+
public void Add(KeyValuePair<K, V> item)
91+
{
92+
_mutableDictionary.Add(item.Key, item.Value);
93+
}
94+
95+
public void Clear()
96+
{
97+
_mutableDictionary.Clear();
98+
}
99+
100+
public bool Contains(KeyValuePair<K, V> item)
101+
{
102+
return _dictionary.Contains(item);
103+
}
104+
105+
public bool ContainsKey(K key)
106+
{
107+
return _dictionary.ContainsKey(key);
108+
}
109+
110+
public void CopyTo(KeyValuePair<K, V>[] array, int arrayIndex)
111+
{
112+
_dictionary.CopyTo(array, arrayIndex);
113+
}
114+
115+
public IEnumerator<KeyValuePair<K, V>> GetEnumerator()
116+
{
117+
return _dictionary.GetEnumerator();
118+
}
119+
120+
public bool Remove(K key)
121+
{
122+
return _mutableDictionary.Remove(key);
123+
}
124+
125+
public bool Remove(KeyValuePair<K, V> item)
126+
{
127+
return _mutableDictionary.Remove(item.Key);
128+
}
129+
130+
public bool TryGetValue(K key, [MaybeNullWhenAttribute(false)] out V value)
131+
{
132+
return _dictionary.TryGetValue(key, out value);
133+
}
134+
135+
Collections::IEnumerator Collections::IEnumerable.GetEnumerator()
136+
{
137+
return _dictionary.GetEnumerator();
138+
}
139+
}

src/Orb/Core/ModelBase.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@ namespace Orb.Core;
2424

2525
public abstract record class ModelBase
2626
{
27-
public Dictionary<string, JsonElement> Properties { get; set; } = [];
27+
private protected FreezableDictionary<string, JsonElement> _properties = [];
28+
29+
public IReadOnlyDictionary<string, JsonElement> Properties
30+
{
31+
get { return this._properties.Freeze(); }
32+
}
2833

2934
internal static readonly JsonSerializerOptions SerializerOptions = new()
3035
{
@@ -626,5 +631,5 @@ public abstract record class ModelBase
626631

627632
interface IFromRaw<T>
628633
{
629-
static abstract T FromRawUnchecked(Dictionary<string, JsonElement> properties);
634+
static abstract T FromRawUnchecked(IReadOnlyDictionary<string, JsonElement> properties);
630635
}

src/Orb/Core/ModelConverter.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ sealed class ModelConverter<TModel> : JsonConverter<TModel>
1414
JsonSerializerOptions options
1515
)
1616
{
17-
Dictionary<string, JsonElement>? properties = JsonSerializer.Deserialize<
18-
Dictionary<string, JsonElement>
19-
>(ref reader, options);
17+
var properties = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(
18+
ref reader,
19+
options
20+
);
2021
if (properties == null)
2122
return null;
2223

src/Orb/Core/ParamsBase.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,19 @@ namespace Orb.Core;
1010

1111
public abstract record class ParamsBase
1212
{
13-
public Dictionary<string, JsonElement> QueryProperties { get; set; } = [];
13+
private protected FreezableDictionary<string, JsonElement> _queryProperties = [];
1414

15-
public Dictionary<string, JsonElement> HeaderProperties { get; set; } = [];
15+
public IReadOnlyDictionary<string, JsonElement> QueryProperties
16+
{
17+
get { return this._queryProperties.Freeze(); }
18+
}
19+
20+
private protected FreezableDictionary<string, JsonElement> _headerProperties = [];
21+
22+
public IReadOnlyDictionary<string, JsonElement> HeaderProperties
23+
{
24+
get { return this._headerProperties.Freeze(); }
25+
}
1626

1727
public abstract Uri Url(IOrbClient client);
1828

src/Orb/Models/Address.cs

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Collections.Frozen;
12
using System.Collections.Generic;
23
using System.Diagnostics.CodeAnalysis;
34
using System.Text.Json;
@@ -13,14 +14,14 @@ public required string? City
1314
{
1415
get
1516
{
16-
if (!this.Properties.TryGetValue("city", out JsonElement element))
17+
if (!this._properties.TryGetValue("city", out JsonElement element))
1718
return null;
1819

1920
return JsonSerializer.Deserialize<string?>(element, ModelBase.SerializerOptions);
2021
}
21-
set
22+
init
2223
{
23-
this.Properties["city"] = JsonSerializer.SerializeToElement(
24+
this._properties["city"] = JsonSerializer.SerializeToElement(
2425
value,
2526
ModelBase.SerializerOptions
2627
);
@@ -31,14 +32,14 @@ public required string? Country
3132
{
3233
get
3334
{
34-
if (!this.Properties.TryGetValue("country", out JsonElement element))
35+
if (!this._properties.TryGetValue("country", out JsonElement element))
3536
return null;
3637

3738
return JsonSerializer.Deserialize<string?>(element, ModelBase.SerializerOptions);
3839
}
39-
set
40+
init
4041
{
41-
this.Properties["country"] = JsonSerializer.SerializeToElement(
42+
this._properties["country"] = JsonSerializer.SerializeToElement(
4243
value,
4344
ModelBase.SerializerOptions
4445
);
@@ -49,14 +50,14 @@ public required string? Line1
4950
{
5051
get
5152
{
52-
if (!this.Properties.TryGetValue("line1", out JsonElement element))
53+
if (!this._properties.TryGetValue("line1", out JsonElement element))
5354
return null;
5455

5556
return JsonSerializer.Deserialize<string?>(element, ModelBase.SerializerOptions);
5657
}
57-
set
58+
init
5859
{
59-
this.Properties["line1"] = JsonSerializer.SerializeToElement(
60+
this._properties["line1"] = JsonSerializer.SerializeToElement(
6061
value,
6162
ModelBase.SerializerOptions
6263
);
@@ -67,14 +68,14 @@ public required string? Line2
6768
{
6869
get
6970
{
70-
if (!this.Properties.TryGetValue("line2", out JsonElement element))
71+
if (!this._properties.TryGetValue("line2", out JsonElement element))
7172
return null;
7273

7374
return JsonSerializer.Deserialize<string?>(element, ModelBase.SerializerOptions);
7475
}
75-
set
76+
init
7677
{
77-
this.Properties["line2"] = JsonSerializer.SerializeToElement(
78+
this._properties["line2"] = JsonSerializer.SerializeToElement(
7879
value,
7980
ModelBase.SerializerOptions
8081
);
@@ -85,14 +86,14 @@ public required string? PostalCode
8586
{
8687
get
8788
{
88-
if (!this.Properties.TryGetValue("postal_code", out JsonElement element))
89+
if (!this._properties.TryGetValue("postal_code", out JsonElement element))
8990
return null;
9091

9192
return JsonSerializer.Deserialize<string?>(element, ModelBase.SerializerOptions);
9293
}
93-
set
94+
init
9495
{
95-
this.Properties["postal_code"] = JsonSerializer.SerializeToElement(
96+
this._properties["postal_code"] = JsonSerializer.SerializeToElement(
9697
value,
9798
ModelBase.SerializerOptions
9899
);
@@ -103,14 +104,14 @@ public required string? State
103104
{
104105
get
105106
{
106-
if (!this.Properties.TryGetValue("state", out JsonElement element))
107+
if (!this._properties.TryGetValue("state", out JsonElement element))
107108
return null;
108109

109110
return JsonSerializer.Deserialize<string?>(element, ModelBase.SerializerOptions);
110111
}
111-
set
112+
init
112113
{
113-
this.Properties["state"] = JsonSerializer.SerializeToElement(
114+
this._properties["state"] = JsonSerializer.SerializeToElement(
114115
value,
115116
ModelBase.SerializerOptions
116117
);
@@ -129,16 +130,21 @@ public override void Validate()
129130

130131
public Address() { }
131132

133+
public Address(IReadOnlyDictionary<string, JsonElement> properties)
134+
{
135+
this._properties = [.. properties];
136+
}
137+
132138
#pragma warning disable CS8618
133139
[SetsRequiredMembers]
134-
Address(Dictionary<string, JsonElement> properties)
140+
Address(FrozenDictionary<string, JsonElement> properties)
135141
{
136-
Properties = properties;
142+
this._properties = [.. properties];
137143
}
138144
#pragma warning restore CS8618
139145

140-
public static Address FromRawUnchecked(Dictionary<string, JsonElement> properties)
146+
public static Address FromRawUnchecked(IReadOnlyDictionary<string, JsonElement> properties)
141147
{
142-
return new(properties);
148+
return new(FrozenDictionary.ToFrozenDictionary(properties));
143149
}
144150
}

0 commit comments

Comments
 (0)