Skip to content

Commit 39bc349

Browse files
oschwaldclaude
andcommitted
Convert all model and response classes to C# records
Replace all [Parameter] attributes with [MapKey] and [Constructor]-based deserialization with property-based deserialization. Replace SetLocales() mutation with [Inject("locales")] for MMDB and WithLocales() using `with` expressions for web service path. Remove deprecated MetroCode property and deprecated backward-compatibility constructors on Traits and InsightsResponse. Add IsExternalInit package for netstandard targets. This is a breaking change for the next major version. ENG-3437 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent cf60cc8 commit 39bc349

32 files changed

Lines changed: 287 additions & 1272 deletions

MaxMind.GeoIP2.UnitTests/DeserializationTests.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
1-
#region
2-
31
using MaxMind.GeoIP2.Responses;
42
using System.Text;
53
using System.Text.Json;
64
using Xunit;
75
using static MaxMind.GeoIP2.UnitTests.ResponseHelper;
86

9-
#endregion
10-
117
namespace MaxMind.GeoIP2.UnitTests
128
{
139
public class DeserializationTests

MaxMind.GeoIP2.UnitTests/Model/LocationTests.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,18 @@ public class LocationTests
1616
public void HasCoordinatesFailure(double? latitude, double? longitude)
1717
{
1818
var location = new Location
19-
(latitude: latitude, longitude: longitude);
19+
{
20+
Latitude = latitude,
21+
Longitude = longitude
22+
};
2023

2124
Assert.False(location.HasCoordinates);
2225
}
2326

2427
[Fact]
2528
public void HasCoordinatesSuccess()
2629
{
27-
var location = new Location(latitude: 50.0, longitude: 0.0);
30+
var location = new Location { Latitude = 50.0, Longitude = 0.0 };
2831
Assert.True(location.HasCoordinates);
2932
}
3033
}
Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,33 @@
1-
#region
2-
31
using MaxMind.GeoIP2.Model;
42
using System.Collections.Generic;
53
using Xunit;
64

7-
#endregion
8-
95
namespace MaxMind.GeoIP2.UnitTests
106
{
117
public class NamedEntityTests
128
{
139
[Fact]
1410
public void CanGetSingleName()
1511
{
16-
var c = new City(
17-
names: new Dictionary<string, string> { { "en", "Foo" } },
18-
locales: new List<string> { "en" }
19-
);
12+
var c = new City
13+
{
14+
Names = new Dictionary<string, string> { { "en", "Foo" } },
15+
Locales = new List<string> { "en" }
16+
};
2017

2118
Assert.Equal("Foo", c.Name);
2219
}
2320

2421
[Fact]
2522
public void NameReturnsCorrectLocale()
2623
{
27-
var c = new City(
28-
names: new Dictionary<string, string> { { "en", "Mexico City" }, { "es", "Ciudad de México" } },
29-
locales: new List<string> { "es" }
30-
);
24+
var c = new City
25+
{
26+
Names = new Dictionary<string, string> { { "en", "Mexico City" }, { "es", "Ciudad de México" } },
27+
Locales = new List<string> { "es" }
28+
};
3129

3230
Assert.Equal("Ciudad de México", c.Name);
3331
}
3432
}
35-
}
33+
}
Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
1-
#region
2-
31
using MaxMind.GeoIP2.Model;
42
using MaxMind.GeoIP2.Responses;
53
using Xunit;
64

7-
#endregion
8-
95
namespace MaxMind.GeoIP2.UnitTests
106
{
117
public class ResponseTests
@@ -14,9 +10,9 @@ public class ResponseTests
1410
public void InsightsConstruction()
1511
{
1612
var city = new City();
17-
var insightsReponse = new InsightsResponse(city: city);
13+
var insightsReponse = new InsightsResponse { City = city };
1814

1915
Assert.Equal(insightsReponse.City, city);
2016
}
2117
}
22-
}
18+
}

MaxMind.GeoIP2/DatabaseReader.cs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -525,15 +525,9 @@ private bool TryExecute<T>(IPAddress ipAddress, string type, [MaybeNullWhen(fals
525525

526526
var injectables = new InjectableValues();
527527
injectables.AddValue("ip_address", ipStr);
528+
injectables.AddValue("locales", _locales);
528529
var response = _reader.Find<T>(ipAddress, injectables);
529530

530-
if (response == null)
531-
{
532-
return null;
533-
}
534-
535-
response.SetLocales(_locales);
536-
537531
return response;
538532
}
539533
}

MaxMind.GeoIP2/MaxMind.GeoIP2.csproj

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,18 @@
4444
</PropertyGroup>
4545

4646
<ItemGroup>
47-
<PackageReference Include="MaxMind.Db" Version="4.3.4" />
47+
<ProjectReference Include="../../../../../MaxMind-DB-Reader-dotnet/.worktrees/greg/eng-3437/MaxMind.Db/MaxMind.Db.csproj" />
4848
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.3" />
4949
</ItemGroup>
5050

5151
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0' Or '$(TargetFramework)' == 'netstandard2.1' ">
5252
<PackageReference Include="System.Text.Json">
5353
<Version>10.0.3</Version>
5454
</PackageReference>
55+
<PackageReference Include="IsExternalInit" Version="1.0.3">
56+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
57+
<PrivateAssets>all</PrivateAssets>
58+
</PackageReference>
5559
</ItemGroup>
5660

5761
<ItemGroup>

MaxMind.GeoIP2/Model/Anonymizer.cs

Lines changed: 10 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,30 @@
1-
#region
2-
31
using System;
42
using System.Text.Json.Serialization;
53

6-
#endregion
7-
84
namespace MaxMind.GeoIP2.Model
95
{
106
/// <summary>
117
/// Contains anonymizer-related data associated with an IP address.
128
/// This data is available from the GeoIP2 Insights web service.
139
/// </summary>
14-
public class Anonymizer
10+
public record Anonymizer
1511
{
16-
/// <summary>
17-
/// Constructor
18-
/// </summary>
19-
public Anonymizer()
20-
{
21-
}
22-
23-
/// <summary>
24-
/// Constructor
25-
/// </summary>
26-
public Anonymizer(
27-
int? confidence = null,
28-
bool isAnonymous = false,
29-
bool isAnonymousVpn = false,
30-
bool isHostingProvider = false,
31-
bool isPublicProxy = false,
32-
bool isResidentialProxy = false,
33-
bool isTorExitNode = false,
34-
#if NET6_0_OR_GREATER
35-
DateOnly? networkLastSeen = null,
36-
#endif
37-
string? providerName = null
38-
)
39-
{
40-
Confidence = confidence;
41-
IsAnonymous = isAnonymous;
42-
IsAnonymousVpn = isAnonymousVpn;
43-
IsHostingProvider = isHostingProvider;
44-
IsPublicProxy = isPublicProxy;
45-
IsResidentialProxy = isResidentialProxy;
46-
IsTorExitNode = isTorExitNode;
47-
#if NET6_0_OR_GREATER
48-
NetworkLastSeen = networkLastSeen;
49-
#endif
50-
ProviderName = providerName;
51-
}
52-
5312
/// <summary>
5413
/// A score ranging from 1 to 99 that represents our percent confidence
5514
/// that the network is currently part of an actively used VPN service.
5615
/// This is available from the GeoIP2 Insights web service.
5716
/// </summary>
5817
[JsonInclude]
5918
[JsonPropertyName("confidence")]
60-
public int? Confidence { get; internal set; }
19+
public int? Confidence { get; init; }
6120

6221
/// <summary>
6322
/// This is true if the IP address belongs to any sort of anonymous
6423
/// network. This is available from the GeoIP2 Insights web service.
6524
/// </summary>
6625
[JsonInclude]
6726
[JsonPropertyName("is_anonymous")]
68-
public bool IsAnonymous { get; internal set; }
27+
public bool IsAnonymous { get; init; }
6928

7029
/// <summary>
7130
/// This is true if the IP address is registered to an anonymous
@@ -79,7 +38,7 @@ public Anonymizer(
7938
/// </remarks>
8039
[JsonInclude]
8140
[JsonPropertyName("is_anonymous_vpn")]
82-
public bool IsAnonymousVpn { get; internal set; }
41+
public bool IsAnonymousVpn { get; init; }
8342

8443
/// <summary>
8544
/// This is true if the IP address belongs to a hosting or VPN
@@ -88,15 +47,15 @@ public Anonymizer(
8847
/// </summary>
8948
[JsonInclude]
9049
[JsonPropertyName("is_hosting_provider")]
91-
public bool IsHostingProvider { get; internal set; }
50+
public bool IsHostingProvider { get; init; }
9251

9352
/// <summary>
9453
/// This is true if the IP address belongs to a public proxy.
9554
/// This is available from the GeoIP2 Insights web service.
9655
/// </summary>
9756
[JsonInclude]
9857
[JsonPropertyName("is_public_proxy")]
99-
public bool IsPublicProxy { get; internal set; }
58+
public bool IsPublicProxy { get; init; }
10059

10160
/// <summary>
10261
/// This is true if the IP address is on a suspected anonymizing
@@ -105,15 +64,15 @@ public Anonymizer(
10564
/// </summary>
10665
[JsonInclude]
10766
[JsonPropertyName("is_residential_proxy")]
108-
public bool IsResidentialProxy { get; internal set; }
67+
public bool IsResidentialProxy { get; init; }
10968

11069
/// <summary>
11170
/// This is true if the IP address belongs to a Tor exit node.
11271
/// This is available from the GeoIP2 Insights web service.
11372
/// </summary>
11473
[JsonInclude]
11574
[JsonPropertyName("is_tor_exit_node")]
116-
public bool IsTorExitNode { get; internal set; }
75+
public bool IsTorExitNode { get; init; }
11776

11877
#if NET6_0_OR_GREATER
11978
/// <summary>
@@ -123,7 +82,7 @@ public Anonymizer(
12382
/// </summary>
12483
[JsonInclude]
12584
[JsonPropertyName("network_last_seen")]
126-
public DateOnly? NetworkLastSeen { get; internal set; }
85+
public DateOnly? NetworkLastSeen { get; init; }
12786
#endif
12887

12988
/// <summary>
@@ -133,27 +92,6 @@ public Anonymizer(
13392
/// </summary>
13493
[JsonInclude]
13594
[JsonPropertyName("provider_name")]
136-
public string? ProviderName { get; internal set; }
137-
138-
/// <summary>
139-
/// Returns a <see cref="string" /> that represents this instance.
140-
/// </summary>
141-
/// <returns>
142-
/// A <see cref="string" /> that represents this instance.
143-
/// </returns>
144-
public override string ToString()
145-
{
146-
return $"{nameof(Confidence)}: {Confidence}, " +
147-
$"{nameof(IsAnonymous)}: {IsAnonymous}, " +
148-
$"{nameof(IsAnonymousVpn)}: {IsAnonymousVpn}, " +
149-
$"{nameof(IsHostingProvider)}: {IsHostingProvider}, " +
150-
$"{nameof(IsPublicProxy)}: {IsPublicProxy}, " +
151-
$"{nameof(IsResidentialProxy)}: {IsResidentialProxy}, " +
152-
$"{nameof(IsTorExitNode)}: {IsTorExitNode}, " +
153-
#if NET6_0_OR_GREATER
154-
$"{nameof(NetworkLastSeen)}: {NetworkLastSeen}, " +
155-
#endif
156-
$"{nameof(ProviderName)}: {ProviderName}";
157-
}
95+
public string? ProviderName { get; init; }
15896
}
15997
}

MaxMind.GeoIP2/Model/City.cs

Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
1-
#region
2-
31
using MaxMind.Db;
4-
using System.Collections.Generic;
52
using System.Text.Json.Serialization;
63

7-
#endregion
8-
94
namespace MaxMind.GeoIP2.Model
105
{
116
/// <summary>
@@ -15,35 +10,16 @@ namespace MaxMind.GeoIP2.Model
1510
/// Do not use any of the city names as a database or dictionary
1611
/// key. Use the <see cred="GeoNameId" /> instead.
1712
/// </remarks>
18-
public class City : NamedEntity
13+
public record City : NamedEntity
1914
{
20-
/// <summary>
21-
/// Constructor
22-
/// </summary>
23-
public City()
24-
{
25-
}
26-
27-
/// <summary>
28-
/// Constructor
29-
/// </summary>
30-
[Constructor]
31-
public City(int? confidence = null,
32-
[Parameter("geoname_id")] long? geoNameId = null,
33-
IReadOnlyDictionary<string, string>? names = null,
34-
IReadOnlyList<string>? locales = null)
35-
: base(geoNameId, names, locales)
36-
{
37-
Confidence = confidence;
38-
}
39-
4015
/// <summary>
4116
/// A value from 0-100 indicating MaxMind's confidence that the city
4217
/// is correct. This value is only set when using the Insights
4318
/// web service or the Enterprise database.
4419
/// </summary>
4520
[JsonInclude]
4621
[JsonPropertyName("confidence")]
47-
public int? Confidence { get; internal set; }
22+
[MapKey("confidence")]
23+
public int? Confidence { get; init; }
4824
}
49-
}
25+
}

0 commit comments

Comments
 (0)