Skip to content

Commit 5afeb42

Browse files
feat(client)!: make models immutable
1 parent 6e88553 commit 5afeb42

File tree

309 files changed

+8331
-4489
lines changed

Some content is hidden

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

309 files changed

+8331
-4489
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+
}

src/Anthropic.Client/Core/ClientOptions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ public string? APIKey
2929
set { _apiKey = new(() => value); }
3030
}
3131

32-
Lazy<string?> _authToken = new(() => Environment.GetEnvironmentVariable("ANTHROPIC_AUTH_TOKEN")
32+
Lazy<string?> _authToken = new(() =>
33+
Environment.GetEnvironmentVariable("ANTHROPIC_AUTH_TOKEN")
3334
);
3435
public string? AuthToken
3536
{
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 Anthropic.Client.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/Anthropic.Client/Core/ModelBase.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ namespace Anthropic.Client.Core;
1010

1111
public abstract record class ModelBase
1212
{
13-
public Dictionary<string, JsonElement> Properties { get; set; } = [];
13+
private protected FreezableDictionary<string, JsonElement> _properties = [];
14+
15+
public IReadOnlyDictionary<string, JsonElement> Properties
16+
{
17+
get { return this._properties.Freeze(); }
18+
}
1419

1520
internal static readonly JsonSerializerOptions SerializerOptions = new()
1621
{
@@ -100,5 +105,5 @@ public abstract record class ModelBase
100105

101106
interface IFromRaw<T>
102107
{
103-
static abstract T FromRawUnchecked(Dictionary<string, JsonElement> properties);
108+
static abstract T FromRawUnchecked(IReadOnlyDictionary<string, JsonElement> properties);
104109
}

src/Anthropic.Client/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/Anthropic.Client/Core/ParamsBase.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,19 @@ namespace Anthropic.Client.Core;
1111

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

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

1828
public abstract Uri Url(IAnthropicClient client);
1929

src/Anthropic.Client/Models/APIErrorObject.cs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Frozen;
23
using System.Collections.Generic;
34
using System.Diagnostics.CodeAnalysis;
45
using System.Text.Json;
@@ -15,7 +16,7 @@ public required string Message
1516
{
1617
get
1718
{
18-
if (!this.Properties.TryGetValue("message", out JsonElement element))
19+
if (!this._properties.TryGetValue("message", out JsonElement element))
1920
throw new AnthropicInvalidDataException(
2021
"'message' cannot be null",
2122
new ArgumentOutOfRangeException("message", "Missing required argument")
@@ -27,9 +28,9 @@ public required string Message
2728
new ArgumentNullException("message")
2829
);
2930
}
30-
set
31+
init
3132
{
32-
this.Properties["message"] = JsonSerializer.SerializeToElement(
33+
this._properties["message"] = JsonSerializer.SerializeToElement(
3334
value,
3435
ModelBase.SerializerOptions
3536
);
@@ -40,17 +41,17 @@ public JsonElement Type
4041
{
4142
get
4243
{
43-
if (!this.Properties.TryGetValue("type", out JsonElement element))
44+
if (!this._properties.TryGetValue("type", out JsonElement element))
4445
throw new AnthropicInvalidDataException(
4546
"'type' cannot be null",
4647
new ArgumentOutOfRangeException("type", "Missing required argument")
4748
);
4849

4950
return JsonSerializer.Deserialize<JsonElement>(element, ModelBase.SerializerOptions);
5051
}
51-
set
52+
init
5253
{
53-
this.Properties["type"] = JsonSerializer.SerializeToElement(
54+
this._properties["type"] = JsonSerializer.SerializeToElement(
5455
value,
5556
ModelBase.SerializerOptions
5657
);
@@ -68,17 +69,26 @@ public APIErrorObject()
6869
this.Type = JsonSerializer.Deserialize<JsonElement>("\"api_error\"");
6970
}
7071

72+
public APIErrorObject(IReadOnlyDictionary<string, JsonElement> properties)
73+
{
74+
this._properties = [.. properties];
75+
76+
this.Type = JsonSerializer.Deserialize<JsonElement>("\"api_error\"");
77+
}
78+
7179
#pragma warning disable CS8618
7280
[SetsRequiredMembers]
73-
APIErrorObject(Dictionary<string, JsonElement> properties)
81+
APIErrorObject(FrozenDictionary<string, JsonElement> properties)
7482
{
75-
Properties = properties;
83+
this._properties = [.. properties];
7684
}
7785
#pragma warning restore CS8618
7886

79-
public static APIErrorObject FromRawUnchecked(Dictionary<string, JsonElement> properties)
87+
public static APIErrorObject FromRawUnchecked(
88+
IReadOnlyDictionary<string, JsonElement> properties
89+
)
8090
{
81-
return new(properties);
91+
return new(FrozenDictionary.ToFrozenDictionary(properties));
8292
}
8393

8494
[SetsRequiredMembers]

src/Anthropic.Client/Models/AuthenticationError.cs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Frozen;
23
using System.Collections.Generic;
34
using System.Diagnostics.CodeAnalysis;
45
using System.Text.Json;
@@ -15,7 +16,7 @@ public required string Message
1516
{
1617
get
1718
{
18-
if (!this.Properties.TryGetValue("message", out JsonElement element))
19+
if (!this._properties.TryGetValue("message", out JsonElement element))
1920
throw new AnthropicInvalidDataException(
2021
"'message' cannot be null",
2122
new ArgumentOutOfRangeException("message", "Missing required argument")
@@ -27,9 +28,9 @@ public required string Message
2728
new ArgumentNullException("message")
2829
);
2930
}
30-
set
31+
init
3132
{
32-
this.Properties["message"] = JsonSerializer.SerializeToElement(
33+
this._properties["message"] = JsonSerializer.SerializeToElement(
3334
value,
3435
ModelBase.SerializerOptions
3536
);
@@ -40,17 +41,17 @@ public JsonElement Type
4041
{
4142
get
4243
{
43-
if (!this.Properties.TryGetValue("type", out JsonElement element))
44+
if (!this._properties.TryGetValue("type", out JsonElement element))
4445
throw new AnthropicInvalidDataException(
4546
"'type' cannot be null",
4647
new ArgumentOutOfRangeException("type", "Missing required argument")
4748
);
4849

4950
return JsonSerializer.Deserialize<JsonElement>(element, ModelBase.SerializerOptions);
5051
}
51-
set
52+
init
5253
{
53-
this.Properties["type"] = JsonSerializer.SerializeToElement(
54+
this._properties["type"] = JsonSerializer.SerializeToElement(
5455
value,
5556
ModelBase.SerializerOptions
5657
);
@@ -68,17 +69,26 @@ public AuthenticationError()
6869
this.Type = JsonSerializer.Deserialize<JsonElement>("\"authentication_error\"");
6970
}
7071

72+
public AuthenticationError(IReadOnlyDictionary<string, JsonElement> properties)
73+
{
74+
this._properties = [.. properties];
75+
76+
this.Type = JsonSerializer.Deserialize<JsonElement>("\"authentication_error\"");
77+
}
78+
7179
#pragma warning disable CS8618
7280
[SetsRequiredMembers]
73-
AuthenticationError(Dictionary<string, JsonElement> properties)
81+
AuthenticationError(FrozenDictionary<string, JsonElement> properties)
7482
{
75-
Properties = properties;
83+
this._properties = [.. properties];
7684
}
7785
#pragma warning restore CS8618
7886

79-
public static AuthenticationError FromRawUnchecked(Dictionary<string, JsonElement> properties)
87+
public static AuthenticationError FromRawUnchecked(
88+
IReadOnlyDictionary<string, JsonElement> properties
89+
)
8090
{
81-
return new(properties);
91+
return new(FrozenDictionary.ToFrozenDictionary(properties));
8292
}
8393

8494
[SetsRequiredMembers]

0 commit comments

Comments
 (0)