diff --git a/HADotNet.Core.Tests/HADotNet.Core.Tests.csproj b/HADotNet.Core.Tests/HADotNet.Core.Tests.csproj index 458e4a2..96099c0 100644 --- a/HADotNet.Core.Tests/HADotNet.Core.Tests.csproj +++ b/HADotNet.Core.Tests/HADotNet.Core.Tests.csproj @@ -5,6 +5,10 @@ false + + TRACE + + diff --git a/HADotNet.Core.Tests/InfoTests.cs b/HADotNet.Core.Tests/InfoTests.cs index 632208b..ba1a613 100644 --- a/HADotNet.Core.Tests/InfoTests.cs +++ b/HADotNet.Core.Tests/InfoTests.cs @@ -1,10 +1,18 @@ using System; using System.Threading.Tasks; using HADotNet.Core.Clients; +using HADotNet.Core.Domain; using NUnit.Framework; namespace HADotNet.Core.Tests { + /// + /// Tests for the Supervisor Info endpoints. + /// + /// + /// NOTE: If you want to test against a non-Supervisor instance, you'll need to set + /// the compilation symbol TEST_ENV_HA_CORE which will test for the correct exception. + /// public class InfoTests { private Uri Instance { get; set; } @@ -22,25 +30,33 @@ public void Setup() [Test] public async Task ShouldRetrieveSupervisorInfo() { - var client = ClientFactory.GetClient(); - + var client = ClientFactory.GetClient(); + +#if TEST_ENV_HA_CORE + Assert.ThrowsAsync(async () => await client.GetSupervisorInfo()); +#else var info = await client.GetSupervisorInfo(); Assert.AreEqual("ok", info.Result); Assert.IsNotNull(info.Data); Assert.IsNotNull(info.Data.Version); +#endif } [Test] public async Task ShouldRetrieveHostInfo() { - var client = ClientFactory.GetClient(); - + var client = ClientFactory.GetClient(); + +#if TEST_ENV_HA_CORE + Assert.ThrowsAsync(async () => await client.GetHostInfo()); +#else var info = await client.GetHostInfo(); Assert.AreEqual("ok", info.Result); Assert.IsNotNull(info.Data); Assert.IsNotNull(info.Data.OperatingSystem); +#endif } [Test] @@ -48,11 +64,15 @@ public async Task ShouldRetrieveCoreInfo() { var client = ClientFactory.GetClient(); +#if TEST_ENV_HA_CORE + Assert.ThrowsAsync(async () => await client.GetCoreInfo()); +#else var info = await client.GetCoreInfo(); Assert.AreEqual("ok", info.Result); Assert.IsNotNull(info.Data); Assert.IsNotNull(info.Data.Version); +#endif } } } diff --git a/HADotNet.Core/BaseClient.cs b/HADotNet.Core/BaseClient.cs index a9182ef..c84be87 100644 --- a/HADotNet.Core/BaseClient.cs +++ b/HADotNet.Core/BaseClient.cs @@ -1,6 +1,7 @@ using System; using System.Net; using System.Threading.Tasks; +using HADotNet.Core.Domain; using Newtonsoft.Json; using RestSharp; @@ -55,7 +56,7 @@ protected async Task Get(string path) where T : class return JsonConvert.DeserializeObject(resp.Content); } - throw new Exception($"Unexpected response code {(int)resp.StatusCode} from Home Assistant API endpoint {path}."); + throw new HttpResponseException((int)resp.StatusCode, resp.ResponseStatus.ToString(), resp.ResponseUri.PathAndQuery, $"Unexpected GET response code {(int)resp.StatusCode} from Home Assistant API endpoint {path}."); } /// @@ -98,7 +99,7 @@ protected async Task Post(string path, object body, bool isRawBody = false return JsonConvert.DeserializeObject(resp.Content); } - throw new Exception($"Unexpected response code {(int)resp.StatusCode} from Home Assistant API endpoint {path}."); + throw new HttpResponseException((int)resp.StatusCode, resp.ResponseStatus.ToString(), resp.ResponseUri.PathAndQuery, $"Unexpected POST response code {(int)resp.StatusCode} from Home Assistant API endpoint {path}."); } /// @@ -128,7 +129,7 @@ protected async Task Delete(string path) where T : class return JsonConvert.DeserializeObject(resp.Content); } - throw new Exception($"Unexpected response code {(int)resp.StatusCode} from Home Assistant API endpoint {path}."); + throw new HttpResponseException((int)resp.StatusCode, resp.ResponseStatus.ToString(), resp.ResponseUri.PathAndQuery, $"Unexpected DELETE response code {(int)resp.StatusCode} from Home Assistant API endpoint {path}."); } /// @@ -147,7 +148,7 @@ protected async Task Delete(string path) if (!(resp.StatusCode == HttpStatusCode.OK || resp.StatusCode == HttpStatusCode.NoContent)) { - throw new Exception($"Unexpected response code {(int)resp.StatusCode} from Home Assistant API endpoint {path}."); + throw new HttpResponseException((int)resp.StatusCode, resp.ResponseStatus.ToString(), resp.ResponseUri.PathAndQuery, $"Unexpected DELETE response code {(int)resp.StatusCode} from Home Assistant API endpoint {path}."); } } } diff --git a/HADotNet.Core/Clients/InfoClient.cs b/HADotNet.Core/Clients/InfoClient.cs index 1812f18..d274676 100644 --- a/HADotNet.Core/Clients/InfoClient.cs +++ b/HADotNet.Core/Clients/InfoClient.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using HADotNet.Core.Domain; using HADotNet.Core.Models; namespace HADotNet.Core.Clients @@ -20,18 +21,48 @@ public InfoClient(Uri instance, string apiKey) : base(instance, apiKey) { } /// Retrieves Supervisor information. /// /// A representing Supervisor informatio. - public async Task> GetSupervisorInfo() => await Get>("/api/hassio/supervisor/info"); + public async Task> GetSupervisorInfo() + { + try + { + return await Get>("/api/hassio/supervisor/info"); + } + catch (HttpResponseException hrex) when (hrex.StatusCode == 404) + { + throw new SupervisorNotFoundException("This does not appear to be a Home Assistant Supervisor instance. See inner exception for more details.", hrex); + } + } /// /// Retrieves Host information. /// /// A representing Host informatio. - public async Task> GetHostInfo() => await Get>("/api/hassio/host/info"); + public async Task> GetHostInfo() + { + try + { + return await Get>("/api/hassio/host/info"); + } + catch (HttpResponseException hrex) when (hrex.StatusCode == 404) + { + throw new SupervisorNotFoundException("This does not appear to be a Home Assistant Supervisor instance. See inner exception for more details.", hrex); + } + } /// /// Retrieves Core information. /// /// A representing Host informatio. - public async Task> GetCoreInfo() => await Get>("/api/hassio/core/info"); + public async Task> GetCoreInfo() + { + try + { + return await Get>("/api/hassio/core/info"); + } + catch (HttpResponseException hrex) when (hrex.StatusCode == 404) + { + throw new SupervisorNotFoundException("This does not appear to be a Home Assistant Supervisor instance. See inner exception for more details.", hrex); + } + } } } diff --git a/HADotNet.Core/Domain/HttpResponseException.cs b/HADotNet.Core/Domain/HttpResponseException.cs new file mode 100644 index 0000000..2811033 --- /dev/null +++ b/HADotNet.Core/Domain/HttpResponseException.cs @@ -0,0 +1,63 @@ +using System; + +namespace HADotNet.Core.Domain +{ + /// + /// Represents a failed HTTP call to a Home Assistant endpoint. + /// + public class HttpResponseException : Exception + { + /// + /// Gets the status code for the HTTP response. + /// + public int StatusCode { get; } + + /// + /// Gets the network description, if the error was at the network level. + /// + public string NetworkDescription { get; } + + /// + /// Gets the original request path. + /// + public string RequestPath { get; } + + /// + /// Gets the error response body. + /// + public string ResponseBody { get; } + + /// + /// Initializes a new HttpResponseException. + /// + public HttpResponseException(int statusCode, string networkDescription, string requestPath, string responseBody) + { + StatusCode = statusCode; + NetworkDescription = networkDescription; + RequestPath = requestPath; + ResponseBody = responseBody; + } + + /// + /// Initializes a new HttpResponseException. + /// + public HttpResponseException(int statusCode, string networkDescription, string requestPath, string responseBody, string message) : base(message) + { + StatusCode = statusCode; + NetworkDescription = networkDescription; + RequestPath = requestPath; + ResponseBody = responseBody; + } + + /// + /// Initializes a new HttpResponseException. + /// + public HttpResponseException(int statusCode, string networkDescription, string requestPath, string responseBody, string message, Exception innerException) : base(message, innerException) + { + StatusCode = statusCode; + NetworkDescription = networkDescription; + RequestPath = requestPath; + ResponseBody = responseBody; + } + } +} diff --git a/HADotNet.Core/Domain/SupervisorNotFoundException.cs b/HADotNet.Core/Domain/SupervisorNotFoundException.cs new file mode 100644 index 0000000..21c6af6 --- /dev/null +++ b/HADotNet.Core/Domain/SupervisorNotFoundException.cs @@ -0,0 +1,31 @@ +using System; + +namespace HADotNet.Core.Domain +{ + /// + /// The exception that occurs when a Supervisor-only API call is made to a non-Supervisor environment. + /// + public class SupervisorNotFoundException : Exception + { + /// + /// Initializes a new instance of the SupervisorNotFoundException. + /// + public SupervisorNotFoundException() + { + } + + /// + /// Initializes a new instance of the SupervisorNotFoundException. + /// + public SupervisorNotFoundException(string message) : base(message) + { + } + + /// + /// Initializes a new instance of the SupervisorNotFoundException. + /// + public SupervisorNotFoundException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/HADotNet.Core/HADotNet.Core.csproj b/HADotNet.Core/HADotNet.Core.csproj index 39d97bd..2609c95 100644 --- a/HADotNet.Core/HADotNet.Core.csproj +++ b/HADotNet.Core/HADotNet.Core.csproj @@ -12,15 +12,15 @@ home-assistant external api library HADotNet Core Library true - 1.4.1 + 1.5.0 Apache-2.0 https://github.com/qJake/HADotNet https://github.com/qJake/HADotNet.git git 7.3 - 1.4.1.0 - 1.4.1.0 + 1.5.0.0 + 1.5.0.0 diff --git a/HADotNet.Core/Models/AddonObject.cs b/HADotNet.Core/Models/AddonObject.cs index ff89ef4..beb56f6 100644 --- a/HADotNet.Core/Models/AddonObject.cs +++ b/HADotNet.Core/Models/AddonObject.cs @@ -14,13 +14,13 @@ public class AddonObject public string Description { get; set; } /// - /// True if icon is available, otherwise False. + /// True if icon is available, otherwise False. /// [JsonProperty("icon")] public bool Icon { get; set; } /// - /// True if logo is available, otherwise False. + /// True if logo is available, otherwise False. /// [JsonProperty("logo")] public bool Logo { get; set; } @@ -50,7 +50,7 @@ public class AddonObject public string State { get; set; } /// - /// True if update is available, otherwise False. + /// True if update is available, otherwise False. /// [JsonProperty("update_available")] public bool UpdateAvailable { get; set; } diff --git a/HADotNet.Core/Models/HostInfoObject.cs b/HADotNet.Core/Models/HostInfoObject.cs index 1f4d125..648ef00 100644 --- a/HADotNet.Core/Models/HostInfoObject.cs +++ b/HADotNet.Core/Models/HostInfoObject.cs @@ -9,43 +9,43 @@ namespace HADotNet.Core.Models public class HostInfoObject { /// - /// Gets or sets the chassis. + /// Gets or sets the chassis type. /// [JsonProperty("chassis")] public string Chassis { get; set; } /// - /// Gets or sets the CPE. + /// Gets or sets the CPE string. /// [JsonProperty("cpe")] public string Cpe { get; set; } /// - /// Gets or sets the deployment. + /// Gets or sets the deployment type (e.g. production). /// [JsonProperty("deployment")] public string Deployment { get; set; } /// - /// Gets or sets the disk free. + /// Gets or sets the disk free, expressed in GB. /// [JsonProperty("disk_free")] public double DiskFree { get; set; } /// - /// Gets or sets the disk total. + /// Gets or sets the disk total, expressed in GB. /// [JsonProperty("disk_total")] public double DiskTotal { get; set; } /// - /// Gets or sets the disk used. + /// Gets or sets the disk used, expressed in GB. /// [JsonProperty("disk_used")] public double DiskUsed { get; set; } /// - /// Gets or sets the features. + /// Gets or sets the feature list. /// [JsonProperty("features")] public IList Features { get; set; } @@ -57,7 +57,7 @@ public class HostInfoObject public string Hostname { get; set; } /// - /// Gets or sets the kernel. + /// Gets or sets the kernel version. /// [JsonProperty("kernel")] public string Kernel { get; set; } diff --git a/README.md b/README.md index 321ab7e..9af1626 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ A simple, straighforward .NET Standard library for the [Home Assistant](https:// * Logbook API * Services API * States API +* Supervisor API (Supervisor-based installations only) * Template API ## Getting Started