diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 0c14f89..4a812de 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -20,7 +20,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.x + dotnet-version: 9.0.x - name: Restore dependencies run: dotnet restore diff --git a/samples/OpenWeatherMapSharp.Console/OpenWeatherMapSharp.Console.csproj b/samples/OpenWeatherMapSharp.Console/OpenWeatherMapSharp.Console.csproj index 194867c..f035b45 100644 --- a/samples/OpenWeatherMapSharp.Console/OpenWeatherMapSharp.Console.csproj +++ b/samples/OpenWeatherMapSharp.Console/OpenWeatherMapSharp.Console.csproj @@ -2,13 +2,13 @@ Exe - net7.0 + net9.0 enable enable - + diff --git a/samples/OpenWeatherMapSharp.Console/Program.cs b/samples/OpenWeatherMapSharp.Console/Program.cs index 4c89098..62001ac 100644 --- a/samples/OpenWeatherMapSharp.Console/Program.cs +++ b/samples/OpenWeatherMapSharp.Console/Program.cs @@ -3,92 +3,88 @@ using OpenWeatherMapSharp.Models.Enums; using Spectre.Console; +// Your OpenWeatherMap API key (replace with your actual one) string openWeatherMapApiKey = "OWMAPIKEY"; -// HEADER +// == HEADER == Grid headerGrid = new(); headerGrid.AddColumn(); headerGrid.AddRow(new FigletText("OpenWeatherMap").Centered().Color(Color.Red)); AnsiConsole.Write(headerGrid); -// ASK FOR CITY NAME +// == ASK FOR CITY == AnsiConsole.WriteLine(); string cityName = AnsiConsole.Ask("[white]Insert the name of the[/] [red]city[/][white]?[/]"); AnsiConsole.WriteLine(); -// GET WEATHER +// == INIT SERVICE == OpenWeatherMapService openWeatherMapService = new(openWeatherMapApiKey); -var geolocationResponse = await openWeatherMapService.GetLocationByNameAsync(cityName); -if (!geolocationResponse.IsSuccess) +// == GEOCODE == +OpenWeatherMapServiceResponse> geolocationResponse + = await openWeatherMapService.GetLocationByNameAsync(cityName); + +if (!geolocationResponse.IsSuccess || geolocationResponse.Response?.FirstOrDefault() is not GeocodeInfo geolocation) { - AnsiConsole.Write("Unfortunately I can't recognize the city. Please try again."); - Console.WriteLine(); + AnsiConsole.MarkupLine("[bold red]Unfortunately I can't recognize the city. Please try again.[/]"); return; } -var geolocations = geolocationResponse.Response; -var geolocation = geolocations.FirstOrDefault(); +// == WEATHER == +OpenWeatherMapServiceResponse weatherResponse + = await openWeatherMapService.GetWeatherAsync( + geolocation.Latitude, + geolocation.Longitude, + unit: Unit.Metric); -if (geolocation is null) +if (!weatherResponse.IsSuccess || weatherResponse.Response is not WeatherRoot weatherRoot) { - AnsiConsole.Write("Unfortunately I can't recognize the city. Please try again."); - Console.WriteLine(); + AnsiConsole.MarkupLine("[bold red]Unfortunately I can't retrieve weather data. Please try again.[/]"); return; } -OpenWeatherMapServiceResponse weatherResponse = await openWeatherMapService.GetWeatherAsync(geolocation.Latitude, geolocation.Longitude, unit: Unit.Metric); +Main mainWeather = weatherRoot.MainWeather; -if (weatherResponse.IsSuccess) +// == LOCATION PANEL == +Panel locationPanel = new(new Rows(new List +{ + new($"[red]City: [/]{weatherRoot.Name}"), + new($"[red]Latitude: [/]{weatherRoot.Coordinates.Latitude:0.0000}"), + new($"[red]Longitude: [/]{weatherRoot.Coordinates.Longitude:0.0000}"), + new($"[red]Country: [/]{geolocation.Country}"), + new($"[red]State: [/]{geolocation.State ?? "[not available]"}") +})) { - WeatherRoot weatherRoot = weatherResponse.Response; - Main mainWeather = weatherRoot.MainWeather; + Header = new PanelHeader("Location"), + Width = 120 +}; +AnsiConsole.Write(locationPanel); - // LOCATION - List locationMarkupList = new() - { - new Markup($"[red]City: [/]{weatherRoot.Name}"), - new Markup($"[red]Latitude: [/]{weatherRoot.Coordinates.Latitude:0.0000}"), - new Markup($"[red]Longitude: [/]{weatherRoot.Coordinates.Longitude:0.0000}"), - new Markup($"[red]Country: [/]{geolocation.Country}"), - new Markup($"[red]State: [/]{geolocation.State}") - }; - Rows locationRows = new(locationMarkupList); - Panel locationPanel = new(locationRows) - { - Header = new PanelHeader("Location"), - Width = 120 - }; - AnsiConsole.Write(locationPanel); +// == WEATHER PANEL == +List weatherMarkupList = +[ + new($"[red]Temperature: [/]{mainWeather.Temperature}° C"), + new($"[red]Feels Like: [/]{mainWeather.FeelsLikeTemperature}° C"), + new($"[red]Min Temperature: [/]{mainWeather.MinTemperature}° C"), + new($"[red]Max Temperature: [/]{mainWeather.MaxTemperature}° C"), + new("-----"), + new($"[red]Pressure (Sea Level): [/]{mainWeather.Pressure} hPa"), + new($"[red]Humidity: [/]{mainWeather.Humidity} %"), + new($"[red]Sunrise: [/]{weatherRoot.System.Sunrise:g}"), + new($"[red]Sunset: [/]{weatherRoot.System.Sunset:g}") +]; - // WEATHER - List weatherMarkupList = new() - { - new Markup($"[red]Temperature: [/]{mainWeather.Temperature}° C"), - new Markup($"[red]Temperature (Feels Like): [/]{mainWeather.FeelsLikeTemperature}° C"), - new Markup($"[red]Minimal Temperature: [/]{mainWeather.MinTemperature}° C"), - new Markup($"[red]Maximal Temperature: [/]{mainWeather.MaxTemperature}° C"), - new Markup("-----"), - new Markup($"[red]Pressure Sea Level hPa: [/]{mainWeather.Pressure} hPa"), - new Markup($"[red]Humidity: [/]{mainWeather.Humidity} %"), - new Markup($"[red]Sunrise: [/]{weatherRoot.System.Sunrise:g}"), - new Markup($"[red]Sunset: [/]{weatherRoot.System.Sunset:g}") - }; - foreach (WeatherInfo weatherInfo in weatherRoot.WeatherInfos) - { - weatherMarkupList.Add(new Markup($"[red]Current Conditions: [/]{weatherInfo.Description}")); - } - Rows currentWeatherRows = new(weatherMarkupList); - Panel currentWeatherPanel = new(currentWeatherRows) - { - Header = new PanelHeader("Current Weather"), - Width = 120 - }; - AnsiConsole.Write(currentWeatherPanel); -} -else +// Add weather descriptions +foreach (WeatherInfo? weatherInfo in weatherRoot.WeatherInfos) { - AnsiConsole.Write("Unfortunately I can't recognize the city. Please try again."); + weatherMarkupList.Add(new($"[red]Conditions: [/]{weatherInfo.Description}")); } -Console.ReadLine(); \ No newline at end of file +Panel weatherPanel = new(new Rows(weatherMarkupList)) +{ + Header = new PanelHeader("Current Weather"), + Width = 120 +}; +AnsiConsole.Write(weatherPanel); + +Console.ReadLine(); diff --git a/src/OpenWeatherMapSharp/IOpenWeatherMapService.cs b/src/OpenWeatherMapSharp/IOpenWeatherMapService.cs index efd159e..d0acf45 100644 --- a/src/OpenWeatherMapSharp/IOpenWeatherMapService.cs +++ b/src/OpenWeatherMapSharp/IOpenWeatherMapService.cs @@ -7,18 +7,19 @@ namespace OpenWeatherMapSharp { /// - /// This interface defines the methods to communicate with the Open Weather Map API + /// Defines the contract for communicating with the OpenWeatherMap API. /// public interface IOpenWeatherMapService { /// - /// Gets forecast for a location given its longitude and latitude + /// Gets forecast data for a location based + /// on its geographic coordinates. /// - /// The latitude of the location - /// The longitude of the location - /// The language used for the response - /// The unit of measurement for the response - /// The OpenWeatherMapServiceResponse containing the forecast information + /// Latitude of the location. + /// Longitude of the location. + /// Language of the response (default: English). + /// Unit of measurement (default: Standard). + /// A forecast response wrapped in . Task> GetForecastAsync( double latitude, double longitude, @@ -26,39 +27,39 @@ Task> GetForecastAsync( Unit unit = Unit.Standard); /// - /// Gets forecast for a location given its city ID + /// Gets forecast data based on a city ID. /// - /// The ID of the location's city - /// The language used for the response - /// The unit of measurement for the response - /// The OpenWeatherMapServiceResponse containing the forecast information - [Obsolete("Please note that API requests by city name, zip-codes and city id have been deprecated. Although they are still available for use, bug fixing and updates are no longer available for this functionality.")] + /// The city ID. + /// Language of the response. + /// Unit of measurement. + /// A forecast response wrapped in . + [Obsolete("API requests by city name, zip-code, and city ID are deprecated and no longer maintained.")] Task> GetForecastAsync( int cityId, LanguageCode language = LanguageCode.EN, Unit unit = Unit.Standard); /// - /// Gets forecast for a location given its city name + /// Gets forecast data based on a city name. /// - /// The name of the location's city - /// The language used for the response - /// The unit of measurement for the response - /// The OpenWeatherMapServiceResponse containing the forecast information - [Obsolete("Please note that API requests by city name, zip-codes and city id have been deprecated. Although they are still available for use, bug fixing and updates are no longer available for this functionality.")] + /// The city name. + /// Language of the response. + /// Unit of measurement. + /// A forecast response wrapped in . + [Obsolete("API requests by city name, zip-code, and city ID are deprecated and no longer maintained.")] Task> GetForecastAsync( string city, LanguageCode language = LanguageCode.EN, Unit unit = Unit.Standard); /// - /// Gets weather information for a location given its longitude and latitude + /// Gets current weather data for a location based on its geographic coordinates. /// - /// The latitude of the location - /// The longitude of the location - /// The language used for the response - /// The unit of measurement for the response - /// The OpenWeatherMapServiceResponse containing the weather information + /// Latitude of the location. + /// Longitude of the location. + /// Language of the response (default: English). + /// Unit of measurement (default: Standard). + /// A weather response wrapped in . Task> GetWeatherAsync( double latitude, double longitude, @@ -66,60 +67,59 @@ Task> GetWeatherAsync( Unit unit = Unit.Standard); /// - /// Gets weather information for a location given its city ID + /// Gets current weather data based on a city ID. /// - /// The ID of the location's city - /// The language used for the response - /// The unit of measurement for the response - /// The OpenWeatherMapServiceResponse containing the weather information - [Obsolete("Please note that API requests by city name, zip-codes and city id have been deprecated. Although they are still available for use, bug fixing and updates are no longer available for this functionality.")] + /// The city ID. + /// Language of the response. + /// Unit of measurement. + /// A weather response wrapped in . + [Obsolete("API requests by city name, zip-code, and city ID are deprecated and no longer maintained.")] Task> GetWeatherAsync( int cityId, LanguageCode language = LanguageCode.EN, Unit unit = Unit.Standard); /// - /// Gets weather information for a location given its city name + /// Gets current weather data based on a city name. /// - /// The name of the location's city - /// The language used for the response - /// The unit of measurement for the response - /// The OpenWeatherMapServiceResponse containing the weather information - [Obsolete("Please note that API requests by city name, zip-codes and city id have been deprecated. Although they are still available for use, bug fixing and updates are no longer available for this functionality.")] + /// The city name. + /// Language of the response. + /// Unit of measurement. + /// A weather response wrapped in . + [Obsolete("API requests by city name, zip-code, and city ID are deprecated and no longer maintained.")] Task> GetWeatherAsync( string city, LanguageCode language = LanguageCode.EN, Unit unit = Unit.Standard); - /// - /// Gets a list of locations given a query string + /// Gets a list of matching locations for a given query string. /// - /// The query string to search for - /// The maximum number of locations to return - /// The OpenWeatherMapServiceResponse containing the list of relevant locations + /// The location name or part of it to search for. + /// The maximum number of results to return (default: 5). + /// A response with a list of matching objects. Task>> GetLocationByNameAsync( string query, int limit = 5); /// - /// Gets location information for a given zip code + /// Gets geolocation information based on a ZIP or postal code. /// - /// The zip code to search for - /// The OpenWeatherMapServiceResponse containing the location information + /// The ZIP/postal code to search for. + /// A response containing a object. Task> GetLocationByZipAsync( string zipCode); /// - /// Gets location information for a given latitude and longitude + /// Gets a list of matching locations for a given pair of coordinates. /// - /// The latitude of the location - /// The longitude of the location - /// The maximum number of locations to return - /// The OpenWeatherMapServiceResponse containing the location information + /// Latitude of the location. + /// Longitude of the location. + /// The maximum number of results to return (default: 5). + /// A response with a list of objects. Task>> GetLocationByLatLonAsync( double latitude, double longitude, int limit = 5); } -} \ No newline at end of file +} diff --git a/src/OpenWeatherMapSharp/Models/City.cs b/src/OpenWeatherMapSharp/Models/City.cs index 2d82590..2a38e0a 100644 --- a/src/OpenWeatherMapSharp/Models/City.cs +++ b/src/OpenWeatherMapSharp/Models/City.cs @@ -1,66 +1,74 @@ -using Newtonsoft.Json; -using OpenWeatherMapSharp.Utils; +using OpenWeatherMapSharp.Utils; using System; +using System.Text.Json.Serialization; namespace OpenWeatherMapSharp.Models { /// - /// City information + /// Represents basic city information, + /// including coordinates, country, + /// population, and sunrise/sunset times. /// public class City { /// - /// City ID + /// The unique city ID as defined + /// by OpenWeatherMap. /// - [JsonProperty("id")] + [JsonPropertyName("id")] public int Id { get; set; } /// - /// City name + /// The name of the city. /// - [JsonProperty("name")] + [JsonPropertyName("name")] public string Name { get; set; } /// - /// Coordinates + /// Geographic coordinates + /// (latitude and longitude) of the city. /// - [JsonProperty("coord")] + [JsonPropertyName("coord")] public Coordinates Coordinates { get; set; } /// - /// Country code (GB, JP etc.) + /// ISO 3166 country code (e.g., "GB", "JP"). /// - [JsonProperty("country")] + [JsonPropertyName("country")] public string Country { get; set; } /// - /// City population + /// Total population of the city. /// - [JsonProperty("population")] + [JsonPropertyName("population")] public int Population { get; set; } /// - /// Sunrise time, unix, UTC + /// Sunrise time in Unix timestamp format (UTC). /// - [JsonProperty("sunrise")] + [JsonPropertyName("sunrise")] public long SunriseUnix { get; set; } /// - /// Sunrise time, DateTime + /// Sunrise time converted to a + /// UTC . /// [JsonIgnore] - public DateTime Sunrise => SunriseUnix.ToDateTime(); + public DateTime Sunrise + => SunriseUnix.ToDateTime(); /// - /// Sunset time, unix, UTC + /// Sunset time in Unix timestamp format (UTC). /// - [JsonProperty("sunset")] + [JsonPropertyName("sunset")] public long SunsetUnix { get; set; } /// - /// Sunset time, DateTime + /// Sunset time converted to a + /// UTC . /// [JsonIgnore] - public DateTime Sunset => SunsetUnix.ToDateTime(); + public DateTime Sunset + => SunsetUnix.ToDateTime(); } } diff --git a/src/OpenWeatherMapSharp/Models/Clouds.cs b/src/OpenWeatherMapSharp/Models/Clouds.cs index 4333f77..b5bddf0 100644 --- a/src/OpenWeatherMapSharp/Models/Clouds.cs +++ b/src/OpenWeatherMapSharp/Models/Clouds.cs @@ -1,16 +1,16 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace OpenWeatherMapSharp.Models { /// - /// Clouds information + /// Represents cloud coverage information. /// public class Clouds { /// - /// Cloudiness, % + /// Cloudiness percentage (0–100). /// - [JsonProperty("all")] + [JsonPropertyName("all")] public int Cloudiness { get; set; } } } diff --git a/src/OpenWeatherMapSharp/Models/Coordinates.cs b/src/OpenWeatherMapSharp/Models/Coordinates.cs index 2b3d30f..ec13883 100644 --- a/src/OpenWeatherMapSharp/Models/Coordinates.cs +++ b/src/OpenWeatherMapSharp/Models/Coordinates.cs @@ -1,22 +1,25 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace OpenWeatherMapSharp.Models { /// - /// Coordinates information + /// Represents the geographic coordinates + /// of a location. /// public class Coordinates { /// - /// Longitude of the location + /// Longitude of the location, + /// in decimal degrees. /// - [JsonProperty("lon")] + [JsonPropertyName("lon")] public double Longitude { get; set; } /// - /// Latitude of the location + /// Latitude of the location, + /// in decimal degrees. /// - [JsonProperty("lat")] + [JsonPropertyName("lat")] public double Latitude { get; set; } } } diff --git a/src/OpenWeatherMapSharp/Models/Enums/LanguageCode.cs b/src/OpenWeatherMapSharp/Models/Enums/LanguageCode.cs index ba4b932..fab2f9d 100644 --- a/src/OpenWeatherMapSharp/Models/Enums/LanguageCode.cs +++ b/src/OpenWeatherMapSharp/Models/Enums/LanguageCode.cs @@ -1,57 +1,162 @@ namespace OpenWeatherMapSharp.Models.Enums { /// - /// Supported languages + /// Represents supported language codes for API localization. + /// These codes align with the OpenWeatherMap API specification. /// public enum LanguageCode { + /// English EN, + + /// Afrikaans AF, - AL, + + /// Arabic AR, + + /// Azerbaijani AZ, + + /// Belarusian + BE, + + /// Bulgarian BG, + + /// Catalan CA, + + /// Czech (note: 'CZ' used instead of ISO 'CS') CZ, + + /// Danish DA, + + /// German DE, + + /// Greek EL, + + /// Basque EU, + + /// Persian (Farsi) FA, + + /// Finnish FI, + + /// French FR, + + /// Galician GL, + + /// Hebrew HE, + + /// Hindi HI, + + /// Croatian HR, + + /// Hungarian HU, + + /// Indonesian ID, + + /// Icelandic + IS, + + /// Italian IT, + + /// Japanese JA, + + /// Korean KR, + + /// Kurdish + KU, + + /// Latin LA, + + /// Lithuanian LT, + + /// Macedonian MK, - NO, + + /// Dutch NL, + + /// Norwegian + NO, + + /// Polish PL, + + /// Portuguese PT, + + /// Portuguese (Brazil) PT_BR, + + /// Romanian RO, + + /// Russian RU, - SV, + + /// Northern Sami SE, + + /// Slovak + SK, + + /// Slovenian SL, + + /// Spanish (note: 'SP' used instead of ISO 'ES') SP, - ES, + + /// Albanian + SQ, + + /// Serbian SR, + + /// Swedish + SV, + + /// Thai TH, + + /// Turkish TR, + + /// Ukrainian (note: 'UA' used instead of ISO 'UK') UA, + + /// Ukrainian UK, + + /// Vietnamese VI, + + /// Chinese (Simplified) ZH_CN, + + /// Chinese (Traditional) ZH_TW, + + /// Zulu ZU } } diff --git a/src/OpenWeatherMapSharp/Models/Enums/Unit.cs b/src/OpenWeatherMapSharp/Models/Enums/Unit.cs index d643020..e0ae237 100644 --- a/src/OpenWeatherMapSharp/Models/Enums/Unit.cs +++ b/src/OpenWeatherMapSharp/Models/Enums/Unit.cs @@ -1,12 +1,31 @@ namespace OpenWeatherMapSharp.Models.Enums { /// - /// Supported units + /// Represents the unit system to be used + /// for temperature, wind speed, and other + /// measurements. These options correspond + /// to the units supported by the + /// OpenWeatherMap API. /// public enum Unit { + /// + /// Standard units: temperature in Kelvin, + /// wind speed in meter/sec. + /// This is the default if no unit is specified. + /// Standard, + + /// + /// Imperial units: temperature in Fahrenheit, + /// wind speed in miles/hour. + /// Imperial, + + /// + /// Metric units: temperature in Celsius, + /// wind speed in meter/sec. + /// Metric } } diff --git a/src/OpenWeatherMapSharp/Models/ForecastItem.cs b/src/OpenWeatherMapSharp/Models/ForecastItem.cs index 332a49e..f498a57 100644 --- a/src/OpenWeatherMapSharp/Models/ForecastItem.cs +++ b/src/OpenWeatherMapSharp/Models/ForecastItem.cs @@ -1,79 +1,86 @@ -using Newtonsoft.Json; -using OpenWeatherMapSharp.Utils; +using OpenWeatherMapSharp.Utils; using System; using System.Collections.Generic; +using System.Text.Json.Serialization; namespace OpenWeatherMapSharp.Models { /// - /// A Forcast Item + /// Represents a single forecast entry + /// containing weather data for a + /// specific point in time. /// public class ForecastItem { /// - /// Main weather information + /// Main weather parameters such as + /// temperature, pressure, and humidity. /// - [JsonProperty("main")] + [JsonPropertyName("main")] public Main MainWeather { get; set; } /// - /// List of weather infos + /// A list of weather conditions + /// (e.g., rain, clouds, clear sky). /// - [JsonProperty("weather")] + [JsonPropertyName("weather")] public List WeatherInfos { get; set; } /// - /// Cloud information + /// Cloud coverage information. /// - [JsonProperty("clouds")] + [JsonPropertyName("clouds")] public Clouds Clouds { get; set; } /// - /// Wind information + /// Wind speed and direction information. /// - [JsonProperty("wind")] + [JsonPropertyName("wind")] public Wind Wind { get; set; } /// - /// Average visibility, metres + /// Average visibility in meters. /// - [JsonProperty("visibility")] + [JsonPropertyName("visibility")] public double Visibility { get; set; } /// - /// Probability of precipitation + /// Probability of precipitation (0.0–1.0). /// - [JsonProperty("pop")] + [JsonPropertyName("pop")] public double Probability { get; set; } /// - /// Rain information + /// Rain volume information. /// - [JsonProperty("rain")] + [JsonPropertyName("rain")] public Volume Rain { get; set; } /// - /// Snow information + /// Snow volume information. /// - [JsonProperty("snow")] + [JsonPropertyName("snow")] public Volume Snow { get; set; } /// - /// City information + /// City details associated with the forecast + /// (if applicable). /// - [JsonProperty("city")] + [JsonPropertyName("city")] public City City { get; set; } /// - /// Date, Unix, UTC + /// Forecast time as Unix timestamp (UTC). /// - [JsonProperty("dt")] + [JsonPropertyName("dt")] public long DateUnix { get; set; } /// - /// Date, DateTime + /// Forecast time as a + /// UTC . /// [JsonIgnore] - public DateTime Date => DateUnix.ToDateTime(); + public DateTime Date + => DateUnix.ToDateTime(); } } diff --git a/src/OpenWeatherMapSharp/Models/ForecastRoot.cs b/src/OpenWeatherMapSharp/Models/ForecastRoot.cs index 0e2705a..944a08f 100644 --- a/src/OpenWeatherMapSharp/Models/ForecastRoot.cs +++ b/src/OpenWeatherMapSharp/Models/ForecastRoot.cs @@ -1,41 +1,47 @@ -using Newtonsoft.Json; -using System.Collections.Generic; +using System.Collections.Generic; +using System.Text.Json.Serialization; namespace OpenWeatherMapSharp.Models { /// - /// The Forcast Root object + /// Represents the root object of a + /// weather forecast API response. /// public class ForecastRoot { /// - /// Internal parameter + /// Response status code + /// (e.g., "200" for success). /// - [JsonProperty("cod")] + [JsonPropertyName("cod")] public string Code { get; set; } /// - /// Internal parameter + /// Internal message or status value + /// (usage may vary by API version). /// - [JsonProperty("message")] + [JsonPropertyName("message")] public double Message { get; set; } /// - /// A number of timestamps returned in the API response + /// Number of forecast entries returned + /// in the response. /// - [JsonProperty("cnt")] + [JsonPropertyName("cnt")] public int Count { get; set; } /// - /// List of forecast items + /// List of forecast items for + /// different timestamps. /// - [JsonProperty("list")] + [JsonPropertyName("list")] public List Items { get; set; } /// - /// City information + /// Information about the city to which + /// the forecast applies. /// - [JsonProperty("city")] + [JsonPropertyName("city")] public City City { get; set; } } } diff --git a/src/OpenWeatherMapSharp/Models/GeocodeInfo.cs b/src/OpenWeatherMapSharp/Models/GeocodeInfo.cs index 9645d82..6e83a46 100644 --- a/src/OpenWeatherMapSharp/Models/GeocodeInfo.cs +++ b/src/OpenWeatherMapSharp/Models/GeocodeInfo.cs @@ -1,44 +1,55 @@ -using Newtonsoft.Json; -using System.Collections.Generic; +using System.Collections.Generic; +using System.Text.Json.Serialization; namespace OpenWeatherMapSharp.Models { + /// + /// Represents a geocoded location with name, + /// coordinates, and optional metadata such + /// as country and state. + /// public class GeocodeInfo { /// - /// Name of the found location + /// Name of the found location. /// - [JsonProperty("name")] + [JsonPropertyName("name")] public string Name { get; set; } /// - /// Name of the found location in dufferent languages + /// Localized names of the location in + /// different languages, keyed by + /// language code (e.g., "en", "de"). /// - [JsonProperty("local_names")] + [JsonPropertyName("local_names")] public Dictionary LocalNames { get; set; } /// - /// Geographical coordinates of the found location: Latitude + /// Latitude of the location + /// in decimal degrees. /// - [JsonProperty("lat")] + [JsonPropertyName("lat")] public double Latitude { get; set; } /// - /// Geographical coordinates of the found location: Longitude + /// Longitude of the location + /// in decimal degrees. /// - [JsonProperty("lon")] + [JsonPropertyName("lon")] public double Longitude { get; set; } /// - /// Country of the found location + /// Country code of the location + /// (ISO 3166 format, e.g., "US", "DE"). /// - [JsonProperty("country")] + [JsonPropertyName("country")] public string Country { get; set; } /// - /// State of the found location, where available + /// Optional state or administrative + /// region of the location, if available. /// - [JsonProperty("state")] + [JsonPropertyName("state")] public string State { get; set; } } } diff --git a/src/OpenWeatherMapSharp/Models/GeocodeZipInfo.cs b/src/OpenWeatherMapSharp/Models/GeocodeZipInfo.cs index 180675e..36e9d47 100644 --- a/src/OpenWeatherMapSharp/Models/GeocodeZipInfo.cs +++ b/src/OpenWeatherMapSharp/Models/GeocodeZipInfo.cs @@ -1,37 +1,46 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace OpenWeatherMapSharp.Models { + /// + /// Represents geolocation data + /// returned from a ZIP/postal code + /// geocoding request. + /// public class GeocodeZipInfo { /// - /// Specified zip/post code in the API request + /// The ZIP or postal code specified + /// in the API request. /// - [JsonProperty("zip")] + [JsonPropertyName("zip")] public string ZipCode { get; set; } /// - /// Name of the found area + /// The name of the found area or locality. /// - [JsonProperty("name")] + [JsonPropertyName("name")] public string Name { get; set; } /// - /// Geographical coordinates of the found location: Latitude + /// Latitude of the found location in + /// decimal degrees. /// - [JsonProperty("lat")] - public double Latiude { get; set; } + [JsonPropertyName("lat")] + public double Latitude { get; set; } /// - /// Geographical coordinates of the found location: Longitude + /// Longitude of the found location + /// in decimal degrees. /// - [JsonProperty("lon")] + [JsonPropertyName("lon")] public double Longitude { get; set; } /// - /// Country of the found zip/post code + /// Country code of the location + /// (ISO 3166 format, e.g., "US", "DE"). /// - [JsonProperty("country")] + [JsonPropertyName("country")] public string Country { get; set; } } } diff --git a/src/OpenWeatherMapSharp/Models/Main.cs b/src/OpenWeatherMapSharp/Models/Main.cs index 8b66653..db85c4d 100644 --- a/src/OpenWeatherMapSharp/Models/Main.cs +++ b/src/OpenWeatherMapSharp/Models/Main.cs @@ -1,64 +1,76 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace OpenWeatherMapSharp.Models { /// - /// Main information + /// Represents the main weather data, + /// including temperature, pressure, and humidity. /// public class Main { /// - /// Temperature. - /// Unit Default: Kelvin, Metric: Celcius, Imperial: Fahrenheit + /// Current temperature. + /// Units — Default: Kelvin, + /// Metric: Celsius, + /// Imperial: Fahrenheit. /// - [JsonProperty("temp")] + [JsonPropertyName("temp")] public double Temperature { get; set; } /// - /// Temperature. This temperature parameter accounts for the human perception of weather. - /// Unit Default: Kelvin, Metric: Celcius, Imperial: Fahrenheit + /// Perceived temperature accounting + /// for human perception. + /// Units — Default: Kelvin, + /// Metric: Celsius, + /// Imperial: Fahrenheit. /// - [JsonProperty("feels_like")] + [JsonPropertyName("feels_like")] public double FeelsLikeTemperature { get; set; } /// - /// Atmospheric pressure of the sea level, hPa + /// Atmospheric pressure at sea level, in hPa. /// - [JsonProperty("pressure")] + [JsonPropertyName("pressure")] public double Pressure { get; set; } /// - /// Humidity, % + /// Humidity percentage (0–100). /// - [JsonProperty("humidity")] + [JsonPropertyName("humidity")] public double Humidity { get; set; } /// - /// Minimum temperature at the moment. - /// This is minimal currently observed temperature (within large megalopolises and urban areas). - /// Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit + /// Minimum observed temperature + /// at the moment (typically within urban areas). + /// Units — Default: Kelvin, + /// Metric: Celsius, + /// Imperial: Fahrenheit. /// - [JsonProperty("temp_min")] + [JsonPropertyName("temp_min")] public double MinTemperature { get; set; } /// - /// Maximum temperature at the moment. - /// This is maximal currently observed temperature (within large megalopolises and urban areas). - /// Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit + /// Maximum observed temperature + /// at the moment (typically within urban areas). + /// Units — Default: Kelvin, + /// Metric: Celsius, + /// Imperial: Fahrenheit. /// - [JsonProperty("temp_max")] + [JsonPropertyName("temp_max")] public double MaxTemperature { get; set; } /// - /// Atmospheric pressure of the sea level, hPa + /// Atmospheric pressure at sea level, + /// in hPa. /// - [JsonProperty("sea_level")] + [JsonPropertyName("sea_level")] public double SeaLevelPressure { get; set; } /// - /// Atmospheric pressure of the ground level, hPa + /// Atmospheric pressure at ground level, + /// in hPa. /// - [JsonProperty("grnd_level")] + [JsonPropertyName("grnd_level")] public double GroundLevelPressure { get; set; } } } diff --git a/src/OpenWeatherMapSharp/Models/OpenWeatherMapServiceResponse.cs b/src/OpenWeatherMapSharp/Models/OpenWeatherMapServiceResponse.cs index 598c62d..c364048 100644 --- a/src/OpenWeatherMapSharp/Models/OpenWeatherMapServiceResponse.cs +++ b/src/OpenWeatherMapSharp/Models/OpenWeatherMapServiceResponse.cs @@ -1,23 +1,32 @@ namespace OpenWeatherMapSharp.Models { /// - /// Wrapper for the OpenWeatherMapService response + /// Generic wrapper for responses returned + /// by the OpenWeatherMapService. + /// Encapsulates success status, + /// response data, and potential error messages. /// - /// - public class OpenWeatherMapServiceResponse where T : class + /// + /// The type of the response object. + /// + public class OpenWeatherMapServiceResponse + where T : class { /// - /// Indicates if the request was successful + /// Indicates whether the request + /// was successful. /// public bool IsSuccess { get; set; } /// - /// Contains the provided response object + /// The response object returned by + /// the service if the request was successful. /// public T Response { get; set; } /// - /// Error information + /// Error message or details + /// if the request failed. /// public string Error { get; set; } } diff --git a/src/OpenWeatherMapSharp/Models/System.cs b/src/OpenWeatherMapSharp/Models/System.cs index 4d445eb..c7c539a 100644 --- a/src/OpenWeatherMapSharp/Models/System.cs +++ b/src/OpenWeatherMapSharp/Models/System.cs @@ -1,54 +1,60 @@ -using Newtonsoft.Json; -using OpenWeatherMapSharp.Utils; +using OpenWeatherMapSharp.Utils; using System; +using System.Text.Json.Serialization; namespace OpenWeatherMapSharp.Models { /// - /// System information + /// Contains system-related metadata + /// from the OpenWeatherMap API response. /// public class System { /// - /// Internal parameter + /// Internal parameter used by the API. /// - [JsonProperty("type")] + [JsonPropertyName("type")] public int Type { get; set; } /// - /// Internal parameter + /// Internal system identifier. /// - [JsonProperty("id")] + [JsonPropertyName("id")] public int Id { get; set; } /// - /// Country code (GB, JP etc.) + /// Country code (ISO 3166 format), + /// e.g., "GB", "JP". /// - [JsonProperty("country")] + [JsonPropertyName("country")] public string Country { get; set; } /// - /// Sunrise time, unix, UTC + /// Sunrise time as a Unix timestamp (UTC). /// - [JsonProperty("sunrise")] + [JsonPropertyName("sunrise")] public long SunriseUnix { get; set; } /// - /// Sunrise time, DateTime + /// Sunrise time converted to a + /// UTC . /// [JsonIgnore] - public DateTime Sunrise => SunriseUnix.ToDateTime(); + public DateTime Sunrise + => SunriseUnix.ToDateTime(); /// - /// Sunset time, unix, UTC + /// Sunset time as a Unix timestamp (UTC). /// - [JsonProperty("sunset")] + [JsonPropertyName("sunset")] public long SunsetUnix { get; set; } /// - /// Sunset time, DateTime + /// Sunset time converted to a + /// UTC . /// [JsonIgnore] - public DateTime Sunset => SunsetUnix.ToDateTime(); + public DateTime Sunset + => SunsetUnix.ToDateTime(); } } diff --git a/src/OpenWeatherMapSharp/Models/Volume.cs b/src/OpenWeatherMapSharp/Models/Volume.cs index 4fa5291..9832972 100644 --- a/src/OpenWeatherMapSharp/Models/Volume.cs +++ b/src/OpenWeatherMapSharp/Models/Volume.cs @@ -1,22 +1,18 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace OpenWeatherMapSharp.Models { /// - /// Volume information + /// Represents precipitation volume over + /// a specific time period. /// public class Volume { /// - /// Volume for the last 1 hour, mm. + /// Precipitation volume for the last + /// 1 hour, in millimeters. /// - [JsonProperty("1h")] + [JsonPropertyName("1h")] public double OneHour { get; set; } - - /// - /// Volume for the last 3 hour, mm. - /// - [JsonProperty("3h")] - public double ThreeHours { get; set; } } } diff --git a/src/OpenWeatherMapSharp/Models/WeatherInfo.cs b/src/OpenWeatherMapSharp/Models/WeatherInfo.cs index 41d6945..a45ad40 100644 --- a/src/OpenWeatherMapSharp/Models/WeatherInfo.cs +++ b/src/OpenWeatherMapSharp/Models/WeatherInfo.cs @@ -1,34 +1,38 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace OpenWeatherMapSharp.Models { /// - /// Weather information + /// Represents detailed weather condition information. /// public class WeatherInfo { /// - /// Weather condition id + /// Weather condition ID as + /// defined by OpenWeatherMap. /// - [JsonProperty("id")] + [JsonPropertyName("id")] public int Id { get; set; } /// - /// Group of weather parameters (Rain, Snow, Clouds etc.) + /// Broad category of weather + /// conditions (e.g., Rain, Snow, Clouds). /// - [JsonProperty("main")] + [JsonPropertyName("main")] public string Main { get; set; } /// - /// Weather coniditon with the group + /// More detailed description of + /// the weather condition. /// - [JsonProperty("description")] + [JsonPropertyName("description")] public string Description { get; set; } /// - /// Weather icon id + /// Weather icon ID that can be used + /// to retrieve the corresponding icon. /// - [JsonProperty("icon")] + [JsonPropertyName("icon")] public string Icon { get; set; } } } diff --git a/src/OpenWeatherMapSharp/Models/WeatherRoot.cs b/src/OpenWeatherMapSharp/Models/WeatherRoot.cs index d95b299..516489a 100644 --- a/src/OpenWeatherMapSharp/Models/WeatherRoot.cs +++ b/src/OpenWeatherMapSharp/Models/WeatherRoot.cs @@ -1,112 +1,142 @@ -using Newtonsoft.Json; -using OpenWeatherMapSharp.Utils; +using OpenWeatherMapSharp.Utils; using System; using System.Collections.Generic; +using System.Text.Json.Serialization; namespace OpenWeatherMapSharp.Models { + /// + /// Represents the root object of the + /// current weather data response from + /// the OpenWeatherMap API. + /// public class WeatherRoot { /// - /// Coordinates information + /// Geographic coordinates of the location. /// - [JsonProperty("coord")] + [JsonPropertyName("coord")] public Coordinates Coordinates { get; set; } /// - /// List of weather infos + /// List of weather conditions. /// - [JsonProperty("weather")] + [JsonPropertyName("weather")] public List WeatherInfos { get; set; } /// - /// Internal parameter + /// Internal parameter (usually "stations"). /// - [JsonProperty("base")] + [JsonPropertyName("base")] public string Base { get; set; } = string.Empty; /// - /// Main weather information + /// Main weather parameters such as + /// temperature, humidity, and pressure. /// - [JsonProperty("main")] + [JsonPropertyName("main")] public Main MainWeather { get; set; } /// - /// Average visibility, metres + /// Visibility in meters. /// - [JsonProperty("visibility")] + [JsonPropertyName("visibility")] public double Visibility { get; set; } = 0; /// - /// Wind information + /// Wind data including speed and direction. /// - [JsonProperty("wind")] + [JsonPropertyName("wind")] public Wind Wind { get; set; } /// - /// Clouds information + /// Cloud coverage data. /// - [JsonProperty("clouds")] + [JsonPropertyName("clouds")] public Clouds Clouds { get; set; } /// - /// Rain information + /// Rain volume data. /// - [JsonProperty("rain")] + [JsonPropertyName("rain")] public Volume Rain { get; set; } /// - /// Snow information + /// Snow volume data. /// - [JsonProperty("snow")] + [JsonPropertyName("snow")] public Volume Snow { get; set; } /// - /// Date, Unix, UTC + /// Timestamp of the weather data (Unix, UTC). /// - [JsonProperty("dt")] + [JsonPropertyName("dt")] public long DateUnix { get; set; } /// - /// Date, DateTime + /// Timestamp of the weather data as a + /// UTC . /// [JsonIgnore] public DateTime Date => DateUnix.ToDateTime(); /// - /// System information + /// System data such as sunrise, sunset, + /// and country code. /// - [JsonProperty("sys")] + [JsonPropertyName("sys")] public System System { get; set; } /// - /// City ID + /// Shift in seconds from UTC. /// - [JsonProperty("id")] + [JsonPropertyName("timezone")] + public int Timezone { get; set; } + + /// + /// City ID. + /// + [JsonPropertyName("id")] public int CityId { get; set; } /// - /// City name + /// City name. /// - [JsonProperty("name")] + [JsonPropertyName("name")] public string Name { get; set; } /// - /// Internal parameter + /// Response status code. /// - [JsonProperty("cod")] + [JsonPropertyName("cod")] public int Code { get; set; } /// - /// Icon url + /// Weather icon URL (default size). + /// + [JsonIgnore] + public string Icon + => $"https://openweathermap.org/img/w/{WeatherInfos?[0]?.Icon}.png"; + + /// + /// Weather icon URL (2x resolution). + /// + [JsonIgnore] + public string Icon2x + => $"https://openweathermap.org/img/w/{WeatherInfos?[0]?.Icon}@2x.png"; + + /// + /// Weather icon URL (4x resolution). /// [JsonIgnore] - public string Icon => $"https://openweathermap.org/img/w/{WeatherInfos?[0]?.Icon}.png"; + public string Icon4x + => $"https://openweathermap.org/img/w/{WeatherInfos?[0]?.Icon}@4x.png"; /// - /// Icon name + /// Weather icon name. /// [JsonIgnore] - public string IconName => $"{WeatherInfos?[0]?.Icon}"; + public string IconName + => WeatherInfos?[0]?.Icon; } } diff --git a/src/OpenWeatherMapSharp/Models/Wind.cs b/src/OpenWeatherMapSharp/Models/Wind.cs index 16a9e89..4c733ee 100644 --- a/src/OpenWeatherMapSharp/Models/Wind.cs +++ b/src/OpenWeatherMapSharp/Models/Wind.cs @@ -1,30 +1,34 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace OpenWeatherMapSharp.Models { /// - /// Wind information + /// Represents wind-related weather data, + /// including speed, direction, and gusts. /// public class Wind { /// - /// Wind speed - /// Unit Default: meter/sec, Metric: meter/sec, Imperial: miles/hour + /// Wind speed. + /// Units — Default & Metric: + /// meters/second, Imperial: miles/hour. /// - [JsonProperty("speed")] + [JsonPropertyName("speed")] public double Speed { get; set; } /// - /// Wind direction, degrees (meteorological) + /// Wind direction in + /// meteorological degrees (0°–360°). /// - [JsonProperty("deg")] + [JsonPropertyName("deg")] public double Degrees { get; set; } /// - /// Wind gust - /// Unit Default: meter/sec, Metric: meter/sec, Imperial: miles/hour + /// Wind gust speed. + /// Units — Default & Metric: + /// meters/second, Imperial: miles/hour. /// - [JsonProperty("gust")] + [JsonPropertyName("gust")] public double Gust { get; set; } } } diff --git a/src/OpenWeatherMapSharp/OpenWeatherMapService.cs b/src/OpenWeatherMapSharp/OpenWeatherMapService.cs index 10f0264..463ba19 100644 --- a/src/OpenWeatherMapSharp/OpenWeatherMapService.cs +++ b/src/OpenWeatherMapSharp/OpenWeatherMapService.cs @@ -8,16 +8,17 @@ namespace OpenWeatherMapSharp { /// - /// This class is used to communicate with the Open Weather Map API + /// Provides access to the OpenWeatherMap API + /// for retrieving weather and geolocation data. /// public class OpenWeatherMapService : IOpenWeatherMapService { private readonly string _apiKey; /// - /// Create an instance of the OpenWeatherMapService. + /// Initializes a new instance of the class with the specified API key. /// - /// + /// Your OpenWeatherMap API key. public OpenWeatherMapService(string apiKey) { _apiKey = apiKey; @@ -30,29 +31,48 @@ public async Task> GetWeatherAsync( LanguageCode language = LanguageCode.EN, Unit unit = Unit.Standard) { - string url = string.Format(Statics.WeatherCoordinatesUri, latitude, longitude, unit.ToString().ToLower(), language.ToString().ToLower(), _apiKey); + string url = string.Format( + Statics.WeatherCoordinatesUri, + latitude, + longitude, + unit.ToString("G").ToLowerInvariant(), + language.ToString("G").ToLowerInvariant(), + _apiKey); + return await HttpService.GetDataAsync(url); } /// - [Obsolete("Please note that API requests by city name, zip-codes and city id have been deprecated. Although they are still available for use, bug fixing and updates are no longer available for this functionality.")] + [Obsolete("API requests by city name, ZIP code, and city ID are deprecated. They are still supported, but no longer maintained or updated.")] public async Task> GetWeatherAsync( string city, LanguageCode language = LanguageCode.EN, Unit unit = Unit.Standard) { - string url = string.Format(Statics.WeatherCityUri, city, unit.ToString().ToLower(), language.ToString().ToLower(), _apiKey); + string url = string.Format( + Statics.WeatherCityUri, + city, + unit.ToString("G").ToLowerInvariant(), + language.ToString("G").ToLowerInvariant(), + _apiKey); + return await HttpService.GetDataAsync(url); } /// - [Obsolete("Please note that API requests by city name, zip-codes and city id have been deprecated. Although they are still available for use, bug fixing and updates are no longer available for this functionality.")] + [Obsolete("API requests by city name, ZIP code, and city ID are deprecated. They are still supported, but no longer maintained or updated.")] public async Task> GetWeatherAsync( int cityId, LanguageCode language = LanguageCode.EN, Unit unit = Unit.Standard) { - string url = string.Format(Statics.WeatherIdUri, cityId, unit.ToString().ToLower(), language.ToString().ToLower(), _apiKey); + string url = string.Format( + Statics.WeatherIdUri, + cityId, + unit.ToString("G").ToLowerInvariant(), + language.ToString("G").ToLowerInvariant(), + _apiKey); + return await HttpService.GetDataAsync(url); } @@ -63,29 +83,48 @@ public async Task> GetForecastAsync( LanguageCode language = LanguageCode.EN, Unit unit = Unit.Standard) { - string url = string.Format(Statics.ForecastCoordinatesUri, latitude, longitude, unit.ToString().ToLower(), language.ToString().ToLower(), _apiKey); + string url = string.Format( + Statics.ForecastCoordinatesUri, + latitude, + longitude, + unit.ToString("G").ToLowerInvariant(), + language.ToString("G").ToLowerInvariant(), + _apiKey); + return await HttpService.GetDataAsync(url); } /// - [Obsolete("Please note that API requests by city name, zip-codes and city id have been deprecated. Although they are still available for use, bug fixing and updates are no longer available for this functionality.")] + [Obsolete("API requests by city name, ZIP code, and city ID are deprecated. They are still supported, but no longer maintained or updated.")] public async Task> GetForecastAsync( string city, LanguageCode language = LanguageCode.EN, Unit unit = Unit.Standard) { - string url = string.Format(Statics.ForecastCityUri, city, unit.ToString().ToLower(), language.ToString().ToLower(), _apiKey); + string url = string.Format( + Statics.ForecastCityUri, + city, + unit.ToString("G").ToLowerInvariant(), + language.ToString("G").ToLowerInvariant(), + _apiKey); + return await HttpService.GetDataAsync(url); } /// - [Obsolete("Please note that API requests by city name, zip-codes and city id have been deprecated. Although they are still available for use, bug fixing and updates are no longer available for this functionality.")] + [Obsolete("API requests by city name, ZIP code, and city ID are deprecated. They are still supported, but no longer maintained or updated.")] public async Task> GetForecastAsync( int id, LanguageCode language = LanguageCode.EN, Unit unit = Unit.Standard) { - string url = string.Format(Statics.ForecastIdUri, id, unit.ToString().ToLower(), language.ToString().ToLower(), _apiKey); + string url = string.Format( + Statics.ForecastIdUri, + id, + unit.ToString("G").ToLowerInvariant(), + language.ToString("G").ToLowerInvariant(), + _apiKey); + return await HttpService.GetDataAsync(url); } diff --git a/src/OpenWeatherMapSharp/OpenWeatherMapSharp.csproj b/src/OpenWeatherMapSharp/OpenWeatherMapSharp.csproj index 26e50c4..efbce50 100644 --- a/src/OpenWeatherMapSharp/OpenWeatherMapSharp.csproj +++ b/src/OpenWeatherMapSharp/OpenWeatherMapSharp.csproj @@ -21,13 +21,13 @@ en - + - + - + diff --git a/src/OpenWeatherMapSharp/Utils/HttpService.cs b/src/OpenWeatherMapSharp/Utils/HttpService.cs index 739d19d..cc4c797 100644 --- a/src/OpenWeatherMapSharp/Utils/HttpService.cs +++ b/src/OpenWeatherMapSharp/Utils/HttpService.cs @@ -1,13 +1,38 @@ -using Newtonsoft.Json; -using OpenWeatherMapSharp.Models; +using OpenWeatherMapSharp.Models; using System; using System.Net.Http; +using System.Text.Json; using System.Threading.Tasks; namespace OpenWeatherMapSharp.Utils { + /// + /// Provides internal HTTP functionality + /// for retrieving and deserializing + /// OpenWeatherMap API responses. + /// internal static class HttpService { + private static readonly JsonSerializerOptions defaultJsonSerializerOptions = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + + /// + /// Sends an HTTP GET request to the + /// specified URL and deserializes the + /// JSON response into the specified class. + /// + /// + /// The type to deserialize the response into. + /// + /// + /// The URL to send the request to. + /// + /// A task representing the asynchronous operation. + /// The result contains a wrapped response with + /// either data or an error message. + /// internal static async Task> GetDataAsync(string url) where TClass : class { using (HttpClient client = new HttpClient()) @@ -18,13 +43,17 @@ internal static async Task> GetDataAsync + { + IsSuccess = false, + Error = "Empty response received from server." + }; } return new OpenWeatherMapServiceResponse { IsSuccess = true, - Response = JsonConvert.DeserializeObject(json) + Response = JsonSerializer.Deserialize(json, defaultJsonSerializerOptions) }; } catch (Exception ex) diff --git a/src/OpenWeatherMapSharp/Utils/LongExtensions.cs b/src/OpenWeatherMapSharp/Utils/LongExtensions.cs index 2f4d33c..df55bee 100644 --- a/src/OpenWeatherMapSharp/Utils/LongExtensions.cs +++ b/src/OpenWeatherMapSharp/Utils/LongExtensions.cs @@ -2,13 +2,27 @@ namespace OpenWeatherMapSharp.Utils { + /// + /// Provides extension methods for + /// converting Unix timestamps to . + /// internal static class LongExtensions { + /// + /// Converts a Unix timestamp + /// (seconds since 1970-01-01 UTC) + /// to a local . + /// + /// + /// The Unix timestamp to convert. + /// + /// A object representing + /// the local time. + /// internal static DateTime ToDateTime(this long unixTimeStamp) { - DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - dateTime = dateTime.AddSeconds(unixTimeStamp).ToLocalTime(); - return dateTime; + DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + return dateTime.AddSeconds(unixTimeStamp).ToLocalTime(); } } } diff --git a/src/OpenWeatherMapSharp/Utils/Statics.cs b/src/OpenWeatherMapSharp/Utils/Statics.cs index bf7c671..32129ef 100644 --- a/src/OpenWeatherMapSharp/Utils/Statics.cs +++ b/src/OpenWeatherMapSharp/Utils/Statics.cs @@ -1,37 +1,84 @@ namespace OpenWeatherMapSharp.Utils { + /// + /// Contains static URI templates for accessing various OpenWeatherMap API endpoints. + /// internal static class Statics { - private static readonly string BaseUri = "https://api.openweathermap.org"; - private static readonly string WeatherBaseUri = $"{BaseUri}/data/2.5/weather"; - private static readonly string ForecastBaseUri = $"{BaseUri}/data/2.5/forecast"; - private static readonly string GeocodeBaseUri = $"{BaseUri}/geo/1.0"; + private static readonly string BaseUri + = "https://api.openweathermap.org"; - public static readonly string WeatherCoordinatesUri + private static readonly string WeatherBaseUri + = $"{BaseUri}/data/2.5/weather"; + + private static readonly string ForecastBaseUri + = $"{BaseUri}/data/2.5/forecast"; + + private static readonly string GeocodeBaseUri + = $"{BaseUri}/geo/1.0"; + + + /// + /// Weather by geographic coordinates (latitude, longitude). + /// Format: lat={0}&lon={1}&units={2}&lang={3}&appid={4} + /// + public static readonly string WeatherCoordinatesUri = WeatherBaseUri + "?lat={0}&lon={1}&units={2}&lang={3}&appid={4}"; - public static readonly string WeatherCityUri + /// + /// Weather by city name. + /// Format: q={0}&units={1}&lang={2}&appid={3} + /// + public static readonly string WeatherCityUri = WeatherBaseUri + "?q={0}&units={1}&lang={2}&appid={3}"; - public static readonly string WeatherIdUri + /// + /// Weather by city ID. + /// Format: id={0}&units={1}&lang={2}&appid={3} + /// + public static readonly string WeatherIdUri = WeatherBaseUri + "?id={0}&units={1}&lang={2}&appid={3}"; - public static readonly string ForecastCoordinatesUri + /// + /// Forecast by geographic coordinates. + /// Format: lat={0}&lon={1}&units={2}&lang={3}&appid={4} + /// + public static readonly string ForecastCoordinatesUri = ForecastBaseUri + "?lat={0}&lon={1}&units={2}&lang={3}&appid={4}"; - - public static readonly string ForecastCityUri + + /// + /// Forecast by city name. + /// Format: q={0}&units={1}&lang={2}&appid={3} + /// + public static readonly string ForecastCityUri = ForecastBaseUri + "?q={0}&units={1}&lang={2}&appid={3}"; - public static readonly string ForecastIdUri + /// + /// Forecast by city ID. + /// Format: id={0}&units={1}&lang={2}&appid={3} + /// + public static readonly string ForecastIdUri = ForecastBaseUri + "?id={0}&units={1}&lang={2}&appid={3}"; - public static readonly string GeocodeLocationUri + /// + /// Geocoding by location name. + /// Format: q={0}&limit={1}&appid={2} + /// + public static readonly string GeocodeLocationUri = GeocodeBaseUri + "/direct?q={0}&limit={1}&appid={2}"; - public static readonly string GeocodeZipUri + /// + /// Geocoding by ZIP/postal code. + /// Format: zip={0}&appid={1} + /// + public static readonly string GeocodeZipUri = GeocodeBaseUri + "/zip?zip={0}&appid={1}"; - public static readonly string GeocodeReverseUri + /// + /// Reverse geocoding by geographic coordinates. + /// Format: lat={0}&lon={1}&limit={2}&appid={3} + /// + public static readonly string GeocodeReverseUri = GeocodeBaseUri + "/reverse?lat={0}&lon={1}&limit={2}&appid={3}"; } } diff --git a/tests/OpenWeatherMapSharp.UnitTests/GlobalUsings.cs b/tests/OpenWeatherMapSharp.UnitTests/GlobalUsings.cs deleted file mode 100644 index 8c927eb..0000000 --- a/tests/OpenWeatherMapSharp.UnitTests/GlobalUsings.cs +++ /dev/null @@ -1 +0,0 @@ -global using Xunit; \ No newline at end of file diff --git a/tests/OpenWeatherMapSharp.UnitTests/OpenWeatherMapServiceTests.cs b/tests/OpenWeatherMapSharp.UnitTests/OpenWeatherMapServiceTests.cs index 42c8802..a751675 100644 --- a/tests/OpenWeatherMapSharp.UnitTests/OpenWeatherMapServiceTests.cs +++ b/tests/OpenWeatherMapSharp.UnitTests/OpenWeatherMapServiceTests.cs @@ -1,212 +1,268 @@ using OpenWeatherMapSharp.Models; +using OpenWeatherMapSharp.Models.Enums; +using Xunit; namespace OpenWeatherMapSharp.UnitTests; +/// +/// Integration tests for the OpenWeatherMapService using actual API requests. +/// public class OpenWeatherMapServiceTests { private const string OPENWEATHERMAPAPIKEY = "OWMAPIKEY"; - [Fact] public async Task BasicInvalidCredentials() { - // arrange - OpenWeatherMapService service = new("apikey"); + // Arrange + OpenWeatherMapService service = new("invalid_api_key"); string cityName = "Pforzheim"; - // act - OpenWeatherMapServiceResponse serviceResponse = await service.GetWeatherAsync(cityName); + // Act + OpenWeatherMapServiceResponse serviceResponse + = await service.GetWeatherAsync(cityName); - // assert + // Assert Assert.NotNull(serviceResponse); Assert.False(serviceResponse.IsSuccess); Assert.NotNull(serviceResponse.Error); } - [Fact] - public async Task GetWeatherLocationTest() + public async Task GetWeatherByCoordinates_ShouldReturnCorrectLocation() { - // arrange + // Arrange OpenWeatherMapService service = new(OPENWEATHERMAPAPIKEY); double latitude = 48.89; double longitude = 8.69; - // act - OpenWeatherMapServiceResponse serviceResponse = await service.GetWeatherAsync(latitude, longitude); + // Act + OpenWeatherMapServiceResponse serviceResponse + = await service.GetWeatherAsync(latitude, longitude); - // assert + // Assert Assert.NotNull(serviceResponse); Assert.NotNull(serviceResponse.Response); Assert.Null(serviceResponse.Error); Assert.True(serviceResponse.IsSuccess); - Assert.Equal(serviceResponse.Response.Coordinates.Latitude, latitude); - Assert.Equal(serviceResponse.Response.Coordinates.Longitude, longitude); + Assert.Equal(latitude, serviceResponse.Response.Coordinates.Latitude); + Assert.Equal(longitude, serviceResponse.Response.Coordinates.Longitude); } [Fact] - public async Task GetWeatherCityNameTest() + public async Task GetWeatherByCityName_ShouldReturnCorrectCity() { - // arrange + // Arrange OpenWeatherMapService service = new(OPENWEATHERMAPAPIKEY); string cityName = "Pforzheim"; - // act - OpenWeatherMapServiceResponse serviceResponse = await service.GetWeatherAsync(cityName); + // Act + OpenWeatherMapServiceResponse serviceResponse + = await service.GetWeatherAsync(cityName); - // assert + // Assert Assert.NotNull(serviceResponse); Assert.NotNull(serviceResponse.Response); Assert.Null(serviceResponse.Error); Assert.True(serviceResponse.IsSuccess); - Assert.Equal(serviceResponse.Response.Name, cityName); + Assert.Equal(cityName, serviceResponse.Response.Name); } [Fact] - public async Task GetWeatherCityIdTest() + public async Task GetWeatherByCityId_ShouldReturnCorrectCity() { - // arrange + // Arrange OpenWeatherMapService service = new(OPENWEATHERMAPAPIKEY); int cityId = 2928810; string cityName = "Essen"; - // act - OpenWeatherMapServiceResponse serviceResponse = await service.GetWeatherAsync(cityId); + // Act + OpenWeatherMapServiceResponse serviceResponse + = await service.GetWeatherAsync(cityId); - // assert + // Assert Assert.NotNull(serviceResponse); Assert.NotNull(serviceResponse.Response); Assert.Null(serviceResponse.Error); Assert.True(serviceResponse.IsSuccess); - Assert.Equal(serviceResponse.Response.Name, cityName); + Assert.Equal(cityName, serviceResponse.Response.Name); } - [Fact] - public async Task GetForecastLocationTest() + public async Task GetForecastByCoordinates_ShouldReturnCorrectLocation() { - // arrange + // Arrange OpenWeatherMapService service = new(OPENWEATHERMAPAPIKEY); double latitude = 48.89; double longitude = 8.69; - // act - OpenWeatherMapServiceResponse serviceResponse = await service.GetForecastAsync(latitude, longitude); + // Act + OpenWeatherMapServiceResponse serviceResponse + = await service.GetForecastAsync(latitude, longitude); - // assert + // Assert Assert.NotNull(serviceResponse); Assert.NotNull(serviceResponse.Response); Assert.Null(serviceResponse.Error); Assert.True(serviceResponse.IsSuccess); - Assert.Equal(serviceResponse.Response.City.Coordinates.Latitude, latitude); - Assert.Equal(serviceResponse.Response.City.Coordinates.Longitude, longitude); + Assert.Equal(latitude, serviceResponse.Response.City.Coordinates.Latitude); + Assert.Equal(longitude, serviceResponse.Response.City.Coordinates.Longitude); } [Fact] - public async Task GetForecastCityNameTest() + public async Task GetForecastByCityName_ShouldReturnCorrectCity() { - // arrange + // Arrange OpenWeatherMapService service = new(OPENWEATHERMAPAPIKEY); string cityName = "Pforzheim"; - // act - OpenWeatherMapServiceResponse serviceResponse = await service.GetForecastAsync(cityName); + // Act + OpenWeatherMapServiceResponse serviceResponse + = await service.GetForecastAsync(cityName); - // assert + // Assert Assert.NotNull(serviceResponse); Assert.NotNull(serviceResponse.Response); Assert.Null(serviceResponse.Error); Assert.True(serviceResponse.IsSuccess); - Assert.Equal(serviceResponse.Response.City.Name, cityName); + Assert.Equal(cityName, serviceResponse.Response.City.Name); } [Fact] - public async Task GetForecastCityIdTest() + public async Task GetForecastByCityId_ShouldReturnCorrectCity() { - // arrange + // Arrange OpenWeatherMapService service = new(OPENWEATHERMAPAPIKEY); int cityId = 2928810; string cityName = "Essen"; - // act - OpenWeatherMapServiceResponse serviceResponse = await service.GetForecastAsync(cityId); + // Act + OpenWeatherMapServiceResponse serviceResponse + = await service.GetForecastAsync(cityId); - // assert + // Assert Assert.NotNull(serviceResponse); Assert.NotNull(serviceResponse.Response); Assert.Null(serviceResponse.Error); Assert.True(serviceResponse.IsSuccess); - Assert.Equal(serviceResponse.Response.City.Name, cityName); + Assert.Equal(cityName, serviceResponse.Response.City.Name); } - [Fact] - public async Task GetGeolocationFromNameTest() + public async Task GetGeolocationByName_ShouldReturnCorrectLocation() { - // arrange + // Arrange OpenWeatherMapService service = new(OPENWEATHERMAPAPIKEY); string name = "Pforzheim"; string country = "DE"; - // act - OpenWeatherMapServiceResponse> serviceResponse = await service.GetLocationByNameAsync(name); + // Act + OpenWeatherMapServiceResponse> serviceResponse + = await service.GetLocationByNameAsync(name); - // assert + // Assert Assert.NotNull(serviceResponse); Assert.NotNull(serviceResponse.Response); Assert.Null(serviceResponse.Error); Assert.True(serviceResponse.IsSuccess); - GeocodeInfo? firstCountry = serviceResponse.Response.FirstOrDefault(); - Assert.NotNull(firstCountry); - Assert.Equal(name, firstCountry.Name); - Assert.Equal(country, firstCountry.Country); + GeocodeInfo? firstResult = serviceResponse.Response.FirstOrDefault(); + Assert.NotNull(firstResult); + Assert.Equal(name, firstResult.Name); + Assert.Equal(country, firstResult.Country); } [Fact] - public async Task GetGeolocationFromZipTest() + public async Task GetGeolocationByCoordinates_ShouldReturnCorrectLocation() { - // arrange + // Arrange OpenWeatherMapService service = new(OPENWEATHERMAPAPIKEY); - string zipCode = "75173"; + double latitude = 48.89; + double longitude = 8.69; string cityName = "Pforzheim"; string country = "DE"; - // act - OpenWeatherMapServiceResponse serviceResponse = await service.GetLocationByZipAsync($"{zipCode},{country}"); + // Act + OpenWeatherMapServiceResponse> serviceResponse + = await service.GetLocationByLatLonAsync(latitude, longitude); - // assert + // Assert Assert.NotNull(serviceResponse); Assert.NotNull(serviceResponse.Response); Assert.Null(serviceResponse.Error); Assert.True(serviceResponse.IsSuccess); - Assert.Equal(zipCode, serviceResponse.Response.ZipCode); - Assert.Equal(cityName, serviceResponse.Response.Name); - Assert.Equal(country, serviceResponse.Response.Country); + + GeocodeInfo? firstResult = serviceResponse.Response.FirstOrDefault(); + Assert.NotNull(firstResult); + Assert.Equal(cityName, firstResult.Name); + Assert.Equal(country, firstResult.Country); } [Fact] - public async Task GetGeolocationFromLatLonTest() + public void Constructor_WithEmptyApiKey_ShouldNotThrowButLikelyFailAtRuntime() { - // arrange + // Arrange + OpenWeatherMapService service = new(""); + + // Act + + // Assert + Assert.NotNull(service); + } + + [Theory] + [InlineData(LanguageCode.EN, Unit.Standard)] + [InlineData(LanguageCode.DE, Unit.Metric)] + [InlineData(LanguageCode.FR, Unit.Imperial)] + public async Task GetWeather_WithDifferentLanguagesAndUnits_ShouldSucceed(LanguageCode lang, Unit unit) + { + // Arrange OpenWeatherMapService service = new(OPENWEATHERMAPAPIKEY); - double latitude = 48.89; - double longitude = 8.69; - string cityName = "Pforzheim"; - string country = "DE"; - // act - OpenWeatherMapServiceResponse> serviceResponse = await service.GetLocationByLatLonAsync(latitude, longitude); + // Act + OpenWeatherMapServiceResponse response + = await service.GetWeatherAsync(48.89, 8.69, lang, unit); - // assert - Assert.NotNull(serviceResponse); - Assert.NotNull(serviceResponse.Response); - Assert.Null(serviceResponse.Error); - Assert.True(serviceResponse.IsSuccess); + // Assert + Assert.NotNull(response); + Assert.True(response.IsSuccess); + Assert.NotNull(response.Response); + Assert.Null(response.Error); + } + + [Fact] + public async Task GetGeolocation_WithInvalidCity_ShouldReturnEmptyResult() + { + // Arrange + OpenWeatherMapService service = new(OPENWEATHERMAPAPIKEY); + + // Act + OpenWeatherMapServiceResponse> response + = await service.GetLocationByNameAsync("NowherevilleXYZ"); + + // Assert + Assert.NotNull(response); + Assert.True(response.IsSuccess); + Assert.NotNull(response.Response); + Assert.Empty(response.Response); + } + + [Theory] + [InlineData(-999, 10)] + [InlineData(91, 10)] + [InlineData(10, -200)] + public async Task GetWeather_WithInvalidCoordinates_ShouldFail(double lat, double lon) + { + // Arrange + OpenWeatherMapService service = new(OPENWEATHERMAPAPIKEY); + + // Act + OpenWeatherMapServiceResponse response + = await service.GetWeatherAsync(lat, lon); - GeocodeInfo? firstCountry = serviceResponse.Response.FirstOrDefault(); - Assert.NotNull(firstCountry); - Assert.Equal(cityName, firstCountry.Name); - Assert.Equal(country, firstCountry.Country); + // Assert + Assert.NotNull(response); + Assert.False(response.IsSuccess); + Assert.NotNull(response.Error); } } \ No newline at end of file diff --git a/tests/OpenWeatherMapSharp.UnitTests/OpenWeatherMapSharp.UnitTests.csproj b/tests/OpenWeatherMapSharp.UnitTests/OpenWeatherMapSharp.UnitTests.csproj index 200077d..2dfe0b0 100644 --- a/tests/OpenWeatherMapSharp.UnitTests/OpenWeatherMapSharp.UnitTests.csproj +++ b/tests/OpenWeatherMapSharp.UnitTests/OpenWeatherMapSharp.UnitTests.csproj @@ -1,7 +1,7 @@ - + - net7.0 + net9.0 enable enable @@ -10,13 +10,13 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all