diff --git a/sdk/core/System.Net.ClientModel/tests/client/MapsClient/CountryRegion.cs b/sdk/core/System.Net.ClientModel/tests/client/MapsClient/CountryRegion.cs new file mode 100644 index 0000000000000..dda12fe02a801 --- /dev/null +++ b/sdk/core/System.Net.ClientModel/tests/client/MapsClient/CountryRegion.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Net.ClientModel.Core; +using System.Text.Json; + +namespace Maps; + +public class CountryRegion : IJsonModel +{ + internal CountryRegion(string isoCode) + { + IsoCode = isoCode; + } + + public string IsoCode { get; } + + internal static CountryRegion FromJson(JsonElement element) + { + if (element.ValueKind == JsonValueKind.Null) + { + return null; + } + + string isoCode = default; + + foreach (var property in element.EnumerateObject()) + { + if (property.NameEquals("isoCode"u8)) + { + isoCode = property.Value.GetString(); + continue; + } + } + + return new CountryRegion(isoCode); + } + + public CountryRegion Read(ref Utf8JsonReader reader, ModelReaderWriterOptions options) + { + using var document = JsonDocument.ParseValue(ref reader); + return FromJson(document.RootElement); + } + + public CountryRegion Read(BinaryData data, ModelReaderWriterOptions options) + { + using var document = JsonDocument.Parse(data.ToString()); + return FromJson(document.RootElement); + } + + public void Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) + { + throw new NotSupportedException("This model is used for output only"); + } + + public BinaryData Write(ModelReaderWriterOptions options) + { + throw new NotSupportedException("This model is used for output only"); + } +} diff --git a/sdk/core/System.Net.ClientModel/tests/client/MapsClient/IPAddressCountryPair.cs b/sdk/core/System.Net.ClientModel/tests/client/MapsClient/IPAddressCountryPair.cs new file mode 100644 index 0000000000000..0b7490533739b --- /dev/null +++ b/sdk/core/System.Net.ClientModel/tests/client/MapsClient/IPAddressCountryPair.cs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Net; +using System.Net.ClientModel.Core; +using System.Text.Json; + +namespace Maps; + +public class IPAddressCountryPair : IJsonModel +{ + internal IPAddressCountryPair(CountryRegion countryRegion, IPAddress ipAddress) + { + CountryRegion = countryRegion; + IpAddress = ipAddress; + } + + public CountryRegion CountryRegion { get; } + + public IPAddress IpAddress { get; } + + internal static IPAddressCountryPair FromJson(JsonElement element) + { + if (element.ValueKind == JsonValueKind.Null) + { + return null; + } + + CountryRegion countryRegion = default; + IPAddress ipAddress = default; + + foreach (var property in element.EnumerateObject()) + { + if (property.NameEquals("countryRegion"u8)) + { + if (property.Value.ValueKind == JsonValueKind.Null) + { + continue; + } + + countryRegion = CountryRegion.FromJson(property.Value); + continue; + } + + if (property.NameEquals("ipAddress"u8)) + { + if (property.Value.ValueKind == JsonValueKind.Null) + { + continue; + } + + ipAddress = IPAddress.Parse(property.Value.GetString()); + continue; + } + } + + return new IPAddressCountryPair(countryRegion, ipAddress); + } + + internal static IPAddressCountryPair FromResponse(PipelineResponse response) + { + using var document = JsonDocument.Parse(response.Content); + return FromJson(document.RootElement); + } + + public IPAddressCountryPair Read(ref Utf8JsonReader reader, ModelReaderWriterOptions options) + { + using var document = JsonDocument.ParseValue(ref reader); + return FromJson(document.RootElement); + } + + public IPAddressCountryPair Read(BinaryData data, ModelReaderWriterOptions options) + { + using var document = JsonDocument.Parse(data.ToString()); + return FromJson(document.RootElement); + } + + public void Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) + { + throw new NotSupportedException("This model is used for output only"); + } + + public BinaryData Write(ModelReaderWriterOptions options) + { + throw new NotSupportedException("This model is used for output only"); + } +} diff --git a/sdk/core/System.Net.ClientModel/tests/client/MapsClient/MapsClient.cs b/sdk/core/System.Net.ClientModel/tests/client/MapsClient/MapsClient.cs new file mode 100644 index 0000000000000..017747f406d22 --- /dev/null +++ b/sdk/core/System.Net.ClientModel/tests/client/MapsClient/MapsClient.cs @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Net; +using System.Net.ClientModel; +using System.Net.ClientModel.Core; +using System.Text; +using System.Threading; + +namespace Maps; + +public class MapsClient +{ + private readonly Uri _endpoint; + private readonly KeyCredential _credential; + private readonly MessagePipeline _pipeline; + private readonly string _apiVersion; + + public MapsClient(Uri endpoint, KeyCredential credential, MapsClientOptions options = default) + { + if (endpoint is null) throw new ArgumentNullException(nameof(endpoint)); + if (credential is null) throw new ArgumentNullException(nameof(credential)); + + options ??= new MapsClientOptions(); + + _endpoint = endpoint; + _credential = credential; + _pipeline = MessagePipeline.Create(options, new KeyCredentialAuthenticationPolicy(_credential, "subscription-key")); + _apiVersion = options.Version; + } + + public virtual Result GetCountryCode(IPAddress ipAddress, CancellationToken cancellationToken = default) + { + if (ipAddress is null) throw new ArgumentNullException(nameof(ipAddress)); + + RequestOptions options = cancellationToken.CanBeCanceled ? + new RequestOptions() { CancellationToken = cancellationToken } : + new RequestOptions(); + + Result result = GetCountryCode(ipAddress.ToString(), options); + + PipelineResponse response = result.GetRawResponse(); + IPAddressCountryPair value = IPAddressCountryPair.FromResponse(response); + + return Result.FromValue(value, response); + } + + public virtual Result GetCountryCode(string ipAddress, RequestOptions options = null) + { + if (ipAddress is null) throw new ArgumentNullException(nameof(ipAddress)); + + options ??= new RequestOptions(); + options.MessageClassifier = MessageClassifier200; + + using PipelineMessage message = CreateGetLocationRequest(ipAddress, options); + + _pipeline.Send(message); + + PipelineResponse response = message.Response; + + if (response.IsError && options.ErrorBehavior == ErrorBehavior.Default) + { + throw new UnsuccessfulRequestException(response); + } + + return Result.FromResponse(response); + } + + private PipelineMessage CreateGetLocationRequest(string ipAddress, RequestOptions options) + { + PipelineMessage message = _pipeline.CreateMessage(); + options.Apply(message); + + PipelineRequest request = message.Request; + request.Method = "GET"; + + UriBuilder uriBuilder = new(_endpoint.ToString()); + + StringBuilder path = new(); + path.Append("geolocation/ip"); + path.Append("/json"); + uriBuilder.Path += path.ToString(); + + StringBuilder query = new(); + query.Append("api-version="); + query.Append(Uri.EscapeDataString(_apiVersion)); + query.Append("&ip="); + query.Append(Uri.EscapeDataString(ipAddress)); + uriBuilder.Query = query.ToString(); + + request.Uri = uriBuilder.Uri; + + request.Headers.Add("Accept", "application/json"); + + return message; + } +} diff --git a/sdk/core/System.Net.ClientModel/tests/client/MapsClient/MapsClientOptions.cs b/sdk/core/System.Net.ClientModel/tests/client/MapsClient/MapsClientOptions.cs new file mode 100644 index 0000000000000..4d27bf8c92f92 --- /dev/null +++ b/sdk/core/System.Net.ClientModel/tests/client/MapsClient/MapsClientOptions.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Net.ClientModel; + +namespace Maps; + +public class MapsClientOptions : RequestOptions +{ + private const ServiceVersion LatestVersion = ServiceVersion.V1; + + public enum ServiceVersion + { + V1 = 1 + } + + internal string Version { get; } + + internal Uri Endpoint { get; } + + public MapsClientOptions(ServiceVersion version = LatestVersion) + { + Version = version switch + { + ServiceVersion.V1 => "1.0", + _ => throw new NotSupportedException() + }; + } +} diff --git a/sdk/core/System.Net.ClientModel/tests/client/MapsClientTests.cs b/sdk/core/System.Net.ClientModel/tests/client/MapsClientTests.cs new file mode 100644 index 0000000000000..af100083c09bf --- /dev/null +++ b/sdk/core/System.Net.ClientModel/tests/client/MapsClientTests.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using NUnit.Framework; +using Maps; + +namespace System.Net.ClientModel.Tests; + +public class MapsClientTests +{ + // This is a "TestSupportProject", so these tests will never be run as part of CIs. + // It's here now for quick manual validation of client functionality, but we can revisit + // this story going forward. + [Test] + public void TestClientSync() + { + string key = Environment.GetEnvironmentVariable("MAPS_API_KEY"); + + KeyCredential credential = new KeyCredential(key); + MapsClient client = new MapsClient(new Uri("https://atlas.microsoft.com"), credential); + + IPAddress ipAddress = IPAddress.Parse("2001:4898:80e8:b::189"); + Result result = client.GetCountryCode(ipAddress); + + Assert.AreEqual("US", result.Value.CountryRegion.IsoCode); + Assert.AreEqual(IPAddress.Parse("2001:4898:80e8:b::189"), result.Value.IpAddress); + } +} diff --git a/sdk/maps/Azure.Maps.Geolocation/Azure.Maps.Geolocation.sln b/sdk/maps/Azure.Maps.Geolocation/Azure.Maps.Geolocation.sln new file mode 100644 index 0000000000000..db5cebbba91ac --- /dev/null +++ b/sdk/maps/Azure.Maps.Geolocation/Azure.Maps.Geolocation.sln @@ -0,0 +1,43 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32526.322 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Maps.Geolocation", "src\Azure.Maps.Geolocation.csproj", "{7DCCA8AB-B7D4-491A-BDCC-FB64DE686FE7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Maps.Geolocation.Tests", "tests\Azure.Maps.Geolocation.Tests.csproj", "{3A87F8D0-5D67-4886-8F0C-64E688B02DF4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.TestFramework", "..\..\core\Azure.Core.TestFramework\src\Azure.Core.TestFramework.csproj", "{9CF40117-7082-4DA2-87E5-48A0EE167391}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestHarness", "TestHarness\TestHarness.csproj", "{CE0A1047-7FAA-4837-86B0-4A2B6156E511}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7DCCA8AB-B7D4-491A-BDCC-FB64DE686FE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7DCCA8AB-B7D4-491A-BDCC-FB64DE686FE7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7DCCA8AB-B7D4-491A-BDCC-FB64DE686FE7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7DCCA8AB-B7D4-491A-BDCC-FB64DE686FE7}.Release|Any CPU.Build.0 = Release|Any CPU + {3A87F8D0-5D67-4886-8F0C-64E688B02DF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3A87F8D0-5D67-4886-8F0C-64E688B02DF4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3A87F8D0-5D67-4886-8F0C-64E688B02DF4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3A87F8D0-5D67-4886-8F0C-64E688B02DF4}.Release|Any CPU.Build.0 = Release|Any CPU + {9CF40117-7082-4DA2-87E5-48A0EE167391}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9CF40117-7082-4DA2-87E5-48A0EE167391}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9CF40117-7082-4DA2-87E5-48A0EE167391}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9CF40117-7082-4DA2-87E5-48A0EE167391}.Release|Any CPU.Build.0 = Release|Any CPU + {CE0A1047-7FAA-4837-86B0-4A2B6156E511}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CE0A1047-7FAA-4837-86B0-4A2B6156E511}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CE0A1047-7FAA-4837-86B0-4A2B6156E511}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CE0A1047-7FAA-4837-86B0-4A2B6156E511}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {122670A6-DDC3-4993-85DB-C594DCCD1AA5} + EndGlobalSection +EndGlobal diff --git a/sdk/maps/Azure.Maps.Geolocation/TestHarness/Program.cs b/sdk/maps/Azure.Maps.Geolocation/TestHarness/Program.cs new file mode 100644 index 0000000000000..9e3bef82aca08 --- /dev/null +++ b/sdk/maps/Azure.Maps.Geolocation/TestHarness/Program.cs @@ -0,0 +1,17 @@ +using System.Net; +using Azure; +using Azure.Maps.Geolocation; +using NUnit.Framework; + +// See https://aka.ms/new-console-template for more information +Console.WriteLine("Hello, World!"); + + +string key = Environment.GetEnvironmentVariable("AZURE_MAPS_API_KEY"); +AzureKeyCredential credential = new AzureKeyCredential(key); +MapsGeolocationClient client = new MapsGeolocationClient(credential); +IPAddress ipAddress = IPAddress.Parse("2001:4898:80e8:b::189"); +CountryRegionResult result = client.GetCountryCode(ipAddress); + + +Assert.AreEqual("US", result.IsoCode); diff --git a/sdk/maps/Azure.Maps.Geolocation/TestHarness/TestHarness.csproj b/sdk/maps/Azure.Maps.Geolocation/TestHarness/TestHarness.csproj new file mode 100644 index 0000000000000..265d2df75dc09 --- /dev/null +++ b/sdk/maps/Azure.Maps.Geolocation/TestHarness/TestHarness.csproj @@ -0,0 +1,18 @@ + + + + Exe + net7.0 + enable + + + + + + + + + + + + diff --git a/sdk/maps/Azure.Maps.Geolocation/src/Azure.Maps.Geolocation.sln b/sdk/maps/Azure.Maps.Geolocation/src/Azure.Maps.Geolocation.sln new file mode 100644 index 0000000000000..109e97b77e4ed --- /dev/null +++ b/sdk/maps/Azure.Maps.Geolocation/src/Azure.Maps.Geolocation.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.34202.233 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Maps.Geolocation", "Azure.Maps.Geolocation.csproj", "{BEB6B073-3AF3-4E66-B336-2FFCA33A3EA3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BEB6B073-3AF3-4E66-B336-2FFCA33A3EA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BEB6B073-3AF3-4E66-B336-2FFCA33A3EA3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BEB6B073-3AF3-4E66-B336-2FFCA33A3EA3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BEB6B073-3AF3-4E66-B336-2FFCA33A3EA3}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E49D96C1-A873-49C3-BFA7-AA126DBFF617} + EndGlobalSection +EndGlobal