chore: initial domain and infrastructure layers
Signed-off-by: Alan Brault <alan.brault@visus.io>
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
import Foundation
|
||||
|
||||
public struct DailyForecast: Sendable, Hashable {
|
||||
public let dewPoint: Double
|
||||
public let humidity: Int
|
||||
public let sunrise: Date
|
||||
public let sunset: Date
|
||||
public let surfacePressure: Double
|
||||
public let temperature: Double
|
||||
public let uvIndex: Double
|
||||
public let visibility: Double
|
||||
public let weatherCode: WeatherCode
|
||||
public let windDirection: Int
|
||||
public let windGusts: Double
|
||||
public let windSpeed: Double
|
||||
|
||||
public init(dewPoint: Double,
|
||||
humidity: Int,
|
||||
sunrise: Date,
|
||||
sunset: Date,
|
||||
surfacePressure: Double,
|
||||
temperature: Double,
|
||||
uvIndex: Double,
|
||||
visibility: Double,
|
||||
weatherCode: WeatherCode,
|
||||
windDirection: Int,
|
||||
windGusts: Double,
|
||||
windSpeed: Double
|
||||
) {
|
||||
self.dewPoint = dewPoint
|
||||
self.humidity = humidity
|
||||
self.sunrise = sunrise
|
||||
self.sunset = sunset
|
||||
self.surfacePressure = surfacePressure
|
||||
self.temperature = temperature
|
||||
self.uvIndex = uvIndex
|
||||
self.visibility = visibility
|
||||
self.weatherCode = weatherCode
|
||||
self.windDirection = windDirection
|
||||
self.windGusts = windGusts
|
||||
self.windSpeed = windSpeed
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import Foundation
|
||||
|
||||
public struct Forecast: Sendable, Hashable {
|
||||
public let dewPoint: Double
|
||||
public let futureForecasts: [DateOnly: DailyForecast]
|
||||
public let humidity: Int
|
||||
public let latitude: Double
|
||||
public let longitude: Double
|
||||
public let sunrise: Date
|
||||
public let sunset: Date
|
||||
public let surfacePressure: Double
|
||||
public let temperature: Double
|
||||
public let timeZone: TimeZone
|
||||
public let uvIndex: Double
|
||||
public let visibility: Double
|
||||
public let weatherCode: WeatherCode
|
||||
public let windDirection: Int
|
||||
public let windGusts: Double
|
||||
public let windSpeed: Double
|
||||
|
||||
public init(dewPoint: Double,
|
||||
futureForecasts: [DateOnly: DailyForecast] = [:],
|
||||
humidity: Int,
|
||||
latitude: Double,
|
||||
longitude: Double,
|
||||
sunrise: Date,
|
||||
sunset: Date,
|
||||
surfacePressure: Double,
|
||||
temperature: Double,
|
||||
timeZone: TimeZone,
|
||||
uvIndex: Double,
|
||||
visibility: Double,
|
||||
weatherCode: WeatherCode,
|
||||
windDirection: Int,
|
||||
windGusts: Double,
|
||||
windSpeed: Double
|
||||
) {
|
||||
self.dewPoint = dewPoint
|
||||
self.futureForecasts = futureForecasts
|
||||
self.humidity = humidity
|
||||
self.latitude = latitude
|
||||
self.longitude = longitude
|
||||
self.sunrise = sunrise
|
||||
self.sunset = sunset
|
||||
self.surfacePressure = surfacePressure
|
||||
self.temperature = temperature
|
||||
self.timeZone = timeZone
|
||||
self.uvIndex = uvIndex
|
||||
self.visibility = visibility
|
||||
self.weatherCode = weatherCode
|
||||
self.windDirection = windDirection
|
||||
self.windGusts = windGusts
|
||||
self.windSpeed = windSpeed
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import Foundation
|
||||
|
||||
public enum WeatherCode: Sendable, Hashable, Decodable {
|
||||
case clearSky
|
||||
case mainlyClear
|
||||
case partlyCloudy
|
||||
case overcast
|
||||
case fog
|
||||
case drizzle
|
||||
case rain
|
||||
case freezingRain
|
||||
case rainShowers
|
||||
case snow
|
||||
case snowShowers
|
||||
case thunderstorm
|
||||
case thunderstormWithHail
|
||||
case unknown(code: Int)
|
||||
|
||||
public static func from(code: Int) -> WeatherCode {
|
||||
switch code {
|
||||
case 0: .clearSky
|
||||
case 1: .mainlyClear
|
||||
case 2: .partlyCloudy
|
||||
case 3: .overcast
|
||||
case 45, 48: .fog
|
||||
case 51, 53, 55, 56, 57: .drizzle
|
||||
case 61, 63, 65: .rain
|
||||
case 66, 67: .freezingRain
|
||||
case 71, 73, 75, 755: .snow
|
||||
case 85, 86: .snowShowers
|
||||
case 95: .thunderstorm
|
||||
case 96, 99: .thunderstormWithHail
|
||||
default: .unknown(code: code)
|
||||
}
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
let code = try container.decode(Int.self)
|
||||
self = .from(code: code)
|
||||
}
|
||||
}
|
||||
|
||||
public extension WeatherCode {
|
||||
var code: Int {
|
||||
switch self {
|
||||
case .clearSky: 0
|
||||
case .mainlyClear: 1
|
||||
case .partlyCloudy: 2
|
||||
case .overcast: 3
|
||||
case .fog: 45
|
||||
case .drizzle: 51
|
||||
case .rain: 61
|
||||
case .freezingRain: 66
|
||||
case .rainShowers: 80
|
||||
case .snow: 71
|
||||
case .snowShowers: 85
|
||||
case .thunderstorm: 95
|
||||
case .thunderstormWithHail: 96
|
||||
case .unknown(let code): code
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
public protocol ForecastRepository: Sendable {
|
||||
func get(latitude: Double, longitude: Double, forceRefresh: Bool) async throws -> Forecast?
|
||||
}
|
||||
|
||||
public extension ForecastRepository {
|
||||
func get(latitude: Double, longitude: Double, forceRefresh: Bool = false) async throws -> Forecast? {
|
||||
try await get(latitude: latitude, longitude: longitude, forceRefresh: forceRefresh)
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
// The Swift Programming Language
|
||||
// https://docs.swift.org/swift-book
|
||||
@@ -0,0 +1,47 @@
|
||||
import Foundation
|
||||
|
||||
public struct DateOnly: Sendable, Hashable, Comparable, Codable {
|
||||
public let year: Int
|
||||
public let month: Int
|
||||
public let day: Int
|
||||
|
||||
public init(year: Int, month: Int, day: Int) {
|
||||
self.year = year
|
||||
self.month = month
|
||||
self.day = day
|
||||
}
|
||||
|
||||
public init(date: Date, timeZone: TimeZone) {
|
||||
var calendar = Calendar(identifier: .gregorian)
|
||||
calendar.timeZone = timeZone
|
||||
|
||||
let components = calendar.dateComponents([.year, .month, .day], from: date)
|
||||
self.init(year: components.year!, month: components.month!, day: components.day!)
|
||||
}
|
||||
|
||||
public static func < (lhs: DateOnly, rhs: DateOnly) -> Bool {
|
||||
(lhs.year, lhs.month, lhs.day) < (rhs.year, rhs.month, rhs.day)
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
let string = try container.decode(String.self)
|
||||
let components = string.split(separator: "-")
|
||||
|
||||
guard components.count == 3,
|
||||
let year = Int(components[0]),
|
||||
let month = Int(components[1]),
|
||||
let day = Int(components[2]) else {
|
||||
throw DecodingError.dataCorruptedError(
|
||||
in: container,
|
||||
debugDescription: "Invalid date format: \(string)")
|
||||
}
|
||||
|
||||
self.init(year: year, month: month, day: day)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
try container.encode(String(format: "%04d-%02d-%02d", year, month, day))
|
||||
}
|
||||
}
|
||||
@@ -6,3 +6,11 @@ import Testing
|
||||
// Swift Testing Documentation
|
||||
// https://developer.apple.com/documentation/testing
|
||||
}
|
||||
|
||||
@Test func weatherCodeRoundTripsKnownCode() async throws {
|
||||
#expect(WeatherCode.from(code: 61).code == 61)
|
||||
}
|
||||
|
||||
@Test func weatherCodePreservesUnknownCode() async throws {
|
||||
#expect(WeatherCode.unknown(code: 12345).code == 12345)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user