From d9ac6d8bb20a71f826f01bf81f049483367c625b Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Sun, 12 Sep 2021 12:01:40 +0200 Subject: [PATCH 1/7] Refactored a bunch and added measurements --- HPOpenWeather.podspec | 4 +- .../DataTypes/Precipitation.swift | 16 ------ .../HPOpenWeather/DataTypes/Temperature.swift | 11 ---- .../{DataTypes => Models}/City.swift | 2 +- .../DailyTemperature.swift | 0 .../Forecasts/BasicWeather.swift | 0 .../Forecasts/CurrentWeather.swift | 0 .../Forecasts/DailyForecast.swift | 0 .../Forecasts/HourlyForecast.swift | 0 .../HPOpenWeather/Models/Precipitation.swift | 32 +++++++++++ .../{DataTypes => Models}/Sun.swift | 2 +- .../HPOpenWeather/Models/Temperature.swift | 31 ++++++++++ .../Alert.swift => Models/WeatherAlert.swift} | 2 +- .../WeatherCondition.swift | 0 .../{DataTypes => Models}/WeatherIcon.swift | 0 .../{DataTypes => Models}/Wind.swift | 13 +++++ Sources/HPOpenWeather/OpenWeather.swift | 55 +++++++++++++----- .../Requests/APINetworkRequest.swift | 20 ++++--- .../Requests/ExcludableField.swift | 11 ---- .../Requests/RequestLanguage.swift | 52 ----------------- .../HPOpenWeather/Requests/RequestUnits.swift | 13 ----- .../Requests/WeatherRequest+Combine.swift | 18 ++++-- .../WeatherRequest+ExcludableField.swift | 13 +++++ .../Requests/WeatherRequest.swift | 16 +----- .../Response/WeatherResponse+Language.swift | 56 +++++++++++++++++++ .../Response/WeatherResponse+Units.swift | 41 ++++++++++++++ .../Response/WeatherResponse.swift | 11 +++- .../HPOpenWeatherTests.swift | 22 ++++---- 28 files changed, 279 insertions(+), 162 deletions(-) delete mode 100644 Sources/HPOpenWeather/DataTypes/Precipitation.swift delete mode 100644 Sources/HPOpenWeather/DataTypes/Temperature.swift rename Sources/HPOpenWeather/{DataTypes => Models}/City.swift (89%) rename Sources/HPOpenWeather/{DataTypes => Models}/DailyTemperature.swift (100%) rename Sources/HPOpenWeather/{DataTypes => Models}/Forecasts/BasicWeather.swift (100%) rename Sources/HPOpenWeather/{DataTypes => Models}/Forecasts/CurrentWeather.swift (100%) rename Sources/HPOpenWeather/{DataTypes => Models}/Forecasts/DailyForecast.swift (100%) rename Sources/HPOpenWeather/{DataTypes => Models}/Forecasts/HourlyForecast.swift (100%) create mode 100644 Sources/HPOpenWeather/Models/Precipitation.swift rename Sources/HPOpenWeather/{DataTypes => Models}/Sun.swift (69%) create mode 100644 Sources/HPOpenWeather/Models/Temperature.swift rename Sources/HPOpenWeather/{DataTypes/Alert.swift => Models/WeatherAlert.swift} (91%) rename Sources/HPOpenWeather/{DataTypes => Models}/WeatherCondition.swift (100%) rename Sources/HPOpenWeather/{DataTypes => Models}/WeatherIcon.swift (100%) rename Sources/HPOpenWeather/{DataTypes => Models}/Wind.swift (52%) delete mode 100644 Sources/HPOpenWeather/Requests/ExcludableField.swift delete mode 100644 Sources/HPOpenWeather/Requests/RequestLanguage.swift delete mode 100644 Sources/HPOpenWeather/Requests/RequestUnits.swift create mode 100644 Sources/HPOpenWeather/Requests/WeatherRequest+ExcludableField.swift create mode 100644 Sources/HPOpenWeather/Response/WeatherResponse+Language.swift create mode 100644 Sources/HPOpenWeather/Response/WeatherResponse+Units.swift diff --git a/HPOpenWeather.podspec b/HPOpenWeather.podspec index a44214c..9745433 100644 --- a/HPOpenWeather.podspec +++ b/HPOpenWeather.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "HPOpenWeather" - s.version = "4.1.5" + s.version = "5.0.0" s.summary = "Cross-platform framework to communicate with the OpenWeatherMap JSON API" s.license = { :type => "MIT", :file => "LICENSE.md" } @@ -27,6 +27,6 @@ Pod::Spec.new do |s| s.swift_version = "5.1" s.requires_arc = true - s.dependency "HPNetwork" + s.dependency "HPNetwork", "~> 2.0.2" end diff --git a/Sources/HPOpenWeather/DataTypes/Precipitation.swift b/Sources/HPOpenWeather/DataTypes/Precipitation.swift deleted file mode 100644 index d40ee0f..0000000 --- a/Sources/HPOpenWeather/DataTypes/Precipitation.swift +++ /dev/null @@ -1,16 +0,0 @@ -import Foundation - -/// Type that holds information about recent precipitation -public struct Precipitation: Codable, Equatable, Hashable { - - /// Precipitation volume for the last 1 hour, measured in mm - public var lastHour: Double? - /// Precipitation volume for the last 3 hours, measured in mm - public var lastThreeHours: Double? - - enum CodingKeys: String, CodingKey { - case lastHour = "1h" - case lastThreeHours = "3h" - } - -} diff --git a/Sources/HPOpenWeather/DataTypes/Temperature.swift b/Sources/HPOpenWeather/DataTypes/Temperature.swift deleted file mode 100644 index 63686bd..0000000 --- a/Sources/HPOpenWeather/DataTypes/Temperature.swift +++ /dev/null @@ -1,11 +0,0 @@ -import Foundation - -/// Type that holds information about daily temperature changes -public struct Temperature: Codable, Equatable, Hashable { - - /// The actually measured temperature - public let actual: Double - /// The feels-like temperature - public let feelsLike: Double - -} diff --git a/Sources/HPOpenWeather/DataTypes/City.swift b/Sources/HPOpenWeather/Models/City.swift similarity index 89% rename from Sources/HPOpenWeather/DataTypes/City.swift rename to Sources/HPOpenWeather/Models/City.swift index c41e982..09c2570 100644 --- a/Sources/HPOpenWeather/DataTypes/City.swift +++ b/Sources/HPOpenWeather/Models/City.swift @@ -2,7 +2,7 @@ import Foundation import CoreLocation /// Type that holds information about the reqeuest's nearest city -public struct City: Codable, Equatable, Hashable { +public struct City: Codable, Equatable, Hashable, Identifiable { /// The ID assigned to the city public let id: Int diff --git a/Sources/HPOpenWeather/DataTypes/DailyTemperature.swift b/Sources/HPOpenWeather/Models/DailyTemperature.swift similarity index 100% rename from Sources/HPOpenWeather/DataTypes/DailyTemperature.swift rename to Sources/HPOpenWeather/Models/DailyTemperature.swift diff --git a/Sources/HPOpenWeather/DataTypes/Forecasts/BasicWeather.swift b/Sources/HPOpenWeather/Models/Forecasts/BasicWeather.swift similarity index 100% rename from Sources/HPOpenWeather/DataTypes/Forecasts/BasicWeather.swift rename to Sources/HPOpenWeather/Models/Forecasts/BasicWeather.swift diff --git a/Sources/HPOpenWeather/DataTypes/Forecasts/CurrentWeather.swift b/Sources/HPOpenWeather/Models/Forecasts/CurrentWeather.swift similarity index 100% rename from Sources/HPOpenWeather/DataTypes/Forecasts/CurrentWeather.swift rename to Sources/HPOpenWeather/Models/Forecasts/CurrentWeather.swift diff --git a/Sources/HPOpenWeather/DataTypes/Forecasts/DailyForecast.swift b/Sources/HPOpenWeather/Models/Forecasts/DailyForecast.swift similarity index 100% rename from Sources/HPOpenWeather/DataTypes/Forecasts/DailyForecast.swift rename to Sources/HPOpenWeather/Models/Forecasts/DailyForecast.swift diff --git a/Sources/HPOpenWeather/DataTypes/Forecasts/HourlyForecast.swift b/Sources/HPOpenWeather/Models/Forecasts/HourlyForecast.swift similarity index 100% rename from Sources/HPOpenWeather/DataTypes/Forecasts/HourlyForecast.swift rename to Sources/HPOpenWeather/Models/Forecasts/HourlyForecast.swift diff --git a/Sources/HPOpenWeather/Models/Precipitation.swift b/Sources/HPOpenWeather/Models/Precipitation.swift new file mode 100644 index 0000000..8c2c045 --- /dev/null +++ b/Sources/HPOpenWeather/Models/Precipitation.swift @@ -0,0 +1,32 @@ +import Foundation + +/// Type that holds information about recent precipitation +public struct Precipitation: Codable, Equatable, Hashable { + + enum CodingKeys: String, CodingKey { + case lastHour = "1h" + case lastThreeHours = "3h" + } + + /// Precipitation volume for the last 1 hour, measured in mm + public var lastHour: Double? + /// Precipitation volume for the last 3 hours, measured in mm + public var lastThreeHours: Double? + + @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) + public var lastHourMeasurement: Measurement? { + guard let lastHour = lastHour else { + return nil + } + return Measurement(value: lastHour, unit: .millimeters) + } + + @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) + public var lastThreeHoursMeasurement: Measurement? { + guard let lastThreeHours = lastThreeHours else { + return nil + } + return Measurement(value: lastThreeHours, unit: .millimeters) + } + +} diff --git a/Sources/HPOpenWeather/DataTypes/Sun.swift b/Sources/HPOpenWeather/Models/Sun.swift similarity index 69% rename from Sources/HPOpenWeather/DataTypes/Sun.swift rename to Sources/HPOpenWeather/Models/Sun.swift index 880baa1..dacf579 100644 --- a/Sources/HPOpenWeather/DataTypes/Sun.swift +++ b/Sources/HPOpenWeather/Models/Sun.swift @@ -5,7 +5,7 @@ public struct Sun: Codable, Equatable, Hashable { /// Sunset time public let sunset: Date - /// Sunrise timeWind speed. Unit Default: meter/sec, Metric: meter/sec, Imperial: miles/hour. + /// Sunrise time public let sunrise: Date } diff --git a/Sources/HPOpenWeather/Models/Temperature.swift b/Sources/HPOpenWeather/Models/Temperature.swift new file mode 100644 index 0000000..31daeaa --- /dev/null +++ b/Sources/HPOpenWeather/Models/Temperature.swift @@ -0,0 +1,31 @@ +import Foundation + +/// Type that holds information about daily temperature changes +public struct Temperature: Codable, Equatable, Hashable { + + /// The actually measured temperature + public let actual: Double + /// The feels-like temperature + public let feelsLike: Double + + @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) + public var actualMeasurement: Measurement { + actualMeasurement(units: OpenWeather.shared.units) + } + + @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) + public func actualMeasurement(units: WeatherResponse.Units) -> Measurement { + Measurement(value: actual, unit: units.temperatureUnit) + } + + @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) + public var feelsLikeMeasurement: Measurement { + feelsLikeMeasurement(units: OpenWeather.shared.units) + } + + @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) + public func feelsLikeMeasurement(units: WeatherResponse.Units) -> Measurement { + Measurement(value: feelsLike, unit: units.temperatureUnit) + } + +} diff --git a/Sources/HPOpenWeather/DataTypes/Alert.swift b/Sources/HPOpenWeather/Models/WeatherAlert.swift similarity index 91% rename from Sources/HPOpenWeather/DataTypes/Alert.swift rename to Sources/HPOpenWeather/Models/WeatherAlert.swift index 426a519..40d6e75 100644 --- a/Sources/HPOpenWeather/DataTypes/Alert.swift +++ b/Sources/HPOpenWeather/Models/WeatherAlert.swift @@ -1,7 +1,7 @@ import Foundation /// Type that holds information about weather alerts -public struct Alert: Codable, Hashable, Equatable { +public struct WeatherAlert: Codable, Hashable, Equatable { /// Name of the alert source. Please read here the full list of alert sources public let senderName: String diff --git a/Sources/HPOpenWeather/DataTypes/WeatherCondition.swift b/Sources/HPOpenWeather/Models/WeatherCondition.swift similarity index 100% rename from Sources/HPOpenWeather/DataTypes/WeatherCondition.swift rename to Sources/HPOpenWeather/Models/WeatherCondition.swift diff --git a/Sources/HPOpenWeather/DataTypes/WeatherIcon.swift b/Sources/HPOpenWeather/Models/WeatherIcon.swift similarity index 100% rename from Sources/HPOpenWeather/DataTypes/WeatherIcon.swift rename to Sources/HPOpenWeather/Models/WeatherIcon.swift diff --git a/Sources/HPOpenWeather/DataTypes/Wind.swift b/Sources/HPOpenWeather/Models/Wind.swift similarity index 52% rename from Sources/HPOpenWeather/DataTypes/Wind.swift rename to Sources/HPOpenWeather/Models/Wind.swift index e52ba0b..6a306e4 100644 --- a/Sources/HPOpenWeather/DataTypes/Wind.swift +++ b/Sources/HPOpenWeather/Models/Wind.swift @@ -10,4 +10,17 @@ public struct Wind: Codable, Equatable, Hashable { /// The wind direction measured in degrees from North public let degrees: Double? + @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) + public var speedMeasurement: Measurement? { + speedMeasurement(units: OpenWeather.shared.units) + } + + @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) + public func speedMeasurement(units: WeatherResponse.Units) -> Measurement? { + guard let speed = speed else { + return nil + } + return Measurement(value: speed, unit: units.windSpeedUnit) + } + } diff --git a/Sources/HPOpenWeather/OpenWeather.swift b/Sources/HPOpenWeather/OpenWeather.swift index 4d86e54..6439790 100644 --- a/Sources/HPOpenWeather/OpenWeather.swift +++ b/Sources/HPOpenWeather/OpenWeather.swift @@ -2,17 +2,26 @@ import CoreLocation import Foundation import HPNetwork +/// A type to request current weather conditions and forecasts public final class OpenWeather { // MARK: - Nested Types /// Type that can be used to configure all settings at once public struct Settings { + /// The API key to use for weather requests let apiKey : String - let language: RequestLanguage - let units: RequestUnits - - public init(apiKey: String, language: RequestLanguage = .english, units: RequestUnits = .metric) { + /// The language that will be used in weather responses + let language: WeatherResponse.Language + /// The units that will be used in weather responses + let units: WeatherResponse.Units + + /// Initialises a new settings instance + /// - Parameters: + /// - apiKey: The API key to use for weather requests + /// - language: The language that will be used in weather responses + /// - units: The units that will be used in weather responses + public init(apiKey: String, language: WeatherResponse.Language = .english, units: WeatherResponse.Units = .metric) { self.language = language self.units = units self.apiKey = apiKey @@ -27,16 +36,20 @@ public final class OpenWeather { /// The OpenWeatherMap API key to authorize requests public var apiKey : String? /// The language that should be used in API responses - public var language: RequestLanguage = .english + public var language: WeatherResponse.Language = .english /// The units that should be used to format the API responses - public var units: RequestUnits = .metric + public var units: WeatherResponse.Units = .metric // MARK: - Init + /// Initialised a new instance of `OpenWeather` and applies the specified API key + /// - Parameter apiKey: the API key to authenticate with the OpenWeatherMap API public init(apiKey: String? = nil) { self.apiKey = apiKey } + /// Initialised a new instance of `OpenWeather` and applies the specified settimgs + /// - Parameter settings: the settings to apply, including API key, language and units public init(settings: Settings) { self.apiKey = settings.apiKey self.language = settings.language @@ -47,7 +60,7 @@ public final class OpenWeather { public func requestWeather( coordinate: CLLocationCoordinate2D, - excludedFields: [ExcludableField]? = nil, + excludedFields: [WeatherRequest.ExcludableField]? = nil, date: Date? = nil, urlSession: URLSession = .shared, finishingQueue: DispatchQueue = .main, @@ -57,25 +70,28 @@ public final class OpenWeather { let request = WeatherRequest( coordinate: coordinate, excludedFields: excludedFields, - date: date, - urlSession: urlSession, - finishingQueue: finishingQueue + date: date ) - schedule(request, progressHandler: progressHandler, completion: completion) + schedule(request, urlSession: urlSession, finishingQueue: finishingQueue, progressHandler: progressHandler, completion: completion) } /// Sends the specified request to the OpenWeather API /// - Parameters: /// - request: The request object that holds information about request location, date, etc. + /// - urlSession: The `URLSession` that will be used schedule requests + /// - finishingQueue: The `DispatchQueue` that the `completion` block will be called on + /// - progressHandler: A block that will be called every time the progress of the network request updates /// - completion: The completion block that will be called once the networking finishes /// - Returns: A network task that can be used to cancel the request public func schedule( _ request: WeatherRequest, + urlSession: URLSession = .shared, + finishingQueue: DispatchQueue = .main, progressHandler: ProgressHandler? = nil, completion: @escaping (Result) -> Void) { guard let apiKey = apiKey else { - request.finishingQueue.async { + finishingQueue.async { completion(.failure(NSError.noApiKey)) } return @@ -84,10 +100,19 @@ public final class OpenWeather { let settings = Settings(apiKey: apiKey, language: language, units: units) do { - let networkRequest = try request.makeNetworkRequest(settings: settings) - Network.shared.schedule(request: networkRequest, progressHandler: progressHandler, completion: completion) + let networkRequest = try request.makeNetworkRequest(settings: settings, urlSession: urlSession, finishingQueue: finishingQueue) + Network.shared.schedule(request: networkRequest, progressHandler: progressHandler) { result in + switch result { + case .success(var response): + response.units = settings.units + response.language = settings.language + completion(.success(response)) + case .failure: + completion(result) + } + } } catch let error { - request.finishingQueue.async { + finishingQueue.async { completion(.failure(error)) } } diff --git a/Sources/HPOpenWeather/Requests/APINetworkRequest.swift b/Sources/HPOpenWeather/Requests/APINetworkRequest.swift index 5b67b9f..69c2205 100644 --- a/Sources/HPOpenWeather/Requests/APINetworkRequest.swift +++ b/Sources/HPOpenWeather/Requests/APINetworkRequest.swift @@ -1,20 +1,26 @@ import Foundation import HPNetwork -public struct APINetworkRequest: DecodableRequest { +struct APINetworkRequest: DecodableRequest { - public let url: URL? - public let urlSession: URLSession - public let finishingQueue: DispatchQueue - public let requestMethod: NetworkRequestMethod = .get - public let headerFields = [NetworkRequestHeaderField.contentTypeJSON] + typealias Output = WeatherResponse - public let decoder: JSONDecoder = { + static let decoder: JSONDecoder = { let decoder = JSONDecoder() decoder.dateDecodingStrategy = .secondsSince1970 return decoder }() + let url: URL? + let urlSession: URLSession + let finishingQueue: DispatchQueue + let requestMethod: NetworkRequestMethod = .get + let headerFields = [NetworkRequestHeaderField.contentTypeJSON] + + var decoder: JSONDecoder { + APINetworkRequest.decoder + } + public func makeURL() throws -> URL { guard let url = url else { throw NSError(code: 6, description: "Could not create URL") diff --git a/Sources/HPOpenWeather/Requests/ExcludableField.swift b/Sources/HPOpenWeather/Requests/ExcludableField.swift deleted file mode 100644 index c2e5c43..0000000 --- a/Sources/HPOpenWeather/Requests/ExcludableField.swift +++ /dev/null @@ -1,11 +0,0 @@ -import Foundation - -public enum ExcludableField: String { - - case current - case minutely - case hourly - case daily - case alerts - -} diff --git a/Sources/HPOpenWeather/Requests/RequestLanguage.swift b/Sources/HPOpenWeather/Requests/RequestLanguage.swift deleted file mode 100644 index 4c25782..0000000 --- a/Sources/HPOpenWeather/Requests/RequestLanguage.swift +++ /dev/null @@ -1,52 +0,0 @@ -import Foundation - -/// The language that should be used in API responses for example for weather condition descriptions -public enum RequestLanguage: String { - - case afrikaans = "af" - case arabic = "ar" - case azerbaijani = "az" - case bulgarian = "bg" - case catalan = "ca" - case czech = "cz" - case danish = "da" - case german = "de" - case greek = "el" - case english = "en" - case basque = "eu" - case persian = "fa" - case finnish = "fi" - case french = "fr" - case galician = "gl" - case hebrew = "he" - case hindi = "hi" - case croatian = "hr" - case hungarian = "hu" - case indonesian = "id" - case italian = "it" - case japanese = "ja" - case korean = "kr" - case latvian = "la" - case lithuanian = "lt" - case macedonian = "mk" - case norwegian = "no" - case dutch = "nl" - case polish = "pl" - case portuguese = "pt" - case portugueseBrasil = "pt_br" - case romanian = "ro" - case russian = "ru" - case swedish = "sv" - case slovak = "sk" - case slovenian = "sl" - case spanish = "es" - case serbian = "sr" - case thai = "th" - case turkish = "tr" - case ukrainian = "ua" - case vietnamese = "vi" - case chineseSimplified = "zh_cn" - case chineseTraditional = "zh_tw" - case zulu = "zu" - -} diff --git a/Sources/HPOpenWeather/Requests/RequestUnits.swift b/Sources/HPOpenWeather/Requests/RequestUnits.swift deleted file mode 100644 index 3308807..0000000 --- a/Sources/HPOpenWeather/Requests/RequestUnits.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Foundation - -/// The units that should the data in the API responses should be formatted in -public enum RequestUnits: String { - - /// Temperature in Kelvin and wind speed in meter/sec - case standard - /// Temperature in Celsius and wind speed in meter/sec - case metric - /// Temperature in Fahrenheit and wind speed in miles/hour - case imperial - -} diff --git a/Sources/HPOpenWeather/Requests/WeatherRequest+Combine.swift b/Sources/HPOpenWeather/Requests/WeatherRequest+Combine.swift index a539865..5cab473 100644 --- a/Sources/HPOpenWeather/Requests/WeatherRequest+Combine.swift +++ b/Sources/HPOpenWeather/Requests/WeatherRequest+Combine.swift @@ -5,12 +5,22 @@ import Foundation @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) public extension WeatherRequest { - func publisher(apiKey: String, language: RequestLanguage = .english, units: RequestUnits = .metric) -> AnyPublisher { - publisher(settings: OpenWeather.Settings(apiKey: apiKey, language: language, units: units)) + func publisher( + apiKey: String, + language: WeatherResponse.Language = .english, + units: WeatherResponse.Units = .metric, + urlSession: URLSession = .shared, + finishingQueue: DispatchQueue = .main) -> AnyPublisher + { + publisher( + settings: OpenWeather.Settings(apiKey: apiKey, language: language, units: units), + urlSession: urlSession, + finishingQueue: finishingQueue + ) } - func publisher(settings: OpenWeather.Settings) -> AnyPublisher { - let request = APINetworkRequest(url: makeURL(settings: settings), urlSession: urlSession, finishingQueue: finishingQueue) + func publisher(settings: OpenWeather.Settings, urlSession: URLSession = .shared, finishingQueue: DispatchQueue = .main) -> AnyPublisher { + let request = APINetworkRequest(url: makeURL(settings: settings), urlSession: urlSession, finishingQueue: finishingQueue) return request.dataTaskPublisher() } diff --git a/Sources/HPOpenWeather/Requests/WeatherRequest+ExcludableField.swift b/Sources/HPOpenWeather/Requests/WeatherRequest+ExcludableField.swift new file mode 100644 index 0000000..9acaf75 --- /dev/null +++ b/Sources/HPOpenWeather/Requests/WeatherRequest+ExcludableField.swift @@ -0,0 +1,13 @@ +import Foundation + +public extension WeatherRequest { + + enum ExcludableField: String, Codable { + case current + case minutely + case hourly + case daily + case alerts + } + +} diff --git a/Sources/HPOpenWeather/Requests/WeatherRequest.swift b/Sources/HPOpenWeather/Requests/WeatherRequest.swift index 4b7890e..68c70ce 100644 --- a/Sources/HPOpenWeather/Requests/WeatherRequest.swift +++ b/Sources/HPOpenWeather/Requests/WeatherRequest.swift @@ -2,7 +2,7 @@ import Foundation import CoreLocation import HPNetwork -public struct WeatherRequest { +public struct WeatherRequest: Codable { // MARK: - Associated Types @@ -13,23 +13,13 @@ public struct WeatherRequest { public let coordinate: CLLocationCoordinate2D public let excludedFields: [ExcludableField]? public let date: Date? - public let urlSession: URLSession - public let finishingQueue: DispatchQueue // MARK: - Init - public init( - coordinate: CLLocationCoordinate2D, - excludedFields: [ExcludableField]? = nil, - date: Date? = nil, - urlSession: URLSession = .shared, - finishingQueue: DispatchQueue = .main - ) { + public init(coordinate: CLLocationCoordinate2D, excludedFields: [ExcludableField]? = nil, date: Date? = nil) { self.coordinate = coordinate self.excludedFields = excludedFields?.hp_nilIfEmpty() self.date = date - self.urlSession = urlSession - self.finishingQueue = finishingQueue } // MARK: - OpenWeatherRequest @@ -47,7 +37,7 @@ public struct WeatherRequest { .build() } - func makeNetworkRequest(settings: OpenWeather.Settings) throws -> APINetworkRequest { + func makeNetworkRequest(settings: OpenWeather.Settings, urlSession: URLSession, finishingQueue: DispatchQueue) throws -> APINetworkRequest { if let date = date, date < Date(), abs(date.timeIntervalSinceNow) <= 6 * .hour { throw NSError.timeMachineDate } diff --git a/Sources/HPOpenWeather/Response/WeatherResponse+Language.swift b/Sources/HPOpenWeather/Response/WeatherResponse+Language.swift new file mode 100644 index 0000000..af7cb86 --- /dev/null +++ b/Sources/HPOpenWeather/Response/WeatherResponse+Language.swift @@ -0,0 +1,56 @@ +import Foundation + +public extension WeatherResponse { + + /// The language that should be used in API responses for example for weather condition descriptions + enum Language: String, Codable { + + case afrikaans = "af" + case arabic = "ar" + case azerbaijani = "az" + case bulgarian = "bg" + case catalan = "ca" + case czech = "cz" + case danish = "da" + case german = "de" + case greek = "el" + case english = "en" + case basque = "eu" + case persian = "fa" + case finnish = "fi" + case french = "fr" + case galician = "gl" + case hebrew = "he" + case hindi = "hi" + case croatian = "hr" + case hungarian = "hu" + case indonesian = "id" + case italian = "it" + case japanese = "ja" + case korean = "kr" + case latvian = "la" + case lithuanian = "lt" + case macedonian = "mk" + case norwegian = "no" + case dutch = "nl" + case polish = "pl" + case portuguese = "pt" + case portugueseBrasil = "pt_br" + case romanian = "ro" + case russian = "ru" + case swedish = "sv" + case slovak = "sk" + case slovenian = "sl" + case spanish = "es" + case serbian = "sr" + case thai = "th" + case turkish = "tr" + case ukrainian = "ua" + case vietnamese = "vi" + case chineseSimplified = "zh_cn" + case chineseTraditional = "zh_tw" + case zulu = "zu" + + } + +} diff --git a/Sources/HPOpenWeather/Response/WeatherResponse+Units.swift b/Sources/HPOpenWeather/Response/WeatherResponse+Units.swift new file mode 100644 index 0000000..baa73dd --- /dev/null +++ b/Sources/HPOpenWeather/Response/WeatherResponse+Units.swift @@ -0,0 +1,41 @@ +import Foundation + +public extension WeatherResponse { + + /// The units that should the data in the API responses should be formatted in + enum Units: String, Codable { + + /// Temperature in Kelvin and wind speed in meter/sec + case standard + /// Temperature in Celsius and wind speed in meter/sec + case metric + /// Temperature in Fahrenheit and wind speed in miles/hour + case imperial + + @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) + var temperatureUnit: UnitTemperature { + switch self { + case .standard: + return .kelvin + case .metric: + return .celsius + case .imperial: + return .fahrenheit + } + } + + @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) + var windSpeedUnit: UnitSpeed { + switch self { + case .standard: + return .metersPerSecond + case .metric: + return .metersPerSecond + case .imperial: + return .milesPerHour + } + } + + } + +} diff --git a/Sources/HPOpenWeather/Response/WeatherResponse.swift b/Sources/HPOpenWeather/Response/WeatherResponse.swift index 536061a..f71cdad 100644 --- a/Sources/HPOpenWeather/Response/WeatherResponse.swift +++ b/Sources/HPOpenWeather/Response/WeatherResponse.swift @@ -7,18 +7,23 @@ public struct WeatherResponse: Codable, Equatable, Hashable { public let hourlyForecasts: [HourlyForecast]? public let dailyForecasts: [DailyForecast]? /// Government weather alerts data from major national weather warning systems - public let alerts: [Alert]? + public let alerts: [WeatherAlert]? - public var timezone: TimeZone { - TimeZone(identifier: timezoneIdentifier)! + public var timezone: TimeZone? { + TimeZone(identifier: timezoneIdentifier) } + public internal(set) var units: Units! + public internal(set) var language: Language! + enum CodingKeys: String, CodingKey { case timezoneIdentifier = "timezone" case currentWeather = "current" case hourlyForecasts = "hourly" case dailyForecasts = "daily" case alerts + case units + case language } } diff --git a/Tests/HPOpenWeatherTests/HPOpenWeatherTests.swift b/Tests/HPOpenWeatherTests/HPOpenWeatherTests.swift index d4ef5ae..37bad5e 100644 --- a/Tests/HPOpenWeatherTests/HPOpenWeatherTests.swift +++ b/Tests/HPOpenWeatherTests/HPOpenWeatherTests.swift @@ -15,36 +15,35 @@ final class HPOpenWeatherTests: XCTestCase { } func testCurrentRequest() { - let request = WeatherRequest(coordinate: .init(latitude: 40, longitude: 30)) let exp = XCTestExpectation(description: "Fetched data") - OpenWeather.shared.schedule(request) { result in - exp.fulfill() - XCTAssertResult(result) - } + OpenWeather.shared.requestWeather(coordinate: .init(latitude: 52.5200, longitude: 13.4050)) { result in + XCTAssertResult(result) + exp.fulfill() + } wait(for: [exp], timeout: 10) } func testTimeMachineRequestFailing() { - let request = WeatherRequest(coordinate: .init(latitude: 40, longitude: 30), date: Date().addingTimeInterval(-1 * .hour)) + let request = WeatherRequest(coordinate: .init(latitude: 52.5200, longitude: 13.4050), date: Date().addingTimeInterval(-1 * .hour)) let exp = XCTestExpectation(description: "Fetched data") OpenWeather.shared.schedule(request) { result in - exp.fulfill() XCTAssertResultError(result) + exp.fulfill() } wait(for: [exp], timeout: 10) } func testTimeMachineRequest() { - let request = WeatherRequest(coordinate: .init(latitude: 40, longitude: 30), date: Date().addingTimeInterval(-7 * .hour)) + let request = WeatherRequest(coordinate: .init(latitude: 52.5200, longitude: 13.4050), date: Date().addingTimeInterval(-7 * .hour)) let exp = XCTestExpectation(description: "Fetched data") OpenWeather.shared.schedule(request) { result in - exp.fulfill() XCTAssertResult(result) + exp.fulfill() } wait(for: [exp], timeout: 10) @@ -53,7 +52,7 @@ final class HPOpenWeatherTests: XCTestCase { @available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) func testPublisher() { - let request = WeatherRequest(coordinate: .init(latitude: 40, longitude: 30)) + let request = WeatherRequest(coordinate: .init(latitude: 52.5200, longitude: 13.4050)) let expectationFinished = expectation(description: "finished") let expectationReceive = expectation(description: "receiveValue") @@ -96,8 +95,7 @@ extension Encodable { /// Asserts that the result is not a failure func XCTAssertResult(_ result: Result) { if case .failure(let error as NSError) = result { - print(error) - XCTFail(error.localizedDescription) + XCTFail(error.localizedDescription) } } From 6c4909b16999ed9fd168ba350ffd86080622c8eb Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Sun, 12 Sep 2021 12:05:41 +0200 Subject: [PATCH 2/7] Added more documentation and removed force unwrapping --- Sources/HPOpenWeather/Models/Wind.swift | 3 +++ Sources/HPOpenWeather/Response/WeatherResponse.swift | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Sources/HPOpenWeather/Models/Wind.swift b/Sources/HPOpenWeather/Models/Wind.swift index 6a306e4..c3ec7a6 100644 --- a/Sources/HPOpenWeather/Models/Wind.swift +++ b/Sources/HPOpenWeather/Models/Wind.swift @@ -10,11 +10,14 @@ public struct Wind: Codable, Equatable, Hashable { /// The wind direction measured in degrees from North public let degrees: Double? + /// A measurement of the `speed` property if existing, measured in the units currently specified in `OpenWeather.shared` @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) public var speedMeasurement: Measurement? { speedMeasurement(units: OpenWeather.shared.units) } + /// A measurement of the `speed` property if existing, measured in the passed in units + /// - Parameter units: The units to use when formatting the `speed` property @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) public func speedMeasurement(units: WeatherResponse.Units) -> Measurement? { guard let speed = speed else { diff --git a/Sources/HPOpenWeather/Response/WeatherResponse.swift b/Sources/HPOpenWeather/Response/WeatherResponse.swift index f71cdad..83090c2 100644 --- a/Sources/HPOpenWeather/Response/WeatherResponse.swift +++ b/Sources/HPOpenWeather/Response/WeatherResponse.swift @@ -13,8 +13,8 @@ public struct WeatherResponse: Codable, Equatable, Hashable { TimeZone(identifier: timezoneIdentifier) } - public internal(set) var units: Units! - public internal(set) var language: Language! + public internal(set) var units: Units = .metric + public internal(set) var language: Language = .english enum CodingKeys: String, CodingKey { case timezoneIdentifier = "timezone" From 3e539c3a5825299d745da71fdfae8052093207a2 Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Sun, 12 Sep 2021 12:11:32 +0200 Subject: [PATCH 3/7] Updating documentation --- README.md | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index cd2714c..f1b6082 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ -

- Storage -

+# HPOpenWeather CodeFactor @@ -8,21 +6,23 @@ [![GitHub license](https://img.shields.io/github/license/henrik-dmg/HPOpenWeather)](https://github.com/henrik-dmg/HPOpenWeather/blob/master/LICENSE.md) HPOpenWeather is a cross-platform Swift framework to communicate with the OpenWeather One-Call API. See their [documentation](https://openweathermap.org/api/one-call-api) for further details. + ## Installation HPOpenWeather supports iOS 9.0+, watchOS 3.0+, tvOS 9.0+ and macOS 10.10+. -#### SPM +### SPM Add `.package(url: "https://github.com/henrik-dmg/HPOpenWeather", from: "4.0.0")` to your `Package.swift` file -#### CocoaPods +### CocoaPods Add `pod 'HPOpenWeather'` to your `Podfile` and run `pod install` ## Usage To get started, you need an API key from [OpenWeather](https://openweathermap.org). Put this API key in the initialiser, additionally you can also specify a custom temperature format and/or language used in the responses (see list for available languages and units below). + ```swift import HPOpenWeather @@ -35,9 +35,10 @@ OpenWeather.shared.units = .metric let settings = OpenWeather.Settings(apiKey: "yourAPIKey", language: .german, units: .metric) OpenWeather.shared.apply(settings) ``` + You can also customise the response data units and language by accessing the `language` and `units` propertis. -## Making a request +### Making a request To make a request, initialize a new request object like this @@ -53,7 +54,7 @@ let timemachineRequest = WeatherRequest(coordinate: .init(latitude: 40, longitud **Note:** the date has to be at least 6 hours in the past -To post a request, call `sendWeatherRequest` on `OpenWeather`: +To post a request, call `sendWeatherRequest` on `OpenWeather`: ```swift OpenWeather.shared.performWeatherRequest(request) { result in @@ -66,9 +67,9 @@ OpenWeather.shared.performWeatherRequest(request) { result in } ``` -**The following response languages are available** +### Available languages (default in bold) -- English (default) +- **English** - Russian - Italian - Spanish @@ -88,8 +89,8 @@ OpenWeather.shared.performWeatherRequest(request) { result in - Croatian - Catalan -**The following temperature units are available** +### Available units (default in bold) -- Celsius (default) -- Kelvin -- Fahrenheit +- **Metric** (wind speed in m/s, temperature in Celsius) +- Imperial (wind speed in mph, temperature in Fahrenheit) +- Standard (wind speed in m/s, temperature in Kelvin) From 6ac46a7de924788501bee33ccabcf6c4cf45295c Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Sun, 27 Feb 2022 14:39:53 +0100 Subject: [PATCH 4/7] Cleaning up --- .vscode/launch.json | 15 +++++ HPOpenWeather.podspec | 16 +++--- Package.resolved | 13 ++++- Package.swift | 9 +-- README.md | 2 +- .../URLQueryItemsBuilder+Extensions.swift | 12 ---- .../HPOpenWeather/Models/Precipitation.swift | 2 - .../HPOpenWeather/Models/Temperature.swift | 4 -- Sources/HPOpenWeather/Models/Wind.swift | 2 - Sources/HPOpenWeather/OpenWeather.swift | 48 ++++------------ .../Requests/APINetworkRequest.swift | 1 - .../Requests/WeatherRequest+Combine.swift | 6 +- .../Requests/WeatherRequest.swift | 29 ++++++---- .../Response/WeatherResponse.swift | 16 ++++-- .../HPOpenWeatherTests.swift | 55 +++++++++++-------- 15 files changed, 110 insertions(+), 120 deletions(-) create mode 100644 .vscode/launch.json delete mode 100644 Sources/HPOpenWeather/Extensions/URLQueryItemsBuilder+Extensions.swift diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..b7e1bb4 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Test HPOpenWeather", + "program": "/Applications/Xcode.app/Contents/Developer/usr/bin/xctest", + "args": [ + ".build/debug/HPOpenWeatherPackageTests.xctest" + ], + "cwd": "${workspaceFolder:HPOpenWeather}", + "preLaunchTask": "swift: Build All" + } + ] +} \ No newline at end of file diff --git a/HPOpenWeather.podspec b/HPOpenWeather.podspec index 9745433..e1044ce 100644 --- a/HPOpenWeather.podspec +++ b/HPOpenWeather.podspec @@ -10,23 +10,23 @@ Pod::Spec.new do |s| s.author = { "henrik-dmg" => "henrik@panhans.dev" } s.social_media_url = "https://twitter.com/henrik_dmg" - s.ios.deployment_target = "9.0" - s.osx.deployment_target = "10.11" - s.watchos.deployment_target = "3.0" - s.tvos.deployment_target = "9.0" + s.ios.deployment_target = "13.0" + s.watchos.deployment_target = "7.0" + s.tvos.deployment_target = "13.0" + s.osx.deployment_target = "10.15" s.source = { :git => 'https://github.com/henrik-dmg/HPOpenWeather.git', :tag => s.version } s.source_files = "Sources/**/*.swift" - + s.framework = "Foundation" s.ios.framework = "UIKit" s.watchos.framework = "UIKit" s.tvos.framework = "UIKit" - s.osx.framework = "AppKit" - s.swift_version = "5.1" + s.swift_version = "5.5" s.requires_arc = true - s.dependency "HPNetwork", "~> 2.0.2" + s.dependency "HPNetwork", "~> 3.1.1" + s.dependency "HPURLBuilder", "~> 1.0.0" end diff --git a/Package.resolved b/Package.resolved index 64216c5..e8836d1 100644 --- a/Package.resolved +++ b/Package.resolved @@ -6,8 +6,17 @@ "repositoryURL": "https://github.com/henrik-dmg/HPNetwork", "state": { "branch": null, - "revision": "7e888cdaabf7cb6e8c6640db57cb4325099f79b2", - "version": "2.0.2" + "revision": "2bdaaa3f1c9d52a30e5afb8f737c3b72f5afba0c", + "version": "3.1.1" + } + }, + { + "package": "HPURLBuilder", + "repositoryURL": "https://github.com/henrik-dmg/HPURLBuilder", + "state": { + "branch": null, + "revision": "49ad1fb6f10914e7134dc9f8e5c21f48a52e3d37", + "version": "1.1.0" } } ] diff --git a/Package.swift b/Package.swift index 6ff0492..2161b65 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.1 +// swift-tools-version:5.5 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -6,7 +6,7 @@ import PackageDescription let package = Package( name: "HPOpenWeather", platforms: [ - .iOS(.v9), .macOS(.v10_11), .tvOS(.v9), .watchOS(.v3) + .iOS(.v13), .tvOS(.v13), .watchOS(.v7), .macOS(.v10_15) ], products: [ // Products define the executables and libraries produced by a package, and make them visible to other packages. @@ -17,14 +17,15 @@ let package = Package( ], dependencies: [ // Dependencies declare other packages that this package depends on. - .package(url: "https://github.com/henrik-dmg/HPNetwork", from: "2.0.0") + .package(url: "https://github.com/henrik-dmg/HPNetwork", from: "3.0.0"), + .package(url: "https://github.com/henrik-dmg/HPURLBuilder", from: "1.0.0") ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. // Targets can depend on other targets in this package, and on products in packages which this package depends on. .target( name: "HPOpenWeather", - dependencies: ["HPNetwork"] + dependencies: ["HPNetwork", "HPURLBuilder"] ), .testTarget( name: "HPOpenWeatherTests", diff --git a/README.md b/README.md index f1b6082..92b522a 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ let timemachineRequest = WeatherRequest(coordinate: .init(latitude: 40, longitud To post a request, call `sendWeatherRequest` on `OpenWeather`: ```swift -OpenWeather.shared.performWeatherRequest(request) { result in +OpenWeather.shared.schedule(request) { result in switch result { case .success(let response): // do something with weather data here diff --git a/Sources/HPOpenWeather/Extensions/URLQueryItemsBuilder+Extensions.swift b/Sources/HPOpenWeather/Extensions/URLQueryItemsBuilder+Extensions.swift deleted file mode 100644 index e86e6cf..0000000 --- a/Sources/HPOpenWeather/Extensions/URLQueryItemsBuilder+Extensions.swift +++ /dev/null @@ -1,12 +0,0 @@ -import HPNetwork - -extension URLBuilder { - - static let weatherBase: URLBuilder = { - URLBuilder(host: "api.openweathermap.org") - .addingPathComponent("data") - .addingPathComponent("2.5") - .addingPathComponent("onecall") - }() - -} diff --git a/Sources/HPOpenWeather/Models/Precipitation.swift b/Sources/HPOpenWeather/Models/Precipitation.swift index 8c2c045..caf5c8f 100644 --- a/Sources/HPOpenWeather/Models/Precipitation.swift +++ b/Sources/HPOpenWeather/Models/Precipitation.swift @@ -13,7 +13,6 @@ public struct Precipitation: Codable, Equatable, Hashable { /// Precipitation volume for the last 3 hours, measured in mm public var lastThreeHours: Double? - @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) public var lastHourMeasurement: Measurement? { guard let lastHour = lastHour else { return nil @@ -21,7 +20,6 @@ public struct Precipitation: Codable, Equatable, Hashable { return Measurement(value: lastHour, unit: .millimeters) } - @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) public var lastThreeHoursMeasurement: Measurement? { guard let lastThreeHours = lastThreeHours else { return nil diff --git a/Sources/HPOpenWeather/Models/Temperature.swift b/Sources/HPOpenWeather/Models/Temperature.swift index 31daeaa..1da8435 100644 --- a/Sources/HPOpenWeather/Models/Temperature.swift +++ b/Sources/HPOpenWeather/Models/Temperature.swift @@ -8,22 +8,18 @@ public struct Temperature: Codable, Equatable, Hashable { /// The feels-like temperature public let feelsLike: Double - @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) public var actualMeasurement: Measurement { actualMeasurement(units: OpenWeather.shared.units) } - @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) public func actualMeasurement(units: WeatherResponse.Units) -> Measurement { Measurement(value: actual, unit: units.temperatureUnit) } - @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) public var feelsLikeMeasurement: Measurement { feelsLikeMeasurement(units: OpenWeather.shared.units) } - @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) public func feelsLikeMeasurement(units: WeatherResponse.Units) -> Measurement { Measurement(value: feelsLike, unit: units.temperatureUnit) } diff --git a/Sources/HPOpenWeather/Models/Wind.swift b/Sources/HPOpenWeather/Models/Wind.swift index c3ec7a6..09bf137 100644 --- a/Sources/HPOpenWeather/Models/Wind.swift +++ b/Sources/HPOpenWeather/Models/Wind.swift @@ -11,14 +11,12 @@ public struct Wind: Codable, Equatable, Hashable { public let degrees: Double? /// A measurement of the `speed` property if existing, measured in the units currently specified in `OpenWeather.shared` - @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) public var speedMeasurement: Measurement? { speedMeasurement(units: OpenWeather.shared.units) } /// A measurement of the `speed` property if existing, measured in the passed in units /// - Parameter units: The units to use when formatting the `speed` property - @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) public func speedMeasurement(units: WeatherResponse.Units) -> Measurement? { guard let speed = speed else { return nil diff --git a/Sources/HPOpenWeather/OpenWeather.swift b/Sources/HPOpenWeather/OpenWeather.swift index 6439790..7d79414 100644 --- a/Sources/HPOpenWeather/OpenWeather.swift +++ b/Sources/HPOpenWeather/OpenWeather.swift @@ -1,6 +1,5 @@ import CoreLocation import Foundation -import HPNetwork /// A type to request current weather conditions and forecasts public final class OpenWeather { @@ -62,60 +61,33 @@ public final class OpenWeather { coordinate: CLLocationCoordinate2D, excludedFields: [WeatherRequest.ExcludableField]? = nil, date: Date? = nil, - urlSession: URLSession = .shared, - finishingQueue: DispatchQueue = .main, - progressHandler: ProgressHandler? = nil, - completion: @escaping (Result) -> Void) - { + urlSession: URLSession = .shared + ) async throws -> WeatherResponse { let request = WeatherRequest( coordinate: coordinate, excludedFields: excludedFields, date: date ) - schedule(request, urlSession: urlSession, finishingQueue: finishingQueue, progressHandler: progressHandler, completion: completion) + return try await response(request, urlSession: urlSession) } /// Sends the specified request to the OpenWeather API /// - Parameters: /// - request: The request object that holds information about request location, date, etc. /// - urlSession: The `URLSession` that will be used schedule requests - /// - finishingQueue: The `DispatchQueue` that the `completion` block will be called on - /// - progressHandler: A block that will be called every time the progress of the network request updates - /// - completion: The completion block that will be called once the networking finishes /// - Returns: A network task that can be used to cancel the request - public func schedule( - _ request: WeatherRequest, - urlSession: URLSession = .shared, - finishingQueue: DispatchQueue = .main, - progressHandler: ProgressHandler? = nil, - completion: @escaping (Result) -> Void) - { + public func response(_ request: WeatherRequest, urlSession: URLSession = .shared) async throws -> WeatherRequest.Output { guard let apiKey = apiKey else { - finishingQueue.async { - completion(.failure(NSError.noApiKey)) - } - return + throw NSError.noApiKey } let settings = Settings(apiKey: apiKey, language: language, units: units) - do { - let networkRequest = try request.makeNetworkRequest(settings: settings, urlSession: urlSession, finishingQueue: finishingQueue) - Network.shared.schedule(request: networkRequest, progressHandler: progressHandler) { result in - switch result { - case .success(var response): - response.units = settings.units - response.language = settings.language - completion(.success(response)) - case .failure: - completion(result) - } - } - } catch let error { - finishingQueue.async { - completion(.failure(error)) - } - } + let networkRequest = try request.makeNetworkRequest(settings: settings, urlSession: urlSession) + var response = try await networkRequest.response().output + response.units = settings.units + response.language = settings.language + return response } // MARK: - Applying Settings diff --git a/Sources/HPOpenWeather/Requests/APINetworkRequest.swift b/Sources/HPOpenWeather/Requests/APINetworkRequest.swift index 69c2205..532c48c 100644 --- a/Sources/HPOpenWeather/Requests/APINetworkRequest.swift +++ b/Sources/HPOpenWeather/Requests/APINetworkRequest.swift @@ -13,7 +13,6 @@ struct APINetworkRequest: DecodableRequest { let url: URL? let urlSession: URLSession - let finishingQueue: DispatchQueue let requestMethod: NetworkRequestMethod = .get let headerFields = [NetworkRequestHeaderField.contentTypeJSON] diff --git a/Sources/HPOpenWeather/Requests/WeatherRequest+Combine.swift b/Sources/HPOpenWeather/Requests/WeatherRequest+Combine.swift index 5cab473..14f1273 100644 --- a/Sources/HPOpenWeather/Requests/WeatherRequest+Combine.swift +++ b/Sources/HPOpenWeather/Requests/WeatherRequest+Combine.swift @@ -1,8 +1,6 @@ -#if canImport(Combine) import Combine import Foundation -@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) public extension WeatherRequest { func publisher( @@ -20,10 +18,8 @@ public extension WeatherRequest { } func publisher(settings: OpenWeather.Settings, urlSession: URLSession = .shared, finishingQueue: DispatchQueue = .main) -> AnyPublisher { - let request = APINetworkRequest(url: makeURL(settings: settings), urlSession: urlSession, finishingQueue: finishingQueue) + let request = APINetworkRequest(url: makeURL(settings: settings), urlSession: urlSession) return request.dataTaskPublisher() } } - -#endif diff --git a/Sources/HPOpenWeather/Requests/WeatherRequest.swift b/Sources/HPOpenWeather/Requests/WeatherRequest.swift index 68c70ce..cd010d9 100644 --- a/Sources/HPOpenWeather/Requests/WeatherRequest.swift +++ b/Sources/HPOpenWeather/Requests/WeatherRequest.swift @@ -1,6 +1,7 @@ import Foundation import CoreLocation import HPNetwork +import HPURLBuilder public struct WeatherRequest: Codable { @@ -25,23 +26,27 @@ public struct WeatherRequest: Codable { // MARK: - OpenWeatherRequest func makeURL(settings: OpenWeather.Settings) -> URL? { - URLBuilder.weatherBase - .addingPathComponent(date != nil ? "timemachine" : nil) - .addingQueryItem(name: "lat", value: coordinate.latitude, digits: 5) - .addingQueryItem(name: "lon", value: coordinate.longitude, digits: 5) - .addingQueryItem(name: "dt", value: date.flatMap({ Int($0.timeIntervalSince1970) })) - .addingQueryItem(name: "exclude", value: excludedFields?.compactMap({ $0.rawValue })) - .addingQueryItem(name: "appid", value: settings.apiKey) - .addingQueryItem(name: "units", value: settings.units.rawValue) - .addingQueryItem(name: "lang", value: settings.language.rawValue) - .build() + URL.build { + Host("api.openweathermap.org") + PathComponent("data") + PathComponent("2.5") + PathComponent("onecall") + PathComponent(date != nil ? "timemachine" : nil) + QueryItem(name: "lat", value: coordinate.latitude, digits: 5) + QueryItem(name: "lon", value: coordinate.longitude, digits: 5) + QueryItem(name: "dt", value: date.flatMap({ Int($0.timeIntervalSince1970) })) + QueryItem(name: "exclude", value: excludedFields?.compactMap({ $0.rawValue })) + QueryItem(name: "appid", value: settings.apiKey) + QueryItem(name: "units", value: settings.units.rawValue) + QueryItem(name: "lang", value: settings.language.rawValue) + } } - func makeNetworkRequest(settings: OpenWeather.Settings, urlSession: URLSession, finishingQueue: DispatchQueue) throws -> APINetworkRequest { + func makeNetworkRequest(settings: OpenWeather.Settings, urlSession: URLSession) throws -> APINetworkRequest { if let date = date, date < Date(), abs(date.timeIntervalSinceNow) <= 6 * .hour { throw NSError.timeMachineDate } - return APINetworkRequest(url: makeURL(settings: settings), urlSession: urlSession, finishingQueue: finishingQueue) + return APINetworkRequest(url: makeURL(settings: settings), urlSession: urlSession) } } diff --git a/Sources/HPOpenWeather/Response/WeatherResponse.swift b/Sources/HPOpenWeather/Response/WeatherResponse.swift index 83090c2..dd880a0 100644 --- a/Sources/HPOpenWeather/Response/WeatherResponse.swift +++ b/Sources/HPOpenWeather/Response/WeatherResponse.swift @@ -9,21 +9,27 @@ public struct WeatherResponse: Codable, Equatable, Hashable { /// Government weather alerts data from major national weather warning systems public let alerts: [WeatherAlert]? + public internal(set) var language: WeatherResponse.Language? + public internal(set) var units: WeatherResponse.Units? + public var timezone: TimeZone? { TimeZone(identifier: timezoneIdentifier) } - public internal(set) var units: Units = .metric - public internal(set) var language: Language = .english - enum CodingKeys: String, CodingKey { case timezoneIdentifier = "timezone" case currentWeather = "current" case hourlyForecasts = "hourly" case dailyForecasts = "daily" case alerts - case units - case language } } + +public struct WeatherResponseContainer: Codable, Equatable, Hashable { + + public let response: WeatherResponse + public let language: WeatherResponse.Language + public let units: WeatherResponse.Units + +} diff --git a/Tests/HPOpenWeatherTests/HPOpenWeatherTests.swift b/Tests/HPOpenWeatherTests/HPOpenWeatherTests.swift index 37bad5e..45b8c19 100644 --- a/Tests/HPOpenWeatherTests/HPOpenWeatherTests.swift +++ b/Tests/HPOpenWeatherTests/HPOpenWeatherTests.swift @@ -14,39 +14,29 @@ final class HPOpenWeatherTests: XCTestCase { OpenWeather.shared.apiKey = nil } - func testCurrentRequest() { - let exp = XCTestExpectation(description: "Fetched data") - - OpenWeather.shared.requestWeather(coordinate: .init(latitude: 52.5200, longitude: 13.4050)) { result in - XCTAssertResult(result) - exp.fulfill() + func testCurrentRequest() async throws { + do { + _ = try await OpenWeather.shared.requestWeather(coordinate: .init(latitude: 52.5200, longitude: 13.4050)) + } catch let error as NSError { + print(error) + throw error } - - wait(for: [exp], timeout: 10) } - func testTimeMachineRequestFailing() { + func testTimeMachineRequestFailing() async throws { let request = WeatherRequest(coordinate: .init(latitude: 52.5200, longitude: 13.4050), date: Date().addingTimeInterval(-1 * .hour)) - let exp = XCTestExpectation(description: "Fetched data") - - OpenWeather.shared.schedule(request) { result in - XCTAssertResultError(result) - exp.fulfill() - } - wait(for: [exp], timeout: 10) + await HPAssertThrowsError { + try await OpenWeather.shared.response(request) + } } - func testTimeMachineRequest() { + func testTimeMachineRequest() async { let request = WeatherRequest(coordinate: .init(latitude: 52.5200, longitude: 13.4050), date: Date().addingTimeInterval(-7 * .hour)) - let exp = XCTestExpectation(description: "Fetched data") - - OpenWeather.shared.schedule(request) { result in - XCTAssertResult(result) - exp.fulfill() - } - wait(for: [exp], timeout: 10) + await HPAssertThrowsNoError { + try await OpenWeather.shared.response(request) + } } @@ -92,6 +82,23 @@ extension Encodable { } +func HPAssertThrowsError(_ work: () async throws -> T) async { + do { + _ = try await work() + XCTFail("Block should throw") + } catch { + return + } +} + +func HPAssertThrowsNoError(_ work: () async throws -> T) async { + do { + _ = try await work() + } catch let error { + XCTFail(error.localizedDescription) + } +} + /// Asserts that the result is not a failure func XCTAssertResult(_ result: Result) { if case .failure(let error as NSError) = result { From a90c1316d15f8c989e569ef35654e01ae3cbc7ce Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Sun, 27 Feb 2022 15:06:01 +0100 Subject: [PATCH 5/7] Cleaning up --- README.md | 4 +- .../Models/DailyTemperature.swift | 12 +- .../Models/Forecasts/CurrentWeather.swift | 116 +++++++-------- .../Models/Forecasts/DailyForecast.swift | 74 +++++----- .../Models/Forecasts/HourlyForecast.swift | 94 ++++++------ .../HPOpenWeather/Models/Precipitation.swift | 34 ++--- Sources/HPOpenWeather/Models/Sun.swift | 4 +- .../HPOpenWeather/Models/Temperature.swift | 34 +++-- .../HPOpenWeather/Models/WeatherAlert.swift | 34 ++--- .../Models/WeatherCondition.swift | 2 +- .../HPOpenWeather/Models/WeatherIcon.swift | 135 +++++++++--------- Sources/HPOpenWeather/Models/Wind.swift | 26 ++-- Sources/HPOpenWeather/OpenWeather.swift | 125 +++++++++------- .../Requests/APINetworkRequest.swift | 38 ++--- .../Requests/WeatherRequest+Combine.swift | 34 ++--- .../WeatherRequest+ExcludableField.swift | 14 +- .../Requests/WeatherRequest.swift | 98 ++++++------- .../Response/WeatherResponse+Language.swift | 98 +++++++------ .../Response/WeatherResponse+Units.swift | 62 ++++---- .../Response/WeatherResponse.swift | 36 ++--- .../HPOpenWeatherTests.swift | 8 +- 21 files changed, 555 insertions(+), 527 deletions(-) diff --git a/README.md b/README.md index 92b522a..4b0e0bd 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,11 @@ HPOpenWeather is a cross-platform Swift framework to communicate with the OpenWe ## Installation -HPOpenWeather supports iOS 9.0+, watchOS 3.0+, tvOS 9.0+ and macOS 10.10+. +HPOpenWeather supports iOS 13.0+, watchOS 7.0+, tvOS 13.0+ and macOS 10.15+. ### SPM -Add `.package(url: "https://github.com/henrik-dmg/HPOpenWeather", from: "4.0.0")` to your `Package.swift` file +Add `.package(url: "https://github.com/henrik-dmg/HPOpenWeather", from: "5.0.0")` to your `Package.swift` file ### CocoaPods diff --git a/Sources/HPOpenWeather/Models/DailyTemperature.swift b/Sources/HPOpenWeather/Models/DailyTemperature.swift index d323bd6..fa9a51a 100644 --- a/Sources/HPOpenWeather/Models/DailyTemperature.swift +++ b/Sources/HPOpenWeather/Models/DailyTemperature.swift @@ -3,17 +3,17 @@ import Foundation /// Type that holds information about daily temperature changes public struct DailyTemperature: Codable, Equatable, Hashable { - /// Day temperature. + /// Day temperature. public let day: Double - /// Night temperature. + /// Night temperature. public let night: Double - /// Minimum daily temperature. + /// Minimum daily temperature. public let min: Double? - /// Max daily temperature. + /// Max daily temperature. public let max: Double? - /// Evening temperature. + /// Evening temperature. public let evening: Double - /// Morning temperature. + /// Morning temperature. public let morning: Double enum CodingKeys: String, CodingKey { diff --git a/Sources/HPOpenWeather/Models/Forecasts/CurrentWeather.swift b/Sources/HPOpenWeather/Models/Forecasts/CurrentWeather.swift index 34694ae..3638d71 100644 --- a/Sources/HPOpenWeather/Models/Forecasts/CurrentWeather.swift +++ b/Sources/HPOpenWeather/Models/Forecasts/CurrentWeather.swift @@ -1,31 +1,31 @@ import Foundation public struct CurrentWeather: BasicWeatherResponse, SunResponse { - - // MARK: - Coding Keys - - enum CodingKeys: String, CodingKey { - case feelsLikeTemperature = "feels_like" - case snow - case rain - case timestamp = "dt" - case actualTemperature = "temp" - case pressure - case humidity - case dewPoint = "dew_point" - case uvIndex = "uvi" - case cloudCoverage = "clouds" - case visibility - case windSpeed = "wind_speed" - case windGust = "wind_gust" - case windDirection = "wind_deg" - case weatherArray = "weather" - case sunrise - case sunset - } - - // MARK: - Properties - + + // MARK: - Coding Keys + + enum CodingKeys: String, CodingKey { + case feelsLikeTemperature = "feels_like" + case snow + case rain + case timestamp = "dt" + case actualTemperature = "temp" + case pressure + case humidity + case dewPoint = "dew_point" + case uvIndex = "uvi" + case cloudCoverage = "clouds" + case visibility + case windSpeed = "wind_speed" + case windGust = "wind_gust" + case windDirection = "wind_deg" + case weatherArray = "weather" + case sunrise + case sunset + } + + // MARK: - Properties + public let timestamp: Date public let pressure: Double? public let humidity: Double? @@ -35,41 +35,41 @@ public struct CurrentWeather: BasicWeatherResponse, SunResponse { public let cloudCoverage: Double? public let rain: Precipitation? public let snow: Precipitation? - - // Temperature - - private let actualTemperature: Double - private let feelsLikeTemperature: Double - - public var temperature: Temperature { - Temperature(actual: actualTemperature, feelsLike: feelsLikeTemperature) - } - - // Weather Conditions - + + // Temperature + + private let actualTemperature: Double + private let feelsLikeTemperature: Double + + public var temperature: Temperature { + Temperature(actual: actualTemperature, feelsLike: feelsLikeTemperature) + } + + // Weather Conditions + private let weatherArray: [WeatherCondition] - + public var condition: WeatherCondition? { weatherArray.first } - - // Wind - - private let windSpeed: Double? - private let windGust: Double? - private let windDirection: Double? - - public var wind: Wind { - Wind(speed: windSpeed, gust: windGust, degrees: windDirection) - } - - // Sun - - private let sunrise: Date - private let sunset: Date - - public var sun: Sun { - Sun(sunset: sunset, sunrise: sunrise) - } - + + // Wind + + private let windSpeed: Double? + private let windGust: Double? + private let windDirection: Double? + + public var wind: Wind { + Wind(speed: windSpeed, gust: windGust, degrees: windDirection) + } + + // Sun + + private let sunrise: Date + private let sunset: Date + + public var sun: Sun { + Sun(sunset: sunset, sunrise: sunrise) + } + } diff --git a/Sources/HPOpenWeather/Models/Forecasts/DailyForecast.swift b/Sources/HPOpenWeather/Models/Forecasts/DailyForecast.swift index f466dab..1b9f063 100644 --- a/Sources/HPOpenWeather/Models/Forecasts/DailyForecast.swift +++ b/Sources/HPOpenWeather/Models/Forecasts/DailyForecast.swift @@ -2,29 +2,29 @@ import Foundation public struct DailyForecast: BasicWeatherResponse, SunResponse { - // MARK: - Coding Keys - - enum CodingKeys: String, CodingKey { - case feelsLikeTemperature = "feels_like" - case totalRain = "rain" - case totalSnow = "snow" - case timestamp = "dt" - case temperature = "temp" - case pressure - case humidity - case dewPoint = "dew_point" - case uvIndex = "uvi" - case cloudCoverage = "clouds" - case visibility - case windSpeed = "wind_speed" - case windGust = "wind_gust" - case windDirection = "wind_deg" - case weatherArray = "weather" - case sunrise - case sunset - } - - // MARK: - Properties + // MARK: - Coding Keys + + enum CodingKeys: String, CodingKey { + case feelsLikeTemperature = "feels_like" + case totalRain = "rain" + case totalSnow = "snow" + case timestamp = "dt" + case temperature = "temp" + case pressure + case humidity + case dewPoint = "dew_point" + case uvIndex = "uvi" + case cloudCoverage = "clouds" + case visibility + case windSpeed = "wind_speed" + case windGust = "wind_gust" + case windDirection = "wind_deg" + case weatherArray = "weather" + case sunrise + case sunset + } + + // MARK: - Properties public let temperature: DailyTemperature public let feelsLikeTemperature: DailyTemperature @@ -39,7 +39,7 @@ public struct DailyForecast: BasicWeatherResponse, SunResponse { public let visibility: Double? public let cloudCoverage: Double? - // Weather Conditions + // Weather Conditions private let weatherArray: [WeatherCondition] @@ -47,24 +47,24 @@ public struct DailyForecast: BasicWeatherResponse, SunResponse { weatherArray.first } - // Wind + // Wind - private let windSpeed: Double? - private let windGust: Double? - private let windDirection: Double? + private let windSpeed: Double? + private let windGust: Double? + private let windDirection: Double? - public var wind: Wind { - Wind(speed: windSpeed, gust: windGust, degrees: windDirection) - } + public var wind: Wind { + Wind(speed: windSpeed, gust: windGust, degrees: windDirection) + } - // Sun + // Sun - private let sunrise: Date - private let sunset: Date + private let sunrise: Date + private let sunset: Date - public var sun: Sun { - Sun(sunset: sunset, sunrise: sunrise) - } + public var sun: Sun { + Sun(sunset: sunset, sunrise: sunrise) + } } diff --git a/Sources/HPOpenWeather/Models/Forecasts/HourlyForecast.swift b/Sources/HPOpenWeather/Models/Forecasts/HourlyForecast.swift index 62febc2..8a2ffbb 100644 --- a/Sources/HPOpenWeather/Models/Forecasts/HourlyForecast.swift +++ b/Sources/HPOpenWeather/Models/Forecasts/HourlyForecast.swift @@ -1,29 +1,29 @@ import Foundation public struct HourlyForecast: BasicWeatherResponse { - - // MARK: - Coding Keys - - enum CodingKeys: String, CodingKey { - case actualTemperature = "temp" - case feelsLikeTemperature = "feels_like" - case snow - case rain - case timestamp = "dt" - case pressure - case humidity - case dewPoint = "dew_point" - case uvIndex = "uvi" - case cloudCoverage = "clouds" - case visibility - case windSpeed = "wind_speed" - case windGust = "wind_gust" - case windDirection = "wind_deg" - case weatherArray = "weather" - } - - // MARK: - Properties - + + // MARK: - Coding Keys + + enum CodingKeys: String, CodingKey { + case actualTemperature = "temp" + case feelsLikeTemperature = "feels_like" + case snow + case rain + case timestamp = "dt" + case pressure + case humidity + case dewPoint = "dew_point" + case uvIndex = "uvi" + case cloudCoverage = "clouds" + case visibility + case windSpeed = "wind_speed" + case windGust = "wind_gust" + case windDirection = "wind_deg" + case weatherArray = "weather" + } + + // MARK: - Properties + public let timestamp: Date public let pressure: Double? public let humidity: Double? @@ -33,32 +33,32 @@ public struct HourlyForecast: BasicWeatherResponse { public let cloudCoverage: Double? public let rain: Precipitation? public let snow: Precipitation? - - // Temperature - - private let actualTemperature: Double - private let feelsLikeTemperature: Double - - public var temperature: Temperature { - Temperature(actual: actualTemperature, feelsLike: feelsLikeTemperature) - } - - // Wind - - private let windSpeed: Double? - private let windGust: Double? - private let windDirection: Double? - - public var wind: Wind { - Wind(speed: windSpeed, gust: windGust, degrees: windDirection) - } - - // Weather - + + // Temperature + + private let actualTemperature: Double + private let feelsLikeTemperature: Double + + public var temperature: Temperature { + Temperature(actual: actualTemperature, feelsLike: feelsLikeTemperature) + } + + // Wind + + private let windSpeed: Double? + private let windGust: Double? + private let windDirection: Double? + + public var wind: Wind { + Wind(speed: windSpeed, gust: windGust, degrees: windDirection) + } + + // Weather + private let weatherArray: [WeatherCondition] - + public var weather: [WeatherCondition] { weatherArray } - + } diff --git a/Sources/HPOpenWeather/Models/Precipitation.swift b/Sources/HPOpenWeather/Models/Precipitation.swift index caf5c8f..fcdccc7 100644 --- a/Sources/HPOpenWeather/Models/Precipitation.swift +++ b/Sources/HPOpenWeather/Models/Precipitation.swift @@ -3,28 +3,30 @@ import Foundation /// Type that holds information about recent precipitation public struct Precipitation: Codable, Equatable, Hashable { - enum CodingKeys: String, CodingKey { - case lastHour = "1h" - case lastThreeHours = "3h" - } + enum CodingKeys: String, CodingKey { + case lastHour = "1h" + case lastThreeHours = "3h" + } /// Precipitation volume for the last 1 hour, measured in mm public var lastHour: Double? /// Precipitation volume for the last 3 hours, measured in mm public var lastThreeHours: Double? - public var lastHourMeasurement: Measurement? { - guard let lastHour = lastHour else { - return nil - } - return Measurement(value: lastHour, unit: .millimeters) - } + /// A convertible measurement of how much precipitation occured in the last hour if any + public var lastHourMeasurement: Measurement? { + guard let lastHour = lastHour else { + return nil + } + return Measurement(value: lastHour, unit: .millimeters) + } - public var lastThreeHoursMeasurement: Measurement? { - guard let lastThreeHours = lastThreeHours else { - return nil - } - return Measurement(value: lastThreeHours, unit: .millimeters) - } + /// A convertible measurement of how much precipitation occured in the last three hours if any + public var lastThreeHoursMeasurement: Measurement? { + guard let lastThreeHours = lastThreeHours else { + return nil + } + return Measurement(value: lastThreeHours, unit: .millimeters) + } } diff --git a/Sources/HPOpenWeather/Models/Sun.swift b/Sources/HPOpenWeather/Models/Sun.swift index dacf579..778dbfa 100644 --- a/Sources/HPOpenWeather/Models/Sun.swift +++ b/Sources/HPOpenWeather/Models/Sun.swift @@ -2,10 +2,10 @@ import Foundation /// Type that holds information about sunrise and sunset times in UTC time public struct Sun: Codable, Equatable, Hashable { - + /// Sunset time public let sunset: Date /// Sunrise time public let sunrise: Date - + } diff --git a/Sources/HPOpenWeather/Models/Temperature.swift b/Sources/HPOpenWeather/Models/Temperature.swift index 1da8435..f2bc8fd 100644 --- a/Sources/HPOpenWeather/Models/Temperature.swift +++ b/Sources/HPOpenWeather/Models/Temperature.swift @@ -3,25 +3,31 @@ import Foundation /// Type that holds information about daily temperature changes public struct Temperature: Codable, Equatable, Hashable { - /// The actually measured temperature + /// The actually measured temperature public let actual: Double - /// The feels-like temperature + /// The feels-like temperature public let feelsLike: Double - public var actualMeasurement: Measurement { - actualMeasurement(units: OpenWeather.shared.units) - } + /// A convertible measurement of the actually measured temperature + public var actualMeasurement: Measurement { + actualMeasurement(units: OpenWeather.shared.units) + } - public func actualMeasurement(units: WeatherResponse.Units) -> Measurement { - Measurement(value: actual, unit: units.temperatureUnit) - } + /// A convertible measurement of the actually measured temperature + /// - Parameter units: The units to use when formatting the `actual` property + public func actualMeasurement(units: WeatherResponse.Units) -> Measurement { + Measurement(value: actual, unit: units.temperatureUnit) + } - public var feelsLikeMeasurement: Measurement { - feelsLikeMeasurement(units: OpenWeather.shared.units) - } + /// A convertible measurement of how the actually measured temperature feels like + public var feelsLikeMeasurement: Measurement { + feelsLikeMeasurement(units: OpenWeather.shared.units) + } - public func feelsLikeMeasurement(units: WeatherResponse.Units) -> Measurement { - Measurement(value: feelsLike, unit: units.temperatureUnit) - } + /// A convertible measurement of how the actually measured temperature feels like + /// - Parameter units: The units to use when formatting the `feelsLike` property + public func feelsLikeMeasurement(units: WeatherResponse.Units) -> Measurement { + Measurement(value: feelsLike, unit: units.temperatureUnit) + } } diff --git a/Sources/HPOpenWeather/Models/WeatherAlert.swift b/Sources/HPOpenWeather/Models/WeatherAlert.swift index 40d6e75..7659d38 100644 --- a/Sources/HPOpenWeather/Models/WeatherAlert.swift +++ b/Sources/HPOpenWeather/Models/WeatherAlert.swift @@ -3,23 +3,23 @@ import Foundation /// Type that holds information about weather alerts public struct WeatherAlert: Codable, Hashable, Equatable { - /// Name of the alert source. Please read here the full list of alert sources - public let senderName: String - /// Alert event name - public let eventName: String - //// Date and time of the start of the alert - public let startDate: Date - //// Date and time of the end of the alert - public let endDate: Date - /// Description of the alert - public let description: String + /// Name of the alert source. Please read here the full list of alert sources + public let senderName: String + /// Alert event name + public let eventName: String + //// Date and time of the start of the alert + public let startDate: Date + //// Date and time of the end of the alert + public let endDate: Date + /// Description of the alert + public let description: String - enum CodingKeys: String, CodingKey { - case senderName = "sender_name" - case eventName = "event" - case startDate = "start" - case endDate = "end" - case description - } + enum CodingKeys: String, CodingKey { + case senderName = "sender_name" + case eventName = "event" + case startDate = "start" + case endDate = "end" + case description + } } diff --git a/Sources/HPOpenWeather/Models/WeatherCondition.swift b/Sources/HPOpenWeather/Models/WeatherCondition.swift index 8478364..7f95d66 100644 --- a/Sources/HPOpenWeather/Models/WeatherCondition.swift +++ b/Sources/HPOpenWeather/Models/WeatherCondition.swift @@ -10,6 +10,6 @@ public struct WeatherCondition: Codable, Equatable, Hashable { /// The weather condition within the group public let description: String /// The ID of the corresponding weather icon - public let icon: WeatherIcon + public let icon: WeatherIcon } diff --git a/Sources/HPOpenWeather/Models/WeatherIcon.swift b/Sources/HPOpenWeather/Models/WeatherIcon.swift index c327a75..bbb1a14 100644 --- a/Sources/HPOpenWeather/Models/WeatherIcon.swift +++ b/Sources/HPOpenWeather/Models/WeatherIcon.swift @@ -8,89 +8,88 @@ import Foundation public enum WeatherIcon: String, Codable, CaseIterable { case clearSky = "01d" - case clearSkyNight = "01n" + case clearSkyNight = "01n" case fewClouds = "02d" - case fewCloudsNight = "02n" - case scatteredClouds = "03d" - case scatteredCloudsNight = "03n" - case brokenClouds = "04d" - case brokenCloudsNight = "04n" - case showerRain = "09d" - case showerRainNight = "09n" - case rain = "10d" - case rainNight = "10n" - case thunderstorm = "11d" - case thunderstormNight = "11n" - case snow = "13d" - case snowNight = "13n" - case mist = "50d" - case mistNight = "50n" + case fewCloudsNight = "02n" + case scatteredClouds = "03d" + case scatteredCloudsNight = "03n" + case brokenClouds = "04d" + case brokenCloudsNight = "04n" + case showerRain = "09d" + case showerRainNight = "09n" + case rain = "10d" + case rainNight = "10n" + case thunderstorm = "11d" + case thunderstormNight = "11n" + case snow = "13d" + case snowNight = "13n" + case mist = "50d" + case mistNight = "50n" } @available(iOS 13.0, macOS 11.0, tvOS 13.0, watchOS 6.0, *) public extension WeatherIcon { - var systemImageName: String { - makeIconName(filled: false) - } + var systemImageName: String { + makeIconName(filled: false) + } - var systemImageNameFilled: String { - makeIconName(filled: true) - } + var systemImageNameFilled: String { + makeIconName(filled: true) + } - #if canImport(UIKit) - - func filledUIImage(withConfiguration configuration: UIImage.Configuration? = nil) -> UIImage? { - UIImage(systemName: systemImageNameFilled, withConfiguration: configuration) - } +#if canImport(UIKit) - func outlineUIImage(withConfiguration configuration: UIImage.Configuration? = nil) -> UIImage? { - UIImage(systemName: systemImageName, withConfiguration: configuration) - } + func filledUIImage(withConfiguration configuration: UIImage.Configuration? = nil) -> UIImage? { + UIImage(systemName: systemImageNameFilled, withConfiguration: configuration) + } - #elseif canImport(AppKit) + func outlineUIImage(withConfiguration configuration: UIImage.Configuration? = nil) -> UIImage? { + UIImage(systemName: systemImageName, withConfiguration: configuration) + } - func filledNSImage(accessibilityDescription: String? = nil) -> NSImage? { - NSImage(systemSymbolName: systemImageNameFilled, accessibilityDescription: accessibilityDescription) - } +#elseif canImport(AppKit) - func outlineNSImage(accessibilityDescription: String? = nil) -> NSImage? { - NSImage(systemSymbolName: systemImageName, accessibilityDescription: accessibilityDescription) - } + func filledNSImage(accessibilityDescription: String? = nil) -> NSImage? { + NSImage(systemSymbolName: systemImageNameFilled, accessibilityDescription: accessibilityDescription) + } - #endif + func outlineNSImage(accessibilityDescription: String? = nil) -> NSImage? { + NSImage(systemSymbolName: systemImageName, accessibilityDescription: accessibilityDescription) + } - private func makeIconName(filled: Bool) -> String { - let iconName: String - switch self { - case .clearSky: - iconName = "sun.max" - case .clearSkyNight: - iconName = "moon" - case .fewClouds: - iconName = "cloud.sun" - case .fewCloudsNight: - iconName = "cloud.moon" - case .scatteredClouds, .scatteredCloudsNight: - iconName = "cloud" - case .brokenClouds, .brokenCloudsNight: - iconName = "smoke" - case .showerRain, .showerRainNight: - iconName = "cloud.rain" - case .rain: - iconName = "cloud.sun.rain" - case .rainNight: - iconName = "cloud.moon.rain" - case .thunderstorm, .thunderstormNight: - iconName = "cloud.bolt.rain" - case .snow, .snowNight: - return "snow" - case .mist, .mistNight: - iconName = "cloud.fog" - } +#endif - return iconName + (filled ? ".fill" : "") - } + private func makeIconName(filled: Bool) -> String { + let iconName: String + switch self { + case .clearSky: + iconName = "sun.max" + case .clearSkyNight: + iconName = "moon" + case .fewClouds: + iconName = "cloud.sun" + case .fewCloudsNight: + iconName = "cloud.moon" + case .scatteredClouds, .scatteredCloudsNight: + iconName = "cloud" + case .brokenClouds, .brokenCloudsNight: + iconName = "smoke" + case .showerRain, .showerRainNight: + iconName = "cloud.rain" + case .rain: + iconName = "cloud.sun.rain" + case .rainNight: + iconName = "cloud.moon.rain" + case .thunderstorm, .thunderstormNight: + iconName = "cloud.bolt.rain" + case .snow, .snowNight: + return "snow" + case .mist, .mistNight: + iconName = "cloud.fog" + } + return iconName + (filled ? ".fill" : "") + } } diff --git a/Sources/HPOpenWeather/Models/Wind.swift b/Sources/HPOpenWeather/Models/Wind.swift index 09bf137..414e32d 100644 --- a/Sources/HPOpenWeather/Models/Wind.swift +++ b/Sources/HPOpenWeather/Models/Wind.swift @@ -9,19 +9,19 @@ public struct Wind: Codable, Equatable, Hashable { public let gust: Double? /// The wind direction measured in degrees from North public let degrees: Double? + + /// A measurement of the `speed` property if existing, measured in the units currently specified in `OpenWeather.shared` + public var speedMeasurement: Measurement? { + speedMeasurement(units: OpenWeather.shared.units) + } - /// A measurement of the `speed` property if existing, measured in the units currently specified in `OpenWeather.shared` - public var speedMeasurement: Measurement? { - speedMeasurement(units: OpenWeather.shared.units) - } - - /// A measurement of the `speed` property if existing, measured in the passed in units - /// - Parameter units: The units to use when formatting the `speed` property - public func speedMeasurement(units: WeatherResponse.Units) -> Measurement? { - guard let speed = speed else { - return nil - } - return Measurement(value: speed, unit: units.windSpeedUnit) - } + /// A measurement of the `speed` property if existing, measured in the passed in units + /// - Parameter units: The units to use when formatting the `speed` property + public func speedMeasurement(units: WeatherResponse.Units) -> Measurement? { + guard let speed = speed else { + return nil + } + return Measurement(value: speed, unit: units.windSpeedUnit) + } } diff --git a/Sources/HPOpenWeather/OpenWeather.swift b/Sources/HPOpenWeather/OpenWeather.swift index 7d79414..3ab6e95 100644 --- a/Sources/HPOpenWeather/OpenWeather.swift +++ b/Sources/HPOpenWeather/OpenWeather.swift @@ -8,18 +8,18 @@ public final class OpenWeather { /// Type that can be used to configure all settings at once public struct Settings { - /// The API key to use for weather requests + /// The API key to use for weather requests let apiKey : String - /// The language that will be used in weather responses - let language: WeatherResponse.Language - /// The units that will be used in weather responses - let units: WeatherResponse.Units - - /// Initialises a new settings instance - /// - Parameters: - /// - apiKey: The API key to use for weather requests - /// - language: The language that will be used in weather responses - /// - units: The units that will be used in weather responses + /// The language that will be used in weather responses + let language: WeatherResponse.Language + /// The units that will be used in weather responses + let units: WeatherResponse.Units + + /// Initialises a new settings instance + /// - Parameters: + /// - apiKey: The API key to use for weather requests + /// - language: The language that will be used in weather responses + /// - units: The units that will be used in weather responses public init(apiKey: String, language: WeatherResponse.Language = .english, units: WeatherResponse.Units = .metric) { self.language = language self.units = units @@ -29,7 +29,7 @@ public final class OpenWeather { // MARK: - Properties - /// A shared instance of the weather client + /// A shared instance of the weather client public static let shared = OpenWeather() /// The OpenWeatherMap API key to authorize requests @@ -41,14 +41,14 @@ public final class OpenWeather { // MARK: - Init - /// Initialised a new instance of `OpenWeather` and applies the specified API key - /// - Parameter apiKey: the API key to authenticate with the OpenWeatherMap API + /// Initialised a new instance of `OpenWeather` and applies the specified API key + /// - Parameter apiKey: the API key to authenticate with the OpenWeatherMap API public init(apiKey: String? = nil) { self.apiKey = apiKey } - /// Initialised a new instance of `OpenWeather` and applies the specified settimgs - /// - Parameter settings: the settings to apply, including API key, language and units + /// Initialised a new instance of `OpenWeather` and applies the specified settimgs + /// - Parameter settings: the settings to apply, including API key, language and units public init(settings: Settings) { self.apiKey = settings.apiKey self.language = settings.language @@ -57,47 +57,72 @@ public final class OpenWeather { // MARK: - Sending Requests - public func requestWeather( - coordinate: CLLocationCoordinate2D, - excludedFields: [WeatherRequest.ExcludableField]? = nil, - date: Date? = nil, - urlSession: URLSession = .shared - ) async throws -> WeatherResponse { - let request = WeatherRequest( - coordinate: coordinate, - excludedFields: excludedFields, - date: date - ) - return try await response(request, urlSession: urlSession) - } - - /// Sends the specified request to the OpenWeather API - /// - Parameters: - /// - request: The request object that holds information about request location, date, etc. - /// - urlSession: The `URLSession` that will be used schedule requests - /// - Returns: A network task that can be used to cancel the request - public func response(_ request: WeatherRequest, urlSession: URLSession = .shared) async throws -> WeatherRequest.Output { + /// Sends the specified request to the OpenWeather API + /// - Parameters: + /// - coordinate: The coordinate for which the weather will be requested + /// - excludedFields: An array specifying the fields that will be excluded from the response + /// - date: The date for which you want to request the weather. If no date is provided, the current weather will be retrieved + /// - urlSession: The `URLSession` that will be used schedule requests + /// - Returns: A weather response object + public func weatherResponse( + coordinate: CLLocationCoordinate2D, + excludedFields: [WeatherRequest.ExcludableField]? = nil, + date: Date? = nil, + urlSession: URLSession = .shared + ) async throws -> WeatherResponse { + let request = WeatherRequest( + coordinate: coordinate, + excludedFields: excludedFields, + date: date + ) + return try await weatherResponse(request, urlSession: urlSession) + } + + /// Sends the specified request to the OpenWeather API + /// - Parameters: + /// - request: The request object that holds information about request location, date, etc. + /// - urlSession: The `URLSession` that will be used schedule requests + /// - Returns: A weather response object + public func weatherResponse(_ request: WeatherRequest, urlSession: URLSession = .shared) async throws -> WeatherRequest.Output { guard let apiKey = apiKey else { - throw NSError.noApiKey + throw NSError.noApiKey } let settings = Settings(apiKey: apiKey, language: language, units: units) - let networkRequest = try request.makeNetworkRequest(settings: settings, urlSession: urlSession) - var response = try await networkRequest.response().output - response.units = settings.units - response.language = settings.language - return response + let networkRequest = try request.makeNetworkRequest(settings: settings, urlSession: urlSession) + var response = try await networkRequest.response().output + response.units = settings.units + response.language = settings.language + return response + } + + /// Sends the specified request to the OpenWeather API + /// - Parameters: + /// - request: The request object that holds information about request location, date, etc. + /// - urlSession: The `URLSession` that will be used schedule requests + /// - completion: A completion that will be called with the result of the network request + /// - Returns: A network task that can be used to cancel the request + @discardableResult + public func schedule(_ request: WeatherRequest, urlSession: URLSession = .shared, completion: @escaping (Result) -> Void) -> Task { + Task { + do { + let response = try await weatherResponse(request, urlSession: urlSession) + completion(.success(response)) + } catch { + completion(.failure(error)) + } + } } - // MARK: - Applying Settings + // MARK: - Applying Settings - /// Applies new settings to the weather client - /// - Parameter settings: The weather client settings, including an API key, language and units - public func apply(_ settings: Settings) { - apiKey = settings.apiKey - language = settings.language - units = settings.units - } + /// Applies new settings to the weather client + /// - Parameter settings: The weather client settings, including an API key, language and units + public func apply(_ settings: Settings) { + apiKey = settings.apiKey + language = settings.language + units = settings.units + } } diff --git a/Sources/HPOpenWeather/Requests/APINetworkRequest.swift b/Sources/HPOpenWeather/Requests/APINetworkRequest.swift index 532c48c..9e799ec 100644 --- a/Sources/HPOpenWeather/Requests/APINetworkRequest.swift +++ b/Sources/HPOpenWeather/Requests/APINetworkRequest.swift @@ -3,28 +3,28 @@ import HPNetwork struct APINetworkRequest: DecodableRequest { - typealias Output = WeatherResponse + typealias Output = WeatherResponse - static let decoder: JSONDecoder = { - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .secondsSince1970 - return decoder - }() + static let decoder: JSONDecoder = { + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .secondsSince1970 + return decoder + }() - let url: URL? - let urlSession: URLSession - let requestMethod: NetworkRequestMethod = .get - let headerFields = [NetworkRequestHeaderField.contentTypeJSON] + let url: URL? + let urlSession: URLSession + let requestMethod: NetworkRequestMethod = .get + let headerFields = [NetworkRequestHeaderField.contentTypeJSON] - var decoder: JSONDecoder { - APINetworkRequest.decoder - } + var decoder: JSONDecoder { + APINetworkRequest.decoder + } - public func makeURL() throws -> URL { - guard let url = url else { - throw NSError(code: 6, description: "Could not create URL") - } - return url - } + func makeURL() throws -> URL { + guard let url = url else { + throw NSError(code: 6, description: "Could not create URL") + } + return url + } } diff --git a/Sources/HPOpenWeather/Requests/WeatherRequest+Combine.swift b/Sources/HPOpenWeather/Requests/WeatherRequest+Combine.swift index 14f1273..d32b1f5 100644 --- a/Sources/HPOpenWeather/Requests/WeatherRequest+Combine.swift +++ b/Sources/HPOpenWeather/Requests/WeatherRequest+Combine.swift @@ -3,23 +3,23 @@ import Foundation public extension WeatherRequest { - func publisher( - apiKey: String, - language: WeatherResponse.Language = .english, - units: WeatherResponse.Units = .metric, - urlSession: URLSession = .shared, - finishingQueue: DispatchQueue = .main) -> AnyPublisher - { - publisher( - settings: OpenWeather.Settings(apiKey: apiKey, language: language, units: units), - urlSession: urlSession, - finishingQueue: finishingQueue - ) - } + func publisher( + apiKey: String, + language: WeatherResponse.Language = .english, + units: WeatherResponse.Units = .metric, + urlSession: URLSession = .shared, + finishingQueue: DispatchQueue = .main) -> AnyPublisher + { + publisher( + settings: OpenWeather.Settings(apiKey: apiKey, language: language, units: units), + urlSession: urlSession, + finishingQueue: finishingQueue + ) + } - func publisher(settings: OpenWeather.Settings, urlSession: URLSession = .shared, finishingQueue: DispatchQueue = .main) -> AnyPublisher { - let request = APINetworkRequest(url: makeURL(settings: settings), urlSession: urlSession) - return request.dataTaskPublisher() - } + func publisher(settings: OpenWeather.Settings, urlSession: URLSession = .shared, finishingQueue: DispatchQueue = .main) -> AnyPublisher { + let request = APINetworkRequest(url: makeURL(settings: settings), urlSession: urlSession) + return request.dataTaskPublisher() + } } diff --git a/Sources/HPOpenWeather/Requests/WeatherRequest+ExcludableField.swift b/Sources/HPOpenWeather/Requests/WeatherRequest+ExcludableField.swift index 9acaf75..bf70b80 100644 --- a/Sources/HPOpenWeather/Requests/WeatherRequest+ExcludableField.swift +++ b/Sources/HPOpenWeather/Requests/WeatherRequest+ExcludableField.swift @@ -2,12 +2,12 @@ import Foundation public extension WeatherRequest { - enum ExcludableField: String, Codable { - case current - case minutely - case hourly - case daily - case alerts - } + enum ExcludableField: String, Codable { + case current + case minutely + case hourly + case daily + case alerts + } } diff --git a/Sources/HPOpenWeather/Requests/WeatherRequest.swift b/Sources/HPOpenWeather/Requests/WeatherRequest.swift index cd010d9..8123b16 100644 --- a/Sources/HPOpenWeather/Requests/WeatherRequest.swift +++ b/Sources/HPOpenWeather/Requests/WeatherRequest.swift @@ -5,64 +5,64 @@ import HPURLBuilder public struct WeatherRequest: Codable { - // MARK: - Associated Types - - public typealias Output = WeatherResponse - - // MARK: - Properties - - public let coordinate: CLLocationCoordinate2D - public let excludedFields: [ExcludableField]? - public let date: Date? - - // MARK: - Init - - public init(coordinate: CLLocationCoordinate2D, excludedFields: [ExcludableField]? = nil, date: Date? = nil) { - self.coordinate = coordinate - self.excludedFields = excludedFields?.hp_nilIfEmpty() - self.date = date - } - - // MARK: - OpenWeatherRequest - - func makeURL(settings: OpenWeather.Settings) -> URL? { - URL.build { - Host("api.openweathermap.org") - PathComponent("data") - PathComponent("2.5") - PathComponent("onecall") - PathComponent(date != nil ? "timemachine" : nil) - QueryItem(name: "lat", value: coordinate.latitude, digits: 5) - QueryItem(name: "lon", value: coordinate.longitude, digits: 5) - QueryItem(name: "dt", value: date.flatMap({ Int($0.timeIntervalSince1970) })) - QueryItem(name: "exclude", value: excludedFields?.compactMap({ $0.rawValue })) - QueryItem(name: "appid", value: settings.apiKey) - QueryItem(name: "units", value: settings.units.rawValue) - QueryItem(name: "lang", value: settings.language.rawValue) - } - } - - func makeNetworkRequest(settings: OpenWeather.Settings, urlSession: URLSession) throws -> APINetworkRequest { - if let date = date, date < Date(), abs(date.timeIntervalSinceNow) <= 6 * .hour { - throw NSError.timeMachineDate - } - return APINetworkRequest(url: makeURL(settings: settings), urlSession: urlSession) - } + // MARK: - Associated Types + + public typealias Output = WeatherResponse + + // MARK: - Properties + + public let coordinate: CLLocationCoordinate2D + public let excludedFields: [ExcludableField]? + public let date: Date? + + // MARK: - Init + + public init(coordinate: CLLocationCoordinate2D, excludedFields: [ExcludableField]? = nil, date: Date? = nil) { + self.coordinate = coordinate + self.excludedFields = excludedFields?.hp_nilIfEmpty() + self.date = date + } + + // MARK: - OpenWeatherRequest + + func makeURL(settings: OpenWeather.Settings) -> URL? { + URL.build { + Host("api.openweathermap.org") + PathComponent("data") + PathComponent("2.5") + PathComponent("onecall") + PathComponent(date != nil ? "timemachine" : nil) + QueryItem(name: "lat", value: coordinate.latitude, digits: 5) + QueryItem(name: "lon", value: coordinate.longitude, digits: 5) + QueryItem(name: "dt", value: date.flatMap({ Int($0.timeIntervalSince1970) })) + QueryItem(name: "exclude", value: excludedFields?.compactMap({ $0.rawValue })) + QueryItem(name: "appid", value: settings.apiKey) + QueryItem(name: "units", value: settings.units.rawValue) + QueryItem(name: "lang", value: settings.language.rawValue) + } + } + + func makeNetworkRequest(settings: OpenWeather.Settings, urlSession: URLSession) throws -> APINetworkRequest { + if let date = date, date < Date(), abs(date.timeIntervalSinceNow) <= 6 * .hour { + throw NSError.timeMachineDate + } + return APINetworkRequest(url: makeURL(settings: settings), urlSession: urlSession) + } } extension Collection { - func hp_nilIfEmpty() -> Self? { - isEmpty ? nil : self - } + func hp_nilIfEmpty() -> Self? { + isEmpty ? nil : self + } } extension TimeInterval { - static let minute = 60.00 - static let hour = 3600.00 - static let day = 86400.00 + static let minute = 60.00 + static let hour = 3600.00 + static let day = 86400.00 } diff --git a/Sources/HPOpenWeather/Response/WeatherResponse+Language.swift b/Sources/HPOpenWeather/Response/WeatherResponse+Language.swift index af7cb86..624b882 100644 --- a/Sources/HPOpenWeather/Response/WeatherResponse+Language.swift +++ b/Sources/HPOpenWeather/Response/WeatherResponse+Language.swift @@ -2,55 +2,53 @@ import Foundation public extension WeatherResponse { - /// The language that should be used in API responses for example for weather condition descriptions - enum Language: String, Codable { - - case afrikaans = "af" - case arabic = "ar" - case azerbaijani = "az" - case bulgarian = "bg" - case catalan = "ca" - case czech = "cz" - case danish = "da" - case german = "de" - case greek = "el" - case english = "en" - case basque = "eu" - case persian = "fa" - case finnish = "fi" - case french = "fr" - case galician = "gl" - case hebrew = "he" - case hindi = "hi" - case croatian = "hr" - case hungarian = "hu" - case indonesian = "id" - case italian = "it" - case japanese = "ja" - case korean = "kr" - case latvian = "la" - case lithuanian = "lt" - case macedonian = "mk" - case norwegian = "no" - case dutch = "nl" - case polish = "pl" - case portuguese = "pt" - case portugueseBrasil = "pt_br" - case romanian = "ro" - case russian = "ru" - case swedish = "sv" - case slovak = "sk" - case slovenian = "sl" - case spanish = "es" - case serbian = "sr" - case thai = "th" - case turkish = "tr" - case ukrainian = "ua" - case vietnamese = "vi" - case chineseSimplified = "zh_cn" - case chineseTraditional = "zh_tw" - case zulu = "zu" - - } + /// The language that should be used in API responses for example for weather condition descriptions + enum Language: String, Codable { + case afrikaans = "af" + case arabic = "ar" + case azerbaijani = "az" + case bulgarian = "bg" + case catalan = "ca" + case czech = "cz" + case danish = "da" + case german = "de" + case greek = "el" + case english = "en" + case basque = "eu" + case persian = "fa" + case finnish = "fi" + case french = "fr" + case galician = "gl" + case hebrew = "he" + case hindi = "hi" + case croatian = "hr" + case hungarian = "hu" + case indonesian = "id" + case italian = "it" + case japanese = "ja" + case korean = "kr" + case latvian = "la" + case lithuanian = "lt" + case macedonian = "mk" + case norwegian = "no" + case dutch = "nl" + case polish = "pl" + case portuguese = "pt" + case portugueseBrasil = "pt_br" + case romanian = "ro" + case russian = "ru" + case swedish = "sv" + case slovak = "sk" + case slovenian = "sl" + case spanish = "es" + case serbian = "sr" + case thai = "th" + case turkish = "tr" + case ukrainian = "ua" + case vietnamese = "vi" + case chineseSimplified = "zh_cn" + case chineseTraditional = "zh_tw" + case zulu = "zu" + } } diff --git a/Sources/HPOpenWeather/Response/WeatherResponse+Units.swift b/Sources/HPOpenWeather/Response/WeatherResponse+Units.swift index baa73dd..38c0fbf 100644 --- a/Sources/HPOpenWeather/Response/WeatherResponse+Units.swift +++ b/Sources/HPOpenWeather/Response/WeatherResponse+Units.swift @@ -2,40 +2,40 @@ import Foundation public extension WeatherResponse { - /// The units that should the data in the API responses should be formatted in - enum Units: String, Codable { + /// The units that should the data in the API responses should be formatted in + enum Units: String, Codable { - /// Temperature in Kelvin and wind speed in meter/sec - case standard - /// Temperature in Celsius and wind speed in meter/sec - case metric - /// Temperature in Fahrenheit and wind speed in miles/hour - case imperial + /// Temperature in Kelvin and wind speed in meter/sec + case standard + /// Temperature in Celsius and wind speed in meter/sec + case metric + /// Temperature in Fahrenheit and wind speed in miles/hour + case imperial - @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) - var temperatureUnit: UnitTemperature { - switch self { - case .standard: - return .kelvin - case .metric: - return .celsius - case .imperial: - return .fahrenheit - } - } + @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) + var temperatureUnit: UnitTemperature { + switch self { + case .standard: + return .kelvin + case .metric: + return .celsius + case .imperial: + return .fahrenheit + } + } - @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) - var windSpeedUnit: UnitSpeed { - switch self { - case .standard: - return .metersPerSecond - case .metric: - return .metersPerSecond - case .imperial: - return .milesPerHour - } - } + @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) + var windSpeedUnit: UnitSpeed { + switch self { + case .standard: + return .metersPerSecond + case .metric: + return .metersPerSecond + case .imperial: + return .milesPerHour + } + } - } + } } diff --git a/Sources/HPOpenWeather/Response/WeatherResponse.swift b/Sources/HPOpenWeather/Response/WeatherResponse.swift index dd880a0..3e1804e 100644 --- a/Sources/HPOpenWeather/Response/WeatherResponse.swift +++ b/Sources/HPOpenWeather/Response/WeatherResponse.swift @@ -1,35 +1,35 @@ import Foundation public struct WeatherResponse: Codable, Equatable, Hashable { - + private let timezoneIdentifier: String public let currentWeather: CurrentWeather? - public let hourlyForecasts: [HourlyForecast]? - public let dailyForecasts: [DailyForecast]? - /// Government weather alerts data from major national weather warning systems - public let alerts: [WeatherAlert]? - + public let hourlyForecasts: [HourlyForecast]? + public let dailyForecasts: [DailyForecast]? + /// Government weather alerts data from major national weather warning systems + public let alerts: [WeatherAlert]? + public internal(set) var language: WeatherResponse.Language? public internal(set) var units: WeatherResponse.Units? - - public var timezone: TimeZone? { - TimeZone(identifier: timezoneIdentifier) - } - + + public var timezone: TimeZone? { + TimeZone(identifier: timezoneIdentifier) + } + enum CodingKeys: String, CodingKey { case timezoneIdentifier = "timezone" case currentWeather = "current" case hourlyForecasts = "hourly" case dailyForecasts = "daily" - case alerts + case alerts } - + } public struct WeatherResponseContainer: Codable, Equatable, Hashable { - - public let response: WeatherResponse - public let language: WeatherResponse.Language - public let units: WeatherResponse.Units - + + public let response: WeatherResponse + public let language: WeatherResponse.Language + public let units: WeatherResponse.Units + } diff --git a/Tests/HPOpenWeatherTests/HPOpenWeatherTests.swift b/Tests/HPOpenWeatherTests/HPOpenWeatherTests.swift index 45b8c19..1414432 100644 --- a/Tests/HPOpenWeatherTests/HPOpenWeatherTests.swift +++ b/Tests/HPOpenWeatherTests/HPOpenWeatherTests.swift @@ -16,7 +16,7 @@ final class HPOpenWeatherTests: XCTestCase { func testCurrentRequest() async throws { do { - _ = try await OpenWeather.shared.requestWeather(coordinate: .init(latitude: 52.5200, longitude: 13.4050)) + _ = try await OpenWeather.shared.weatherResponse(coordinate: .init(latitude: 52.5200, longitude: 13.4050)) } catch let error as NSError { print(error) throw error @@ -27,7 +27,7 @@ final class HPOpenWeatherTests: XCTestCase { let request = WeatherRequest(coordinate: .init(latitude: 52.5200, longitude: 13.4050), date: Date().addingTimeInterval(-1 * .hour)) await HPAssertThrowsError { - try await OpenWeather.shared.response(request) + try await OpenWeather.shared.weatherResponse(request) } } @@ -35,12 +35,10 @@ final class HPOpenWeatherTests: XCTestCase { let request = WeatherRequest(coordinate: .init(latitude: 52.5200, longitude: 13.4050), date: Date().addingTimeInterval(-7 * .hour)) await HPAssertThrowsNoError { - try await OpenWeather.shared.response(request) + try await OpenWeather.shared.weatherResponse(request) } } - - @available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) func testPublisher() { let request = WeatherRequest(coordinate: .init(latitude: 52.5200, longitude: 13.4050)) From 2af665d393c42f002fdbbdd6d97a0dd4c2539d7e Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Sun, 27 Feb 2022 15:11:18 +0100 Subject: [PATCH 6/7] README updates --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 4b0e0bd..4dd798e 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ let timemachineRequest = WeatherRequest(coordinate: .init(latitude: 40, longitud To post a request, call `sendWeatherRequest` on `OpenWeather`: ```swift +// Classic completion handler approach OpenWeather.shared.schedule(request) { result in switch result { case .success(let response): @@ -65,6 +66,9 @@ OpenWeather.shared.schedule(request) { result in // handle error } } + +// Or using the new concurrency features +let response = try await OpenWeather.shared.weatherResponse(request) ``` ### Available languages (default in bold) From 02179f57718c871a5198de7f1ddafa51f8e567dc Mon Sep 17 00:00:00 2001 From: Henrik Panhans Date: Sun, 27 Feb 2022 21:43:16 +0100 Subject: [PATCH 7/7] Updating Github actions --- .github/workflows/swift.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index a8f4bf0..08e9469 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -23,4 +23,4 @@ jobs: env: API_KEY: ${{ secrets.API_KEY }} - name: Codecov - uses: codecov/codecov-action@v1.0.15 \ No newline at end of file + uses: codecov/codecov-action@v2 \ No newline at end of file