diff --git a/.gitattributes b/.gitattributes index 1ff0c42..682d4fa 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,63 +1,19 @@ -############################################################################### -# Set default behavior to automatically normalize line endings. -############################################################################### * text=auto -############################################################################### -# Set default behavior for command prompt diff. -# -# This is need for earlier builds of msysgit that does not have it on by -# default for csharp files. -# Note: This is only used by command line -############################################################################### -#*.cs diff=csharp +# Force bash scripts to always use lf line endings so that if a repo is accessed +# in Unix via a file share from Windows, the scripts will work. +*.in text eol=lf +*.sh text eol=lf -############################################################################### -# Set the merge driver for project and solution files -# -# Merging from the command prompt will add diff markers to the files if there -# are conflicts (Merging from VS is not affected by the settings below, in VS -# the diff markers are never inserted). Diff markers may cause the following -# file extensions to fail to load in VS. An alternative would be to treat -# these files as binary and thus will always conflict and require user -# intervention with every merge. To do so, just uncomment the entries below -############################################################################### -#*.sln merge=binary -#*.csproj merge=binary -#*.vbproj merge=binary -#*.vcxproj merge=binary -#*.vcproj merge=binary -#*.dbproj merge=binary -#*.fsproj merge=binary -#*.lsproj merge=binary -#*.wixproj merge=binary -#*.modelproj merge=binary -#*.sqlproj merge=binary -#*.wwaproj merge=binary +# Likewise, force cmd and batch scripts to always use crlf +*.cmd text eol=crlf +*.bat text eol=crlf -############################################################################### -# behavior for image files -# -# image files are treated as binary by default. -############################################################################### -#*.jpg binary -#*.png binary -#*.gif binary +*.cs text=auto diff=csharp +*.csproj text=auto +*.sln text=auto +*.resx text=auto +*.xml text=auto +*.txt text=auto -############################################################################### -# diff behavior for common document formats -# -# Convert binary document formats to text before diffing them. This feature -# is only available from the command line. Turn it on by uncommenting the -# entries below. -############################################################################### -#*.doc diff=astextplain -#*.DOC diff=astextplain -#*.docx diff=astextplain -#*.DOCX diff=astextplain -#*.dot diff=astextplain -#*.DOT diff=astextplain -#*.pdf diff=astextplain -#*.PDF diff=astextplain -#*.rtf diff=astextplain -#*.RTF diff=astextplain +packages/ binary \ No newline at end of file diff --git a/GeoTools.GeoServer/Class1.cs b/GeoTools.GeoServer/Class1.cs deleted file mode 100644 index 9079c34..0000000 --- a/GeoTools.GeoServer/Class1.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace GeoTools.GeoServer -{ - public class Class1 - { - - } -} diff --git a/GeoTools.sln b/GeoTools.sln index 77dc30e..f636330 100644 --- a/GeoTools.sln +++ b/GeoTools.sln @@ -3,7 +3,11 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.4.33020.496 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GeoTools.GeoServer", "GeoTools.GeoServer\GeoTools.GeoServer.csproj", "{46D1A21A-2C2F-46CF-899C-90D3D8E3B747}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GeoTools.GeoServer", "src\GeoTools.GeoServer\GeoTools.GeoServer.csproj", "{46D1A21A-2C2F-46CF-899C-90D3D8E3B747}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GeoTools.GeoServer.Abstractions", "src\GeoTools.GeoServer.Abstractions\GeoTools.GeoServer.Abstractions.csproj", "{CD6C97F4-922E-4C71-AADF-926008764840}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GeoTools.GeoServer.Tests", "tests\GeoTools.GeoServer.Tests\GeoTools.GeoServer.Tests.csproj", "{AA3ECC68-7ACF-4625-896F-96948198ECFE}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -15,6 +19,14 @@ Global {46D1A21A-2C2F-46CF-899C-90D3D8E3B747}.Debug|Any CPU.Build.0 = Debug|Any CPU {46D1A21A-2C2F-46CF-899C-90D3D8E3B747}.Release|Any CPU.ActiveCfg = Release|Any CPU {46D1A21A-2C2F-46CF-899C-90D3D8E3B747}.Release|Any CPU.Build.0 = Release|Any CPU + {CD6C97F4-922E-4C71-AADF-926008764840}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD6C97F4-922E-4C71-AADF-926008764840}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD6C97F4-922E-4C71-AADF-926008764840}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD6C97F4-922E-4C71-AADF-926008764840}.Release|Any CPU.Build.0 = Release|Any CPU + {AA3ECC68-7ACF-4625-896F-96948198ECFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AA3ECC68-7ACF-4625-896F-96948198ECFE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA3ECC68-7ACF-4625-896F-96948198ECFE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AA3ECC68-7ACF-4625-896F-96948198ECFE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/GeoTools.GeoServer/GeoTools.GeoServer.csproj b/src/GeoTools.GeoServer.Abstractions/GeoTools.GeoServer.Abstractions.csproj similarity index 67% rename from GeoTools.GeoServer/GeoTools.GeoServer.csproj rename to src/GeoTools.GeoServer.Abstractions/GeoTools.GeoServer.Abstractions.csproj index 9f5c4f4..66646c1 100644 --- a/GeoTools.GeoServer/GeoTools.GeoServer.csproj +++ b/src/GeoTools.GeoServer.Abstractions/GeoTools.GeoServer.Abstractions.csproj @@ -4,4 +4,8 @@ netstandard2.0 + + + + diff --git a/src/GeoTools.GeoServer.Abstractions/Models/Workspace/GetWorkspaceResponse.cs b/src/GeoTools.GeoServer.Abstractions/Models/Workspace/GetWorkspaceResponse.cs new file mode 100644 index 0000000..0fda459 --- /dev/null +++ b/src/GeoTools.GeoServer.Abstractions/Models/Workspace/GetWorkspaceResponse.cs @@ -0,0 +1,12 @@ +namespace GeoTools.GeoServer.Models +{ + public class GetWorkspaceResponse + { + public WorkspaceSummary Workspace { get; } + + public GetWorkspaceResponse(WorkspaceSummary workspace) + { + Workspace = workspace; + } + } +} diff --git a/src/GeoTools.GeoServer.Abstractions/Models/Workspace/WorkspaceInfo.cs b/src/GeoTools.GeoServer.Abstractions/Models/Workspace/WorkspaceInfo.cs new file mode 100644 index 0000000..6affe17 --- /dev/null +++ b/src/GeoTools.GeoServer.Abstractions/Models/Workspace/WorkspaceInfo.cs @@ -0,0 +1,30 @@ +namespace GeoTools.GeoServer.Models +{ + /// + /// Workspace. + /// + public class WorkspaceInfo + { + /// + /// Name of the workspace. + /// + public string Name { get; } + + /// + /// Is the workspace content isolated so it is not included as part of + /// global web services. + /// + /// + /// Isolated workspaces content is only visible and queryable in the + /// context of a virtual service bound to the isolated workspace. + /// + /// False by default. + public bool Isolated { get; } + + public WorkspaceInfo(string name, bool isolated = false) + { + Name = name; + Isolated = isolated; + } + } +} diff --git a/src/GeoTools.GeoServer.Abstractions/Models/Workspace/WorkspaceSummary.cs b/src/GeoTools.GeoServer.Abstractions/Models/Workspace/WorkspaceSummary.cs new file mode 100644 index 0000000..a589138 --- /dev/null +++ b/src/GeoTools.GeoServer.Abstractions/Models/Workspace/WorkspaceSummary.cs @@ -0,0 +1,48 @@ +namespace GeoTools.GeoServer.Models +{ + /// + /// Workspace Response. + /// + public class WorkspaceSummary + { + /// + /// Name of workspace. + /// + public string Name { get; } + + /// + /// Is the workspace content isolated so it is not included as part of + /// global web services. + /// + /// + /// Isolated workspaces content is only visible and queryable in the + /// context of a virtual service bound to the isolated workspace. + /// + /// False by default. + public bool Isolated { get; } + + /// + /// URL to Datas tores in this workspace. + /// + public string DataStores { get; } + + /// + /// URL to Coverage stores in this workspace. + /// + public string CoverageStores { get; } + + /// + /// URL to WMS stores in this workspace. + /// + public string WmsStores { get; } + + public WorkspaceSummary(string name, string dataStores, string coverageStores, string wmsStores, bool isolated = false) + { + Name = name; + Isolated = isolated; + DataStores = dataStores; + CoverageStores = coverageStores; + WmsStores = wmsStores; + } + } +} diff --git a/src/GeoTools.GeoServer.Abstractions/Models/Workspace/WorkspaceWrapper.cs b/src/GeoTools.GeoServer.Abstractions/Models/Workspace/WorkspaceWrapper.cs new file mode 100644 index 0000000..85f6088 --- /dev/null +++ b/src/GeoTools.GeoServer.Abstractions/Models/Workspace/WorkspaceWrapper.cs @@ -0,0 +1,15 @@ +namespace GeoTools.GeoServer.Models +{ + /// + /// Wrapper object around Workspace, in order to conform to how XStream serializes to JSON in GeoServer. + /// + public class WorkspaceWrapper + { + public WorkspaceInfo Workspace { get; } + + public WorkspaceWrapper(WorkspaceInfo workspace) + { + Workspace = workspace; + } + } +} diff --git a/src/GeoTools.GeoServer.Abstractions/Services/IWorkspaceService.cs b/src/GeoTools.GeoServer.Abstractions/Services/IWorkspaceService.cs new file mode 100644 index 0000000..35e54f0 --- /dev/null +++ b/src/GeoTools.GeoServer.Abstractions/Services/IWorkspaceService.cs @@ -0,0 +1,17 @@ +using GeoTools.GeoServer.Models; +using System.Threading; +using System.Threading.Tasks; + +namespace GeoTools.GeoServer.Services +{ + public interface IWorkspaceService + { + /// + /// Retrieve a Workspace. + /// + /// The name of the workspace to fetch. + /// + /// Retrieves a single workspace definition. + Task GetWorkspaceAsync(string name, CancellationToken token); + } +} diff --git a/src/GeoTools.GeoServer/Extensions/GeoServerExtensions.cs b/src/GeoTools.GeoServer/Extensions/GeoServerExtensions.cs new file mode 100644 index 0000000..99f7182 --- /dev/null +++ b/src/GeoTools.GeoServer/Extensions/GeoServerExtensions.cs @@ -0,0 +1,68 @@ +using GeoTools.GeoServer.Helpers.Options; +using GeoTools.GeoServer.Services; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using System; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; + +namespace GeoTools.GeoServer.Extensions +{ + public static class GeoServerExtensions + { + public static IServiceCollection AddGeoServer(this IServiceCollection services) + { + return services.AddGeoServer(options => { }); + } + + public static IServiceCollection AddGeoServer(this IServiceCollection services, Action configureOptions) + { + return AddGeoServer(services, (options, _) => configureOptions(options)); + } + + public static IServiceCollection AddGeoServer(this IServiceCollection services, Action configureOptions) + { + services + .AddSingleton, ValidateGeoServerOptions>() + .AddOptions() + .Configure(configureOptions); + + services + .AddHttpClient(GeoServerOptions.HttpClientName, ConfigureHttpClient); + + return services + .AddTransient(); + } + + private static void ConfigureHttpClient(IServiceProvider provider, HttpClient client) + { + var optionsWrapper = provider.GetService>(); + + if (optionsWrapper != null) + { + var options = optionsWrapper.Value; + + if (options.ConfigureHttpClient != null) + { + options.ConfigureHttpClient(provider, client); + } + else + { + client.BaseAddress = options.BaseAddress; + + if (options.AuthorizationHeaderValue != null) + { + var authTokens = options.AuthorizationHeaderValue.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + var authSchema = authTokens[0]; + var authParameter = string.Join(" ", authTokens.Skip(1)); + + client.DefaultRequestHeaders.Authorization = string.IsNullOrEmpty(authParameter) + ? new AuthenticationHeaderValue(authSchema) + : new AuthenticationHeaderValue(authSchema, authParameter); + } + } + } + } + } +} diff --git a/src/GeoTools.GeoServer/GeoTools.GeoServer.csproj b/src/GeoTools.GeoServer/GeoTools.GeoServer.csproj new file mode 100644 index 0000000..1008a27 --- /dev/null +++ b/src/GeoTools.GeoServer/GeoTools.GeoServer.csproj @@ -0,0 +1,36 @@ + + + + netstandard2.0 + + + + + + + + + + + + + + + + + + + True + True + ExceptionMessages.resx + + + + + + ResXFileCodeGenerator + ExceptionMessages.Designer.cs + + + + diff --git a/src/GeoTools.GeoServer/Helpers/Options/ValidateGeoServerOptions.cs b/src/GeoTools.GeoServer/Helpers/Options/ValidateGeoServerOptions.cs new file mode 100644 index 0000000..6a2d030 --- /dev/null +++ b/src/GeoTools.GeoServer/Helpers/Options/ValidateGeoServerOptions.cs @@ -0,0 +1,37 @@ +using GeoTools.GeoServer.Resources; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; + +namespace GeoTools.GeoServer.Helpers.Options +{ + internal class ValidateGeoServerOptions : IValidateOptions + { + public ValidateOptionsResult Validate(string name, GeoServerOptions options) + { + var failures = new List(); + + if (options is null) + return ValidateOptionsResult.Fail(ExceptionMessages.Options_Null); + + if (options.ConfigureHttpClient != null) + return ValidateOptionsResult.Success; + + if (options.BaseAddress == null) + failures.Add(ExceptionMessages.Options_BaseAddressNull); + else if (options.BaseAddress.IsFile) + failures.Add(ExceptionMessages.Options_BaseAddressFile); + + if (options.AuthorizationHeaderValue != null && + options.AuthorizationHeaderValue.Split( + new char[] { ' ' }, + StringSplitOptions.RemoveEmptyEntries).Length < 1) + failures.Add(ExceptionMessages.Options_AuthorizationHeaderValueWrongFormat); + + if (failures.Count > 0) + return ValidateOptionsResult.Fail(failures); + else + return ValidateOptionsResult.Success; + } + } +} diff --git a/src/GeoTools.GeoServer/Options/GeoServerOptions.cs b/src/GeoTools.GeoServer/Options/GeoServerOptions.cs new file mode 100644 index 0000000..0c19c7a --- /dev/null +++ b/src/GeoTools.GeoServer/Options/GeoServerOptions.cs @@ -0,0 +1,47 @@ +using System; +using System.Net.Http; + +namespace GeoTools.GeoServer +{ + public class GeoServerOptions + { + /// + /// If a GeoServer error, like 400/401/500 occurs, return null instead + /// of throwing an exception. + /// + /// True by default. + public bool IgnoreServerErrors { get; set; } = true; + + /// + /// Avoids logging an exception when the workspace is not present. Note + /// that 404 status code will still be returned. + /// + /// True by default. + public bool QuietIfNotFound { get; set; } = true; + + #region HttpClient + + /// + /// Base uri address of the server, + /// e.g. http://localhost:8080/geoserver/rest/. + /// + public Uri BaseAddress { get; set; } + + /// + /// Full value of the Authorization header, e.g. Basic AABBCC. + /// + public string AuthorizationHeaderValue { get; set; } + + /// + /// If not null, overrides other http-related options. + /// + public Action ConfigureHttpClient { get; set; } + + /// + /// Name of the client to get from the factory. + /// + internal const string HttpClientName = nameof(GeoServer); + + #endregion + } +} diff --git a/src/GeoTools.GeoServer/README.md b/src/GeoTools.GeoServer/README.md new file mode 100644 index 0000000..9a9ee91 --- /dev/null +++ b/src/GeoTools.GeoServer/README.md @@ -0,0 +1,3 @@ +# GeoTools.GeoServer + +This client library uses documentation from [the GeoServer community OpenAPI configuration](https://github.com/geoserver/geoserver/tree/main/src/community/rest-openapi/openapi/src/main/resources/org/geoserver/rest/openapi/1.0.0). \ No newline at end of file diff --git a/src/GeoTools.GeoServer/Resources/ExceptionMessages.Designer.cs b/src/GeoTools.GeoServer/Resources/ExceptionMessages.Designer.cs new file mode 100644 index 0000000..513d194 --- /dev/null +++ b/src/GeoTools.GeoServer/Resources/ExceptionMessages.Designer.cs @@ -0,0 +1,135 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace GeoTools.GeoServer.Resources { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class ExceptionMessages { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal ExceptionMessages() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("GeoTools.GeoServer.Resources.ExceptionMessages", typeof(ExceptionMessages).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Authorization header value should consist of a schema with an optional parameter.. + /// + internal static string Options_AuthorizationHeaderValueWrongFormat { + get { + return ResourceManager.GetString("Options_AuthorizationHeaderValueWrongFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The BaseAddress Uri can't point to a file.. + /// + internal static string Options_BaseAddressFile { + get { + return ResourceManager.GetString("Options_BaseAddressFile", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The BaseAddress Uri can't be null if ConfigureHttpClient isn't specified.. + /// + internal static string Options_BaseAddressNull { + get { + return ResourceManager.GetString("Options_BaseAddressNull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Validated options model is null.. + /// + internal static string Options_Null { + get { + return ResourceManager.GetString("Options_Null", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Request {0} failed with response 404 Not Found.. + /// + internal static string Request_NotFound { + get { + return ResourceManager.GetString("Request_NotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Request {0} succeeded with response 200 OK.. + /// + internal static string Request_OK { + get { + return ResourceManager.GetString("Request_OK", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Request {0} failed with response 401 Unauthorized.. + /// + internal static string Request_Unauthorized { + get { + return ResourceManager.GetString("Request_Unauthorized", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Value {0} is out of supported range {1}.. + /// + internal static string Value_OutOfRange { + get { + return ResourceManager.GetString("Value_OutOfRange", resourceCulture); + } + } + } +} diff --git a/src/GeoTools.GeoServer/Resources/ExceptionMessages.resx b/src/GeoTools.GeoServer/Resources/ExceptionMessages.resx new file mode 100644 index 0000000..00b4005 --- /dev/null +++ b/src/GeoTools.GeoServer/Resources/ExceptionMessages.resx @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Authorization header value should consist of a schema with an optional parameter. + + + The BaseAddress Uri can't point to a file. + + + The BaseAddress Uri can't be null if ConfigureHttpClient isn't specified. + + + Validated options model is null. + + + Request {0} failed with response 404 Not Found. + + + Request {0} succeeded with response 200 OK. + + + Request {0} failed with response 401 Unauthorized. + + + Value {0} is out of supported range {1}. + + \ No newline at end of file diff --git a/src/GeoTools.GeoServer/Resources/ExceptionMessages.uk-UA.resx b/src/GeoTools.GeoServer/Resources/ExceptionMessages.uk-UA.resx new file mode 100644 index 0000000..49ec2c5 --- /dev/null +++ b/src/GeoTools.GeoServer/Resources/ExceptionMessages.uk-UA.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Значення заголовка Authorization має містити схему та опціонально параметр. + + + Uri BaseAddress не може вказувати на файл. + + + Uri BaseAddress не може містити значення null якщо ConfigureHttpClient не присвоєно. + + + Валідована модель опцій має значення null. + + \ No newline at end of file diff --git a/src/GeoTools.GeoServer/Services/WorkspaceService.cs b/src/GeoTools.GeoServer/Services/WorkspaceService.cs new file mode 100644 index 0000000..0f55582 --- /dev/null +++ b/src/GeoTools.GeoServer/Services/WorkspaceService.cs @@ -0,0 +1,89 @@ +using GeoTools.GeoServer.Models; +using GeoTools.GeoServer.Resources; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Net.Http.Json; +using System.Threading; +using System.Threading.Tasks; + +namespace GeoTools.GeoServer.Services +{ + public class WorkspaceService : IWorkspaceService + { + private readonly IHttpClientFactory _httpClientFactory; + private readonly ILogger _logger; + private readonly GeoServerOptions _options; + + public WorkspaceService(IHttpClientFactory httpClientFactory, IServiceProvider provider, IOptions options) + { + _httpClientFactory = httpClientFactory; + _logger = provider.GetService>(); + _options = options.Value; + } + + public async Task GetWorkspaceAsync(string name, CancellationToken token) + { + using (HttpClient client = _httpClientFactory.CreateClient(GeoServerOptions.HttpClientName)) + { + try + { + var request = new HttpRequestMessage(HttpMethod.Get, $"workspaces/{name}?quietOnNotFound={_options.QuietIfNotFound}"); + request.Headers.Accept.Clear(); + request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json")); + + var response = await client.SendAsync(request, token); + + switch (response.StatusCode) + { + case HttpStatusCode.OK: + var result = await response.Content.ReadFromJsonAsync(cancellationToken: token); + _logger.LogInformation(string.Format( + ExceptionMessages.Request_OK, + request.RequestUri)); + return result.Workspace; + case HttpStatusCode.NotFound: + _logger.LogInformation(string.Format( + ExceptionMessages.Request_NotFound, + request.RequestUri)); + return null; + case HttpStatusCode.Unauthorized: + throw new UnauthorizedAccessException(string.Format( + ExceptionMessages.Request_Unauthorized, + request.RequestUri)); + default: + throw new ArgumentOutOfRangeException( + nameof(response.StatusCode), + response.StatusCode, + string.Format( + ExceptionMessages.Value_OutOfRange, + nameof(response.StatusCode), + "{200,401,404}")); + } + } + catch (UnauthorizedAccessException e) + { + _logger.LogError(e, nameof(GetWorkspaceAsync)); + throw; + } + catch (Exception e) + { + if (_options.IgnoreServerErrors) + { + _logger.LogWarning(e, nameof(GetWorkspaceAsync)); + return null; + } + else + { + _logger.LogError(e, nameof(GetWorkspaceAsync)); + throw; + } + } + } + } + } +} diff --git a/tests/GeoTools.GeoServer.Tests/GeoTools.GeoServer.Tests.csproj b/tests/GeoTools.GeoServer.Tests/GeoTools.GeoServer.Tests.csproj new file mode 100644 index 0000000..dce1ab3 --- /dev/null +++ b/tests/GeoTools.GeoServer.Tests/GeoTools.GeoServer.Tests.csproj @@ -0,0 +1,36 @@ + + + + net7.0 + enable + enable + + false + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + diff --git a/tests/GeoTools.GeoServer.Tests/Services/WorkspaceServiceTests.cs b/tests/GeoTools.GeoServer.Tests/Services/WorkspaceServiceTests.cs new file mode 100644 index 0000000..3469f6d --- /dev/null +++ b/tests/GeoTools.GeoServer.Tests/Services/WorkspaceServiceTests.cs @@ -0,0 +1,55 @@ +using GeoTools.GeoServer.Extensions; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using System.Globalization; +using System.Text; + +namespace GeoTools.GeoServer.Tests.Services +{ + public class WorkspaceServiceTests + { + private readonly ITestOutputHelper _output; + private readonly IConfiguration _configuration; + private readonly IServiceProvider _serviceProvider; + + [Fact] + public async Task GivenDefaultInstallation_GetWorkspace() + { + CancellationToken token = default; + + var service = _serviceProvider.GetRequiredService(); + + var response = await service.GetWorkspaceAsync("ne", token); + + Assert.NotNull(response); + Assert.False(string.IsNullOrWhiteSpace(response.Name)); + Assert.False(string.IsNullOrWhiteSpace(response.CoverageStores)); + Assert.False(string.IsNullOrWhiteSpace(response.DataStores)); + Assert.False(string.IsNullOrWhiteSpace(response.WmsStores)); + + Assert.Null(await service.GetWorkspaceAsync("nen", token)); + } + + public WorkspaceServiceTests(ITestOutputHelper output) + { + _output = output; + + _configuration = new ConfigurationBuilder() + .AddInMemoryCollection() + .Build(); + + _serviceProvider = new ServiceCollection() + .AddGeoServer(options => + { + options.BaseAddress = new Uri("http://localhost:8080/geoserver/rest/"); + options.AuthorizationHeaderValue = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes("admin:geoserver")); + }) + .AddLogging((builder) => builder.AddXUnit(_output, options => + { + options.IncludeScopes = true; + })) + .BuildServiceProvider(); + } + } +} \ No newline at end of file diff --git a/tests/GeoTools.GeoServer.Tests/Usings.cs b/tests/GeoTools.GeoServer.Tests/Usings.cs new file mode 100644 index 0000000..50bb510 --- /dev/null +++ b/tests/GeoTools.GeoServer.Tests/Usings.cs @@ -0,0 +1,3 @@ +global using Xunit; +global using Xunit.Abstractions; +global using GeoTools.GeoServer.Services; \ No newline at end of file