Skip to content

Commit 60151f8

Browse files
authored
Add IPApiLite to support Lite API (#34)
* Update System.Text.Json to 6.0.10 More info here GHSA-8g4q-xg66-9fp4 * Add support for Lite API * Add workflow to run tests on PRs and push on main * Add missing semicolon * Revert PublicKey in project file * Properly name constructors * Fix BaseUrl assignment * Fix accessibility modified for BaseUrl * Remove unnecessary uri string builder * Use the correct endpoint when IP is not set * Fix response deserialization * Update README.md
1 parent b4e874c commit 60151f8

9 files changed

Lines changed: 525 additions & 9 deletions

File tree

.github/workflows/test.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Run tests
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- main
10+
11+
jobs:
12+
build:
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
- uses: actions/checkout@v3
17+
18+
- name: Setup .NET
19+
uses: actions/setup-dotnet@v3
20+
with:
21+
dotnet-version: "6.x"
22+
23+
- name: Create Strong Name Keypair
24+
run: echo "${{ secrets.SNK_BASE64 }}" | base64 --decode > sgKeyIPinfoStrongName.snk
25+
26+
- name: Restore dependencies
27+
run: dotnet restore
28+
29+
- name: Test
30+
run: dotnet test
31+
env:
32+
IPINFO_TOKEN: ${{ secrets.IPINFO_TOKEN }}
33+
34+
- name: Build
35+
run: dotnet build --configuration Release /p:AssemblyOriginatorKeyFile=sgKeyIPinfoStrongName.snk

IPinfo.Tests/IPApiLiteTest.cs

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Xunit;
4+
5+
using IPinfo.Models;
6+
7+
namespace IPinfo.Tests
8+
{
9+
public class IPApiLiteTest
10+
{
11+
[Fact]
12+
public void TestGetDetails()
13+
{
14+
string ip = "8.8.8.8";
15+
IPinfoClientLite client = new IPinfoClientLite.Builder()
16+
.AccessToken(Environment.GetEnvironmentVariable("IPINFO_TOKEN"))
17+
.Build();
18+
19+
IPResponseLite actual = client.IPApi.GetDetails(ip);
20+
21+
var expectations = new List<Tuple<object, object>>()
22+
{
23+
new("8.8.8.8", actual.IP),
24+
new("AS15169", actual.Asn),
25+
new("Google LLC", actual.AsName),
26+
new("google.com", actual.AsDomain),
27+
new("US", actual.CountryCode),
28+
new("United States", actual.Country),
29+
new("United States", actual.CountryName),
30+
new(false, actual.IsEU),
31+
new("🇺🇸", actual.CountryFlag.Emoji),
32+
new("U+1F1FA U+1F1F8", actual.CountryFlag.Unicode),
33+
new("https://cdn.ipinfo.io/static/images/countries-flags/US.svg", actual.CountryFlagURL),
34+
new("USD", actual.CountryCurrency.Code),
35+
new("$", actual.CountryCurrency.Symbol),
36+
new("NA", actual.Continent.Code),
37+
new("North America", actual.Continent.Name),
38+
};
39+
Assert.All(expectations, pair => Assert.Equal(pair.Item1, pair.Item2));
40+
}
41+
42+
[Fact]
43+
public void TestGetDetailsIPV6()
44+
{
45+
string ip = "2001:4860:4860::8888";
46+
IPinfoClientLite client = new IPinfoClientLite.Builder()
47+
.AccessToken(Environment.GetEnvironmentVariable("IPINFO_TOKEN"))
48+
.Build();
49+
50+
IPResponseLite actual = client.IPApi.GetDetails(ip);
51+
52+
var expectations = new List<Tuple<object, object>>()
53+
{
54+
new("2001:4860:4860::8888", actual.IP),
55+
new("AS15169", actual.Asn),
56+
new("Google LLC", actual.AsName),
57+
new("google.com", actual.AsDomain),
58+
new("US", actual.CountryCode),
59+
new("United States", actual.Country),
60+
new("United States", actual.CountryName),
61+
new(false, actual.IsEU),
62+
new("🇺🇸", actual.CountryFlag.Emoji),
63+
new("U+1F1FA U+1F1F8", actual.CountryFlag.Unicode),
64+
new("https://cdn.ipinfo.io/static/images/countries-flags/US.svg", actual.CountryFlagURL),
65+
new("USD", actual.CountryCurrency.Code),
66+
new("$", actual.CountryCurrency.Symbol),
67+
new("NA", actual.Continent.Code),
68+
new("North America", actual.Continent.Name),
69+
};
70+
Assert.All(expectations, pair => Assert.Equal(pair.Item1, pair.Item2));
71+
}
72+
73+
[Fact]
74+
public void TestGetDetailsCurrentIP()
75+
{
76+
IPinfoClientLite client = new IPinfoClientLite.Builder()
77+
.AccessToken(Environment.GetEnvironmentVariable("IPINFO_TOKEN"))
78+
.Build();
79+
80+
IPResponseLite actual = client.IPApi.GetDetails();
81+
82+
Assert.NotNull(actual.IP);
83+
Assert.NotNull(actual.Asn);
84+
Assert.NotNull(actual.AsName);
85+
Assert.NotNull(actual.AsDomain);
86+
Assert.NotNull(actual.CountryCode);
87+
Assert.NotNull(actual.Country);
88+
Assert.NotNull(actual.CountryName);
89+
Assert.NotNull(actual.CountryFlag);
90+
Assert.NotNull(actual.CountryFlag.Emoji);
91+
Assert.NotNull(actual.CountryFlag.Unicode);
92+
Assert.NotNull(actual.CountryFlagURL);
93+
Assert.NotNull(actual.CountryCurrency);
94+
Assert.NotNull(actual.Continent);
95+
}
96+
97+
[Fact]
98+
public void TestBogonIPV4()
99+
{
100+
string ip = "127.0.0.1";
101+
IPinfoClientLite client = new IPinfoClientLite.Builder()
102+
.AccessToken(Environment.GetEnvironmentVariable("IPINFO_TOKEN"))
103+
.Build();
104+
105+
IPResponseLite actual = client.IPApi.GetDetails(ip);
106+
107+
Assert.Equal("127.0.0.1", actual.IP);
108+
Assert.True(actual.Bogon);
109+
}
110+
111+
[Fact]
112+
public void TestBogonIPV6()
113+
{
114+
string ip = "2001:0:c000:200::0:255:1";
115+
IPinfoClientLite client = new IPinfoClientLite.Builder()
116+
.AccessToken(Environment.GetEnvironmentVariable("IPINFO_TOKEN"))
117+
.Build();
118+
119+
IPResponseLite actual = client.IPApi.GetDetails(ip);
120+
121+
Assert.Equal("2001:0:c000:200::0:255:1", actual.IP);
122+
Assert.True(actual.Bogon);
123+
}
124+
125+
[Fact]
126+
public void TestNonBogonIPV4()
127+
{
128+
string ip = "1.1.1.1";
129+
IPinfoClientLite client = new IPinfoClientLite.Builder()
130+
.AccessToken(Environment.GetEnvironmentVariable("IPINFO_TOKEN"))
131+
.Build();
132+
133+
IPResponseLite actual = client.IPApi.GetDetails(ip);
134+
135+
Assert.Equal("1.1.1.1", actual.IP);
136+
Assert.False(actual.Bogon);
137+
}
138+
139+
[Fact]
140+
public void TestNonBogonIPV6()
141+
{
142+
string ip = "2a03:2880:f10a:83:face:b00c:0:25de";
143+
IPinfoClientLite client = new IPinfoClientLite.Builder()
144+
.AccessToken(Environment.GetEnvironmentVariable("IPINFO_TOKEN"))
145+
.Build();
146+
147+
IPResponseLite actual = client.IPApi.GetDetails(ip);
148+
149+
Assert.Equal("2a03:2880:f10a:83:face:b00c:0:25de", actual.IP);
150+
Assert.False(actual.Bogon);
151+
}
152+
}
153+
}

README.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ You'll need an IPinfo API access token, which you can get by signing up for a fr
1616

1717
The free plan is limited to 50,000 requests per month, and doesn't include some of the data fields such as IP type and company data. To enable all the data fields and additional request volumes see [https://ipinfo.io/pricing](https://ipinfo.io/pricing)
1818

19-
⚠️ Note: This SDK does not currently support our newest free API https://ipinfo.io/lite. If you’d like to use IPinfo Lite, you can call the [endpoint directly](https://ipinfo.io/developers/lite-api) using your preferred HTTP client. Developers are also welcome to contribute support for Lite by submitting a pull request.
19+
The library also supports the Lite API, see the [Lite API section](#lite-api) for more info.
2020

2121
### Installation
2222

@@ -126,6 +126,33 @@ Console.WriteLine($"IPResponse.Continent.Code: {ipResponse.Continent.Code}");
126126
Console.WriteLine($"IPResponse.Continent.Name: {ipResponse.Continent.Name}");
127127
```
128128

129+
### Lite API
130+
131+
The library gives the possibility to use the [Lite API](https://ipinfo.io/developers/lite-api) too, authentication with your token is still required.
132+
133+
The returned details are slightly different from the Core API.
134+
135+
```csharp
136+
// namespace
137+
using IPinfo;
138+
using IPinfo.Models;
139+
140+
// initializing IPinfo client for Lite API
141+
string token = "MY_TOKEN";
142+
IPinfoClientLite client = new IPinfoClientLite.Builder()
143+
.AccessToken(token)
144+
.Build();
145+
146+
// making API call
147+
string ip = "216.239.36.21";
148+
IPResponseLite ipResponse = await client.IPApi.GetDetailsAsync(ip);
149+
150+
// accessing details from response
151+
Console.WriteLine($"IPResponse.IP: {ipResponse.IP}");
152+
Console.WriteLine($"IPResponse.Country: {ipResponse.Country}");
153+
Console.WriteLine($"IPResponse.CountryName: {ipResponse.CountryName}");
154+
```
155+
129156
### Caching
130157

131158
In-memory caching of data is provided by default. Custom implementation of the cache can also be provided by implementing the `ICache` interface.

src/IPinfo/Apis/BaseApi.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ internal BaseApi(
6060
/// <summary>
6161
/// Gets base url values.
6262
/// </summary>
63-
internal string BaseUrl => DefaultBaseUrl;
63+
protected string BaseUrl { get; set; } = DefaultBaseUrl;
6464
internal string BaseUrlIPv6 => DefaultBaseUrlIPv6;
6565

6666
/// <summary>

src/IPinfo/Apis/IPApiLite.cs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
using System.Collections.Generic;
2+
using System.Net;
3+
using System.Text;
4+
using System.Threading.Tasks;
5+
using System.Threading;
6+
7+
using IPinfo.Utilities;
8+
using IPinfo.Http.Client;
9+
using IPinfo.Http.Request;
10+
using IPinfo.Models;
11+
using IPinfo.Http.Response;
12+
using IPinfo.Cache;
13+
14+
namespace IPinfo.Apis
15+
{
16+
/// <summary>
17+
/// IPApiLite.
18+
/// </summary>
19+
public sealed class IPApiLite : BaseApi
20+
{
21+
/// <summary>
22+
/// Initializes a new instance of the <see cref="IPApiLite"/> class.
23+
/// </summary>
24+
/// <param name="httpClient"> httpClient. </param>
25+
/// <param name="token"> token. </param>
26+
internal IPApiLite(IHttpClient httpClient, string token, CacheHandler cacheHandler)
27+
: base(httpClient, token, cacheHandler)
28+
{
29+
this.BaseUrl = "https://api.ipinfo.io/lite/";
30+
}
31+
32+
/// <summary>
33+
/// Retrieves details of an IP address.
34+
/// </summary>
35+
/// <param name="ipAddress">The IP address of the user to retrieve details for.</param>
36+
/// <returns>Returns the Models.IPResponseLite response from the API call.</returns>
37+
public Models.IPResponseLite GetDetails(
38+
IPAddress ipAddress)
39+
{
40+
string ipString = ipAddress?.ToString();
41+
return this.GetDetails(ipString);
42+
}
43+
44+
/// <summary>
45+
/// Retrieves details of an IP address.
46+
/// </summary>
47+
/// <param name="ipAddress">The IP address of the user to retrieve details for.</param>
48+
/// <returns>Returns the Models.IPResponseLite response from the API call.</returns>
49+
public Models.IPResponseLite GetDetails(
50+
string ipAddress = "")
51+
{
52+
Task<Models.IPResponseLite> t = this.GetDetailsAsync(ipAddress);
53+
ApiHelper.RunTaskSynchronously(t);
54+
return t.Result;
55+
}
56+
57+
/// <summary>
58+
/// Retrieves details of an IP address.
59+
/// </summary>
60+
/// <param name="ipAddress">The IP address of the user to retrieve details for.</param>
61+
/// <param name="cancellationToken">Cancellation token if the request is cancelled. </param>
62+
/// <returns>Returns the Models.IPResponseLite response from the API call.</returns>
63+
public Task<Models.IPResponseLite> GetDetailsAsync(
64+
IPAddress ipAddress,
65+
CancellationToken cancellationToken = default)
66+
{
67+
string ipString = ipAddress?.ToString();
68+
return this.GetDetailsAsync(ipString, cancellationToken);
69+
}
70+
71+
72+
/// <summary>
73+
/// Retrieves details of an IP address.
74+
/// </summary>
75+
/// <param name="ipAddress">The IP address of the user to retrieve details for.</param>
76+
/// <param name="cancellationToken">Cancellation token if the request is cancelled. </param>
77+
/// <returns>Returns the Models.IPResponseLite response from the API call.</returns>
78+
public async Task<Models.IPResponseLite> GetDetailsAsync(
79+
string ipAddress = "",
80+
CancellationToken cancellationToken = default)
81+
{
82+
if (string.IsNullOrEmpty(ipAddress))
83+
{
84+
ipAddress = "me";
85+
}
86+
// first check the data in the cache if cache is available
87+
IPResponseLite ipResponse = (IPResponseLite)GetFromCache(ipAddress);
88+
if (ipResponse != null)
89+
{
90+
return ipResponse;
91+
}
92+
93+
if (BogonHelper.IsBogon(ipAddress))
94+
{
95+
ipResponse = new IPResponseLite()
96+
{
97+
IP = ipAddress,
98+
Bogon = true
99+
};
100+
return ipResponse;
101+
}
102+
103+
// prepare the API call request to fetch the response.
104+
HttpRequest httpRequest = this.CreateGetRequest(this.BaseUrl + ipAddress);
105+
// invoke request and get response.
106+
HttpStringResponse response = await this.GetClientInstance().ExecuteAsStringAsync(httpRequest, cancellationToken).ConfigureAwait(false);
107+
HttpContext context = new HttpContext(httpRequest, response);
108+
109+
// handle errors defined at the API level.
110+
this.ValidateResponse(context);
111+
112+
var responseModel = JsonHelper.ParseIPResponseLite(response.Body);
113+
114+
SetInCache(ipAddress, responseModel);
115+
return responseModel;
116+
}
117+
}
118+
}

src/IPinfo/IPinfo.csproj

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,14 @@
3535

3636
<ItemGroup>
3737
<PackageReference Include="System.Runtime.Caching" Version="6.0.0" />
38-
<PackageReference Include="System.Text.Json" Version="6.0.2" />
38+
<PackageReference Include="System.Text.Json" Version="6.0.10" />
3939
</ItemGroup>
40-
40+
4141
<ItemGroup>
4242
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
4343
<_Parameter1>IPinfo.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a3e25284f42d5a26fdeb222c8fb9ac5df0b7421bdf350a8e828c18f72f1ec06fec81eff098d3610ebd64ec3c3b9b66f3801fc3d38fde5968d04c46b319896074cdae04c20a5c4d2dc5438e259265617106567f7357b6daf0fbf301fbe43df5019c53c5668b231423e23411e6e4c4cfae2b0b2e0f6e1333e0fe2cd691c26717d4</_Parameter1>
4444
</AssemblyAttribute>
45+
4546
</ItemGroup>
4647

4748
<ItemGroup>

0 commit comments

Comments
 (0)