diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml
index 2cb9a5c..cfc9bac 100644
--- a/.github/workflows/documentation.yml
+++ b/.github/workflows/documentation.yml
@@ -1,14 +1,12 @@
-name: Pages Deploy
+name: Documentation
on:
push:
- branches: [ main ]
-
-# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
-permissions:
- contents: read
- pages: write
- id-token: write
+ branches: ["main"]
+ paths:
+ - "Sources/**/*.swift"
+ - "Tests/**/*.swift"
+ - "**/*.md"
# Allow one concurrent deployment
concurrency:
@@ -16,29 +14,35 @@ concurrency:
cancel-in-progress: true
jobs:
- # Single deploy job since we're just deploying
- deploy:
+ deploy-pages:
+ name: Deploy Documentation to GitHub Pages
+ runs-on: macos-14
environment:
- # Must be set to this for deploying to GitHub Pages
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
- runs-on: macos-12
+ permissions:
+ pages: write # to deploy to Pages
+ id-token: write # to verify the deployment originates from an appropriate source
steps:
- - name: Checkout 🛎️
- uses: actions/checkout@v3
+ - name: Configure Xcode
+ uses: maxim-lobanov/setup-xcode@v1
+ with:
+ xcode-version: latest-stable
+ - name: Checkout Code
+ uses: actions/checkout@v4
+ - name: Cache SPM dependencies
+ uses: actions/cache@v4
+ with:
+ path: .build
+ key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}
+ restore-keys: |
+ ${{ runner.os }}-spm-
- name: Build DocC
- run: |
- swift package --allow-writing-to-directory ./docs \
- generate-documentation --target HPOpenWeather \
- --transform-for-static-hosting \
- --hosting-base-path HPOpenWeather \
- --output-path ./docs
- echo "" > docs/index.html
+ run: Scripts/build-docc-archive HPOpenWeather
- name: Upload artifact
- uses: actions/upload-pages-artifact@v1
+ uses: actions/upload-pages-artifact@v3
with:
- # Upload only docs directory
- path: 'docs'
+ path: ${{ runner.temp }}/docs
- name: Deploy to GitHub Pages
id: deployment
- uses: actions/deploy-pages@v1
+ uses: actions/deploy-pages@v4
diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml
index 123af42..5df0065 100644
--- a/.github/workflows/swift.yml
+++ b/.github/workflows/swift.yml
@@ -2,25 +2,70 @@ name: Swift
on:
push:
- branches: [ main ]
+ branches: [main]
+ paths:
+ - "Sources/**/*.swift"
+ - "Tests/**/*.swift"
pull_request:
- branches: [ main ]
+ branches: [main]
+ paths:
+ - "Sources/**/*.swift"
+ - "Tests/**/*.swift"
jobs:
- build:
-
- runs-on: macos-latest
-
+ test-swift:
+ name: Test Swift Code
+ runs-on: macos-14
steps:
- - uses: maxim-lobanov/setup-xcode@v1
- with:
- xcode-version: latest-stable
- - uses: actions/checkout@v2
- - name: Build
- run: swift build -v
- - name: Run tests
- run: swift test -v
- env:
+ - name: Configure Xcode
+ uses: maxim-lobanov/setup-xcode@v1
+ with:
+ xcode-version: latest-stable
+ - name: Checkout Code
+ uses: actions/checkout@v4
+ - name: Cache SPM dependencies
+ uses: actions/cache@v4
+ with:
+ path: .build
+ key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}
+ restore-keys: |
+ ${{ runner.os }}-spm-
+ - name: Build
+ run: swift build -v
+ - name: Run tests
+ run: swift test --enable-code-coverage -v
+ env:
API_KEY: ${{ secrets.API_KEY }}
- - name: Codecov
- uses: codecov/codecov-action@v2
\ No newline at end of file
+ - name: Convert coverage report
+ continue-on-error: true
+ run: Scripts/convert-coverage-report --target HPOpenWeatherPackageTests
+ - name: Upload coverage reports to Codecov
+ continue-on-error: true
+ uses: codecov/codecov-action@v4
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+ slug: henrik-dmg/HPOpenWeather
+
+ lint-code:
+ name: Lint Swift Code
+ runs-on: macos-14
+ steps:
+ - name: Configure Xcode
+ uses: maxim-lobanov/setup-xcode@v1
+ with:
+ xcode-version: latest-stable
+ - name: Checkout Code
+ uses: actions/checkout@v4
+ - name: Cache SPM dependencies
+ uses: actions/cache@v4
+ with:
+ path: .build
+ key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}
+ restore-keys: |
+ ${{ runner.os }}-spm-
+ - name: Install SwiftLint
+ run: brew install swift-format peripheryapp/periphery/periphery
+ - name: Lint code
+ run: Scripts/lint-swift-code
+ - name: Scan for dead code
+ run: periphery scan --strict --config config/periphery.yml
diff --git a/.gitignore b/.gitignore
index c4c0743..ed8d367 100644
--- a/.gitignore
+++ b/.gitignore
@@ -78,3 +78,7 @@ fastlane/test_output
iOSInjectionProject/
.swiftpm
settings.json
+/docs
+/coverage
+
+**/.DS_Store
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
deleted file mode 100644
index b7e1bb4..0000000
--- a/.vscode/launch.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "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/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 77b84c7..e24371c 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -14,22 +14,22 @@ appearance, race, religion, or sexual identity and orientation.
Examples of behavior that contributes to creating a positive environment
include:
-* Using welcoming and inclusive language
-* Being respectful of differing viewpoints and experiences
-* Gracefully accepting constructive criticism
-* Focusing on what is best for the community
-* Showing empathy towards other community members
+- Using welcoming and inclusive language
+- Being respectful of differing viewpoints and experiences
+- Gracefully accepting constructive criticism
+- Focusing on what is best for the community
+- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
-* The use of sexualized language or imagery and unwelcome sexual attention or
- advances
-* Trolling, insulting/derogatory comments, and personal or political attacks
-* Public or private harassment
-* Publishing others' private information, such as a physical or electronic
- address, without explicit permission
-* Other conduct which could reasonably be considered inappropriate in a
- professional setting
+- The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+- Trolling, insulting/derogatory comments, and personal or political attacks
+- Public or private harassment
+- Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+- Other conduct which could reasonably be considered inappropriate in a
+ professional setting
## Our Responsibilities
diff --git a/HPOpenWeather.podspec b/HPOpenWeather.podspec
deleted file mode 100644
index e1044ce..0000000
--- a/HPOpenWeather.podspec
+++ /dev/null
@@ -1,32 +0,0 @@
-Pod::Spec.new do |s|
-
- s.name = "HPOpenWeather"
- s.version = "5.0.0"
- s.summary = "Cross-platform framework to communicate with the OpenWeatherMap JSON API"
-
- s.license = { :type => "MIT", :file => "LICENSE.md" }
- s.homepage = "https://panhans.dev/opensource/hpopenweather"
-
- s.author = { "henrik-dmg" => "henrik@panhans.dev" }
- s.social_media_url = "https://twitter.com/henrik_dmg"
-
- 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.swift_version = "5.5"
- s.requires_arc = true
- s.dependency "HPNetwork", "~> 3.1.1"
- s.dependency "HPURLBuilder", "~> 1.0.0"
-
-end
diff --git a/Package.resolved b/Package.resolved
index 0ca497e..fa54949 100644
--- a/Package.resolved
+++ b/Package.resolved
@@ -1,88 +1,32 @@
{
- "object": {
- "pins": [
- {
- "package": "HPNetwork",
- "repositoryURL": "https://github.com/henrik-dmg/HPNetwork",
- "state": {
- "branch": null,
- "revision": "fba08b668713bad984ad4d76bf99b55e9192fcce",
- "version": "3.1.2"
- }
- },
- {
- "package": "HPURLBuilder",
- "repositoryURL": "https://github.com/henrik-dmg/HPURLBuilder",
- "state": {
- "branch": null,
- "revision": "49ad1fb6f10914e7134dc9f8e5c21f48a52e3d37",
- "version": "1.1.0"
- }
- },
- {
- "package": "swift-argument-parser",
- "repositoryURL": "https://github.com/apple/swift-argument-parser.git",
- "state": {
- "branch": null,
- "revision": "9f39744e025c7d377987f30b03770805dcb0bcd1",
- "version": "1.1.4"
- }
- },
- {
- "package": "SwiftDocCPlugin",
- "repositoryURL": "https://github.com/apple/swift-docc-plugin",
- "state": {
- "branch": "main",
- "revision": "a3217706ad049ca058743003e065767773cc56cc",
- "version": null
- }
- },
- {
- "package": "SymbolKit",
- "repositoryURL": "https://github.com/apple/swift-docc-symbolkit",
- "state": {
- "branch": "main",
- "revision": "b45d1f2ed151d057b54504d653e0da5552844e34",
- "version": null
- }
- },
- {
- "package": "swift-format",
- "repositoryURL": "https://github.com/apple/swift-format",
- "state": {
- "branch": "main",
- "revision": "e5875f32d37d0de760bd4ca3b988f42373866f96",
- "version": null
- }
- },
- {
- "package": "SwiftSyntax",
- "repositoryURL": "https://github.com/apple/swift-syntax.git",
- "state": {
- "branch": "main",
- "revision": "1e61cc3bd13c0f61d75e509994e00e64fecf8bf3",
- "version": null
- }
- },
- {
- "package": "swift-system",
- "repositoryURL": "https://github.com/apple/swift-system.git",
- "state": {
- "branch": null,
- "revision": "836bc4557b74fe6d2660218d56e3ce96aff76574",
- "version": "1.1.1"
- }
- },
- {
- "package": "swift-tools-support-core",
- "repositoryURL": "https://github.com/apple/swift-tools-support-core.git",
- "state": {
- "branch": null,
- "revision": "284a41800b7c5565512ec6ae21ee818aac1f84ac",
- "version": "0.4.0"
- }
+ "pins" : [
+ {
+ "identity" : "hpnetwork",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/henrik-dmg/HPNetwork",
+ "state" : {
+ "revision" : "a4629039573f695f97fc044032c4e654b40671b0",
+ "version" : "4.0.1"
}
- ]
- },
- "version": 1
+ },
+ {
+ "identity" : "hpurlbuilder",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/henrik-dmg/HPURLBuilder",
+ "state" : {
+ "revision" : "49ad1fb6f10914e7134dc9f8e5c21f48a52e3d37",
+ "version" : "1.1.0"
+ }
+ },
+ {
+ "identity" : "swift-http-types",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/apple/swift-http-types.git",
+ "state" : {
+ "revision" : "ae67c8178eb46944fd85e4dc6dd970e1f3ed6ccd",
+ "version" : "1.3.0"
+ }
+ }
+ ],
+ "version" : 2
}
diff --git a/Package.swift b/Package.swift
index f223b28..65a130e 100644
--- a/Package.swift
+++ b/Package.swift
@@ -1,4 +1,4 @@
-// swift-tools-version:5.5
+// swift-tools-version:5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
@@ -6,32 +6,37 @@ import PackageDescription
let package = Package(
name: "HPOpenWeather",
platforms: [
- .iOS(.v13), .tvOS(.v13), .watchOS(.v7), .macOS(.v10_15)
+ .iOS(.v15), .tvOS(.v15), .watchOS(.v6), .macOS(.v12),
],
products: [
// Products define the executables and libraries produced by a package, and make them visible to other packages.
.library(
name: "HPOpenWeather",
targets: ["HPOpenWeather"]
- )
+ )
],
dependencies: [
// Dependencies declare other packages that this package depends on.
- .package(url: "https://github.com/henrik-dmg/HPNetwork", from: "3.0.0"),
- .package(url: "https://github.com/henrik-dmg/HPURLBuilder", from: "1.0.0"),
- .package(url: "https://github.com/apple/swift-docc-plugin", branch: "main"),
- .package(url: "https://github.com/apple/swift-format", branch: "main")
+ .package(url: "https://github.com/henrik-dmg/HPNetwork", from: "4.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", "HPURLBuilder"]
- ),
+ dependencies: [
+ .product(name: "HPNetworkMock", package: "HPNetwork"),
+ "HPURLBuilder",
+ ]
+ ),
.testTarget(
name: "HPOpenWeatherTests",
- dependencies: ["HPOpenWeather"]
- )
+ dependencies: [
+ "HPOpenWeather",
+ .product(name: "HPNetworkMock", package: "HPNetwork"),
+ ],
+ resources: [.process("Resources")]
+ ),
]
)
diff --git a/README.md b/README.md
index 4dd798e..14d5f69 100644
--- a/README.md
+++ b/README.md
@@ -1,100 +1,20 @@
# HPOpenWeather
-
-
-
+[![codecov](https://codecov.io/gh/henrik-dmg/HPOpenWeather/graph/badge.svg?token=mX05cXr144)](https://codecov.io/gh/henrik-dmg/HPOpenWeather)
+[![Swift](https://github.com/henrik-dmg/HPOpenWeather/actions/workflows/swift.yml/badge.svg)](https://github.com/henrik-dmg/HPOpenWeather/actions/workflows/swift.yml)
[![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 13.0+, watchOS 7.0+, tvOS 13.0+ and macOS 10.15+.
+`HPOpenWeather` supports iOS 15.0+, watchOS 6.0+, tvOS 15.0+ and macOS 12+.
+It can be installed via SPM:
-### SPM
-
-Add `.package(url: "https://github.com/henrik-dmg/HPOpenWeather", from: "5.0.0")` to your `Package.swift` file
-
-### 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
-
-// Assign API key
-OpenWeather.shared.apiKey = "--- YOUR API KEY ---"
-OpenWeather.shared.language = .german
-OpenWeather.shared.units = .metric
-
-// Or use options
-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
-
-To make a request, initialize a new request object like this
-
-```swift
-let request = WeatherRequest(coordinate: .init(latitude: 40, longitude: 30))
```
-
-Or to request weather data from the past:
-
-```swift
-let timemachineRequest = WeatherRequest(coordinate: .init(latitude: 40, longitude: 30), date: someDate)
+.package(url: "https://github.com/henrik-dmg/HPOpenWeather", from: "6.0.0")
```
-**Note:** the date has to be at least 6 hours in the past
-
-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):
- // do something with weather data here
- case .failure(let error):
- // handle error
- }
-}
-
-// Or using the new concurrency features
-let response = try await OpenWeather.shared.weatherResponse(request)
-```
-
-### Available languages (default in bold)
-
-- **English**
-- Russian
-- Italian
-- Spanish
-- Ukrainian
-- German
-- Portuguese
-- Romanian
-- Polish
-- Finnish
-- Dutch
-- French
-- Bulgarian
-- Swedish
-- Chinese Traditional
-- Chinese Simplified
-- Turkish
-- Croatian
-- Catalan
-
-### Available units (default in bold)
+## Documentation
-- **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)
+The documentation has been moved to https://henrik-dmg.github.io/HPOpenWeather, which is deployed from the DocC catalog.
diff --git a/Scripts/build-docc-archive b/Scripts/build-docc-archive
new file mode 100755
index 0000000..1ef153a
--- /dev/null
+++ b/Scripts/build-docc-archive
@@ -0,0 +1,39 @@
+#!/usr/bin/env bash
+
+if [[ -z $RUNNER_TEMP ]]; then
+ echo "RUNNER_TEMP is not set. Setting to root of repository."
+ RUNNER_TEMP=$(git rev-parse --show-toplevel)
+fi
+
+TARGET=$1
+HOSTING_BASE_PATH=$2
+
+if [[ -z $HOSTING_BASE_PATH ]]; then
+ HOSTING_BASE_PATH=$TARGET
+fi
+
+# First, insert docc-plugin dependency. This is very hacky, but it avoids everyone having to pull in the docc-plugin when they use this library.
+
+sed '/Dependencies declare/a\
+.package(url: "https://github.com/apple/swift-docc-plugin", branch: "main"),\
+' "Package.swift" > "Package.tmp"
+
+mv "Package.tmp" "Package.swift"
+
+swift package resolve
+
+swift package \
+ --allow-writing-to-directory "$RUNNER_TEMP/docs" \
+ generate-documentation \
+ --target "$TARGET" \
+ --transform-for-static-hosting \
+ --hosting-base-path "$HOSTING_BASE_PATH" \
+ --output-path "$RUNNER_TEMP/docs"
+
+CUSTOM_PATH=$(echo $HOSTING_BASE_PATH | tr '[:upper:]' '[:lower:]')
+echo "" > "$RUNNER_TEMP/docs/index.html"
+
+if [[ -z $GITHUB_ACTIONS ]]; then
+ echo "Restoring Package.swift to original state."
+ sed -i '' '/swift-docc-plugin/d' "Package.swift"
+fi
\ No newline at end of file
diff --git a/Scripts/configure-hooks b/Scripts/configure-hooks
new file mode 100755
index 0000000..a83bfc4
--- /dev/null
+++ b/Scripts/configure-hooks
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+
+GIT_ROOT=$(git rev-parse --show-toplevel)
+
+# Check if the pre-commit hook already exists
+if [ -f "$GIT_ROOT/.git/hooks/pre-commit" ]; then
+ rm "$GIT_ROOT/.git/hooks/pre-commit"
+fi
+
+ln -s "$GIT_ROOT/Scripts/lint-swift-code" "$GIT_ROOT/.git/hooks/pre-commit"
\ No newline at end of file
diff --git a/Scripts/convert-coverage-report b/Scripts/convert-coverage-report
new file mode 100755
index 0000000..676c41c
--- /dev/null
+++ b/Scripts/convert-coverage-report
@@ -0,0 +1,54 @@
+#!/usr/bin/env bash
+
+# Adapted from https://github.com/michaelhenry/swifty-code-coverage/blob/main/lcov.sh
+
+OUTPUT_FILE="coverage/lcov.info"
+IGNORE_FILENAME_REGEX=".build|Tests|Pods|Carthage|DerivedData"
+BUILD_PATH=".build"
+
+while :; do
+ case $1 in
+ --target) TARGET=$2
+ shift
+ ;;
+ --output) OUTPUT_FILE=$2
+ shift
+ ;;
+ *) break
+ esac
+ shift
+done
+
+if [ -z "$BUILD_PATH" ]; then
+ echo "Missing --build-path. Either DerivedData or .build (for spm)"
+ exit 1
+fi
+
+if [ -z "$TARGET" ]; then
+ echo "Missing --target. Either an .app or an .xctest (for spm)"
+ exit 1
+fi
+
+INSTR_PROFILE=$(find $BUILD_PATH -name "*.profdata")
+TARGET_PATH=$(find $BUILD_PATH -name "$TARGET" | head -1)
+if [ -f $TARGET_PATH ]; then
+ OBJECT_FILE="$TARGET_PATH"
+else
+ TARGET=$(echo $TARGET | sed 's/\.[^.]*$//')
+ OBJECT_FILE=$(find $BUILD_PATH -name "$TARGET" | head -1)
+fi
+
+mkdir -p $(dirname "$OUTPUT_FILE")
+
+# print to stdout
+xcrun llvm-cov report \
+ "$OBJECT_FILE" \
+ --instr-profile=$INSTR_PROFILE \
+ --ignore-filename-regex=$IGNORE_FILENAME_REGEX \
+
+# Export to code coverage file
+xcrun llvm-cov export \
+ "$OBJECT_FILE" \
+ --instr-profile=$INSTR_PROFILE \
+ --ignore-filename-regex=$IGNORE_FILENAME_REGEX \
+ --format="lcov" > $OUTPUT_FILE
\ No newline at end of file
diff --git a/Scripts/format-swift-code b/Scripts/format-swift-code
new file mode 100755
index 0000000..4e66902
--- /dev/null
+++ b/Scripts/format-swift-code
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+
+swift-format format \
+ --recursive \
+ --parallel \
+ --in-place \
+ --configuration config/swift-format.json \
+ Sources/ \
+ Tests/ \
+ Package.swift
\ No newline at end of file
diff --git a/Scripts/lint-swift-code b/Scripts/lint-swift-code
new file mode 100755
index 0000000..9af98be
--- /dev/null
+++ b/Scripts/lint-swift-code
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+
+swift-format lint \
+ --recursive \
+ --parallel \
+ --strict \
+ --configuration config/swift-format.json \
+ Sources/ \
+ Tests/ \
+ Package.swift
\ No newline at end of file
diff --git a/Sources/HPOpenWeather/Documentation.docc/Documentation.md b/Sources/HPOpenWeather/Documentation.docc/Documentation.md
new file mode 100644
index 0000000..891ca04
--- /dev/null
+++ b/Sources/HPOpenWeather/Documentation.docc/Documentation.md
@@ -0,0 +1,93 @@
+# ``HPOpenWeather``
+
+## Overview
+
+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 15.0+, watchOS 6.0+, tvOS 15.0+ and macOS 12+.
+It can be installed via SPM:
+
+```
+.package(url: "https://github.com/henrik-dmg/HPOpenWeather", from: "6.0.0")
+```
+
+## Usage
+
+### Configuration
+
+To get started, you need an API key from [OpenWeather](https://openweathermap.org). Then you can create an instance of the ``OpenWeather`` class.
+
+```swift
+import HPOpenWeather
+
+// Create instance
+let openWeatherClient = OpenWeather(apiKey: "")
+
+// Or use options
+let settings = OpenWeather.Settings(apiKey: "", language: .german, units: .metric)
+let openWeather = OpenWeather(settings: settings)
+
+// Change settings at any point
+openWeatherClient.apiKey = ""
+openWeatherClient.language = .german
+openWeatherClient.units = .metric
+```
+
+### Retrieving Weather Information
+
+To fetch the weather, there are two options: async/await or callback. Both expect a ``CLLocationCoordinate2D`` for which to fetch the weather.
+Additionally, you can specify which fields should be excluded from the response to save bandwidth, or specify a historic date or a date up to 4 days in the future.
+
+#### Async
+
+```swift
+let weather = try await openWeatherClient.weather(for: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194))
+```
+
+#### Callback
+
+```swift
+openWeatherClient.requestWeather(for: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194)) { result in
+ switch result {
+ case .success(let weather):
+ print(weather)
+ case .failure(let error):
+ print(error)
+ }
+}
+```
+
+### Available languages (default in bold)
+
+- **English**
+- Russian
+- Italian
+- Spanish
+- Ukrainian
+- German
+- Portuguese
+- Romanian
+- Polish
+- Finnish
+- Dutch
+- French
+- Bulgarian
+- Swedish
+- Chinese Traditional
+- Chinese Simplified
+- Turkish
+- Croatian
+- Catalan
+
+See ``WeatherLanguage`` for details.
+
+### Available units (default in bold)
+
+- **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)
+
+See ``WeatherUnits`` for details.
\ No newline at end of file
diff --git a/Sources/HPOpenWeather/Extensions/CLLocationCoordinate+Extensions.swift b/Sources/HPOpenWeather/Extensions/CLLocationCoordinate+Extensions.swift
index 1b4da93..b71f8d2 100644
--- a/Sources/HPOpenWeather/Extensions/CLLocationCoordinate+Extensions.swift
+++ b/Sources/HPOpenWeather/Extensions/CLLocationCoordinate+Extensions.swift
@@ -1,5 +1,5 @@
-import Foundation
import CoreLocation
+import Foundation
extension CLLocationCoordinate2D: Codable {
diff --git a/Sources/HPOpenWeather/Extensions/NSError+Extensions.swift b/Sources/HPOpenWeather/Extensions/NSError+Extensions.swift
deleted file mode 100644
index 3252af9..0000000
--- a/Sources/HPOpenWeather/Extensions/NSError+Extensions.swift
+++ /dev/null
@@ -1,17 +0,0 @@
-import Foundation
-import HPNetwork
-
-extension NSError {
-
- convenience init(domain: String = "com.henrikpanhans.HPOpenWeather", code: Int, description: String) {
- self.init(
- domain: domain,
- code: code,
- userInfo: [NSLocalizedDescriptionKey: description]
- )
- }
-
- static let noApiKey = NSError(code: 2, description: "API key was not provided")
- static let timeMachineDate = NSError(code: 3, description: "TimeMachineRequest's date has to be at least 6 hours in the past")
-
-}
diff --git a/Sources/HPOpenWeather/Models/City.swift b/Sources/HPOpenWeather/Models/City.swift
deleted file mode 100644
index 09c2570..0000000
--- a/Sources/HPOpenWeather/Models/City.swift
+++ /dev/null
@@ -1,23 +0,0 @@
-import Foundation
-import CoreLocation
-
-/// Type that holds information about the reqeuest's nearest city
-public struct City: Codable, Equatable, Hashable, Identifiable {
-
- /// The ID assigned to the city
- public let id: Int
- /// The name of the city
- public let name: String
- /// The location of the city
- public let location: CLLocationCoordinate2D
- /// The country code of the city
- public let countryCode: String
-
- enum CodingKeys: String, CodingKey {
- case id
- case name
- case location = "coord"
- case countryCode = "country"
- }
-
-}
diff --git a/Sources/HPOpenWeather/Models/DailyTemperature.swift b/Sources/HPOpenWeather/Models/DailyTemperature.swift
index fa9a51a..893e3e2 100644
--- a/Sources/HPOpenWeather/Models/DailyTemperature.swift
+++ b/Sources/HPOpenWeather/Models/DailyTemperature.swift
@@ -1,8 +1,21 @@
import Foundation
-/// Type that holds information about daily temperature changes
+/// Type that holds information about daily temperature changes.
public struct DailyTemperature: Codable, Equatable, Hashable {
+ // MARK: - Nested Types
+
+ enum CodingKeys: String, CodingKey {
+ case day
+ case night
+ case min
+ case max
+ case evening = "eve"
+ case morning = "morn"
+ }
+
+ // MARK: - Properties
+
/// Day temperature.
public let day: Double
/// Night temperature.
@@ -16,13 +29,4 @@ public struct DailyTemperature: Codable, Equatable, Hashable {
/// Morning temperature.
public let morning: Double
- enum CodingKeys: String, CodingKey {
- case day
- case night
- case min
- case max
- case evening = "eve"
- case morning = "morn"
- }
-
}
diff --git a/Sources/HPOpenWeather/Models/Forecasts/BasicWeather.swift b/Sources/HPOpenWeather/Models/Forecasts/BasicWeather.swift
deleted file mode 100644
index c6c5cf3..0000000
--- a/Sources/HPOpenWeather/Models/Forecasts/BasicWeather.swift
+++ /dev/null
@@ -1,30 +0,0 @@
-import Foundation
-
-public protocol BasicWeatherResponse: Codable, Hashable {
-
- /// The timestamp when the data was collected
- var timestamp: Date { get }
- /// Atmospheric pressure on the sea level, hPa
- var pressure: Double? { get }
- /// Humidity in percent
- var humidity: Double? { get }
- /// Atmospheric temperature (varying according to pressure and humidity) below which
- /// water droplets begin to condense and dew can form. Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit.
- var dewPoint: Double? { get }
- /// UV index
- var uvIndex: Double? { get }
- /// Average visibility
- var visibility: Double? { get }
- /// Cloudiness in percent
- var cloudCoverage: Double? { get }
- /// Basic information about observed wind
- var wind: Wind { get }
-
-}
-
-public protocol SunResponse: Codable, Hashable {
-
- /// A container that holds information about sunset and sunrise timestamps
- var sun: Sun { get }
-
-}
diff --git a/Sources/HPOpenWeather/Models/Forecasts/CurrentWeather.swift b/Sources/HPOpenWeather/Models/Forecasts/CurrentWeather.swift
index 3638d71..5cec335 100644
--- a/Sources/HPOpenWeather/Models/Forecasts/CurrentWeather.swift
+++ b/Sources/HPOpenWeather/Models/Forecasts/CurrentWeather.swift
@@ -1,9 +1,10 @@
import Foundation
-public struct CurrentWeather: BasicWeatherResponse, SunResponse {
-
+/// A type containing information about the current weather.
+public struct CurrentWeather: ForecastBase, SunForecast {
+
// MARK: - Coding Keys
-
+
enum CodingKeys: String, CodingKey {
case feelsLikeTemperature = "feels_like"
case snow
@@ -19,13 +20,13 @@ public struct CurrentWeather: BasicWeatherResponse, SunResponse {
case windSpeed = "wind_speed"
case windGust = "wind_gust"
case windDirection = "wind_deg"
- case weatherArray = "weather"
+ case weather
case sunrise
case sunset
}
-
+
// MARK: - Properties
-
+
public let timestamp: Date
public let pressure: Double?
public let humidity: Double?
@@ -35,41 +36,71 @@ 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
-
- 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)
+ public let wind: Wind
+ public let sun: Sun
+ public let currentCondition: WeatherCondition
+ public let temperature: Temperature
+
+ // MARK: - Init
+
+ public init(from decoder: any Decoder) throws {
+ let container = try decoder.container(keyedBy: CodingKeys.self)
+
+ let weatherArray = try container.decode([WeatherCondition].self, forKey: .weather)
+ guard let currentCondition = weatherArray.first else {
+ throw OpenWeatherError.noCurrentConditionReturned
+ }
+ self.currentCondition = currentCondition
+
+ self.snow = try container.decodeIfPresent(Precipitation.self, forKey: .snow)
+ self.rain = try container.decodeIfPresent(Precipitation.self, forKey: .rain)
+ self.timestamp = try container.decode(Date.self, forKey: .timestamp)
+ self.pressure = try container.decodeIfPresent(Double.self, forKey: .pressure)
+ self.humidity = try container.decodeIfPresent(Double.self, forKey: .humidity)
+ self.dewPoint = try container.decodeIfPresent(Double.self, forKey: .dewPoint)
+ self.uvIndex = try container.decodeIfPresent(Double.self, forKey: .uvIndex)
+ self.cloudCoverage = try container.decodeIfPresent(Double.self, forKey: .cloudCoverage)
+ self.visibility = try container.decodeIfPresent(Double.self, forKey: .visibility)
+
+ let actualTemperature = try container.decode(Double.self, forKey: .actualTemperature)
+ let feelsLikeTemperature = try container.decode(Double.self, forKey: .feelsLikeTemperature)
+ self.temperature = Temperature(actual: actualTemperature, feelsLike: feelsLikeTemperature)
+
+ let windSpeed = try container.decodeIfPresent(Double.self, forKey: .windSpeed)
+ let windGust = try container.decodeIfPresent(Double.self, forKey: .windGust)
+ let windDirection = try container.decodeIfPresent(Double.self, forKey: .windDirection)
+ self.wind = Wind(speed: windSpeed, gust: windGust, degrees: windDirection)
+
+ let sunrise = try container.decode(Date.self, forKey: .sunrise)
+ let sunset = try container.decode(Date.self, forKey: .sunset)
+ self.sun = Sun(sunset: sunset, sunrise: sunrise)
}
-
- // Sun
-
- private let sunrise: Date
- private let sunset: Date
-
- public var sun: Sun {
- Sun(sunset: sunset, sunrise: sunrise)
+
+ // MARK: - Encoding
+
+ public func encode(to encoder: any Encoder) throws {
+ var container = encoder.container(keyedBy: CodingKeys.self)
+
+ try container.encodeIfPresent(timestamp, forKey: .timestamp)
+ try container.encodeIfPresent(pressure, forKey: .pressure)
+ try container.encodeIfPresent(humidity, forKey: .humidity)
+ try container.encodeIfPresent(dewPoint, forKey: .dewPoint)
+ try container.encodeIfPresent(uvIndex, forKey: .uvIndex)
+ try container.encodeIfPresent(visibility, forKey: .visibility)
+ try container.encodeIfPresent(cloudCoverage, forKey: .cloudCoverage)
+ try container.encodeIfPresent(rain, forKey: .rain)
+ try container.encodeIfPresent(snow, forKey: .snow)
+ try container.encodeIfPresent([currentCondition], forKey: .weather)
+
+ try container.encodeIfPresent(temperature.actual, forKey: .actualTemperature)
+ try container.encodeIfPresent(temperature.feelsLike, forKey: .feelsLikeTemperature)
+
+ try container.encodeIfPresent(sun.sunrise, forKey: .sunrise)
+ try container.encodeIfPresent(sun.sunset, forKey: .sunset)
+
+ try container.encodeIfPresent(wind.gust, forKey: .windGust)
+ try container.encodeIfPresent(wind.speed, forKey: .windSpeed)
+ try container.encodeIfPresent(wind.degrees, forKey: .windDirection)
}
-
+
}
diff --git a/Sources/HPOpenWeather/Models/Forecasts/DailyForecast.swift b/Sources/HPOpenWeather/Models/Forecasts/DailyForecast.swift
index 1b9f063..7e77290 100644
--- a/Sources/HPOpenWeather/Models/Forecasts/DailyForecast.swift
+++ b/Sources/HPOpenWeather/Models/Forecasts/DailyForecast.swift
@@ -1,6 +1,6 @@
import Foundation
-public struct DailyForecast: BasicWeatherResponse, SunResponse {
+public struct DailyForecast: ForecastBase, SunForecast, MoonForecast {
// MARK: - Coding Keys
@@ -19,9 +19,11 @@ public struct DailyForecast: BasicWeatherResponse, SunResponse {
case windSpeed = "wind_speed"
case windGust = "wind_gust"
case windDirection = "wind_deg"
- case weatherArray = "weather"
+ case weather
case sunrise
case sunset
+ case moonrise
+ case moonset
}
// MARK: - Properties
@@ -38,33 +40,75 @@ public struct DailyForecast: BasicWeatherResponse, SunResponse {
public let uvIndex: Double?
public let visibility: Double?
public let cloudCoverage: Double?
-
- // 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)
+ public let condition: WeatherCondition
+ public let sun: Sun
+ public let wind: Wind
+ public let moon: Moon
+
+ // MARK: - Init
+
+ public init(from decoder: any Decoder) throws {
+ let container = try decoder.container(keyedBy: CodingKeys.self)
+
+ let weatherArray = try container.decode([WeatherCondition].self, forKey: .weather)
+ guard let condition = weatherArray.first else {
+ throw OpenWeatherError.noCurrentConditionReturned
+ }
+ self.condition = condition
+
+ self.timestamp = try container.decode(Date.self, forKey: .timestamp)
+ self.pressure = try container.decodeIfPresent(Double.self, forKey: .pressure)
+ self.humidity = try container.decodeIfPresent(Double.self, forKey: .humidity)
+ self.dewPoint = try container.decodeIfPresent(Double.self, forKey: .dewPoint)
+ self.uvIndex = try container.decodeIfPresent(Double.self, forKey: .uvIndex)
+ self.cloudCoverage = try container.decodeIfPresent(Double.self, forKey: .cloudCoverage)
+ self.visibility = try container.decodeIfPresent(Double.self, forKey: .visibility)
+ self.temperature = try container.decode(DailyTemperature.self, forKey: .temperature)
+ self.feelsLikeTemperature = try container.decode(DailyTemperature.self, forKey: .feelsLikeTemperature)
+ self.totalRain = try container.decodeIfPresent(Double.self, forKey: .totalRain)
+ self.totalSnow = try container.decodeIfPresent(Double.self, forKey: .totalSnow)
+
+ let windSpeed = try container.decodeIfPresent(Double.self, forKey: .windSpeed)
+ let windGust = try container.decodeIfPresent(Double.self, forKey: .windGust)
+ let windDirection = try container.decodeIfPresent(Double.self, forKey: .windDirection)
+ self.wind = Wind(speed: windSpeed, gust: windGust, degrees: windDirection)
+
+ let sunrise = try container.decode(Date.self, forKey: .sunrise)
+ let sunset = try container.decode(Date.self, forKey: .sunset)
+ self.sun = Sun(sunset: sunset, sunrise: sunrise)
+
+ let moonrise = try container.decode(Date.self, forKey: .moonrise)
+ let moonset = try container.decode(Date.self, forKey: .moonset)
+ self.moon = Moon(moonset: moonset, moonrise: moonrise)
}
- // Sun
-
- private let sunrise: Date
- private let sunset: Date
-
- public var sun: Sun {
- Sun(sunset: sunset, sunrise: sunrise)
+ // MARK: - Encoding
+
+ public func encode(to encoder: any Encoder) throws {
+ var container = encoder.container(keyedBy: CodingKeys.self)
+
+ try container.encodeIfPresent(temperature, forKey: .temperature)
+ try container.encodeIfPresent(feelsLikeTemperature, forKey: .feelsLikeTemperature)
+ try container.encodeIfPresent(totalRain, forKey: .totalRain)
+ try container.encodeIfPresent(totalSnow, forKey: .totalSnow)
+ try container.encodeIfPresent(timestamp, forKey: .timestamp)
+ try container.encodeIfPresent(pressure, forKey: .pressure)
+ try container.encodeIfPresent(humidity, forKey: .humidity)
+ try container.encodeIfPresent(dewPoint, forKey: .dewPoint)
+ try container.encodeIfPresent(uvIndex, forKey: .uvIndex)
+ try container.encodeIfPresent(visibility, forKey: .visibility)
+ try container.encodeIfPresent(cloudCoverage, forKey: .cloudCoverage)
+ try container.encodeIfPresent([condition], forKey: .weather)
+
+ try container.encodeIfPresent(sun.sunrise, forKey: .sunrise)
+ try container.encodeIfPresent(sun.sunset, forKey: .sunset)
+
+ try container.encodeIfPresent(moon.moonrise, forKey: .moonrise)
+ try container.encodeIfPresent(moon.moonset, forKey: .moonset)
+
+ try container.encodeIfPresent(wind.gust, forKey: .windGust)
+ try container.encodeIfPresent(wind.speed, forKey: .windSpeed)
+ try container.encodeIfPresent(wind.degrees, forKey: .windDirection)
}
}
-
diff --git a/Sources/HPOpenWeather/Models/Forecasts/ForecastBase.swift b/Sources/HPOpenWeather/Models/Forecasts/ForecastBase.swift
new file mode 100644
index 0000000..099b2e2
--- /dev/null
+++ b/Sources/HPOpenWeather/Models/Forecasts/ForecastBase.swift
@@ -0,0 +1,38 @@
+import Foundation
+
+public protocol ForecastBase: Codable, Hashable {
+
+ /// The timestamp when the data was collected.
+ var timestamp: Date { get }
+ /// Atmospheric pressure on the sea level, hPa.
+ var pressure: Double? { get }
+ /// Humidity in percent.
+ var humidity: Double? { get }
+ /// Atmospheric temperature (varying according to pressure and humidity) below which water droplets begin to condense and dew can form.
+ ///
+ /// Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit.
+ var dewPoint: Double? { get }
+ /// UV index.
+ var uvIndex: Double? { get }
+ /// Average visibility.
+ var visibility: Double? { get }
+ /// Cloudiness in percent.
+ var cloudCoverage: Double? { get }
+ /// Basic information about observed wind.
+ var wind: Wind { get }
+
+}
+
+public protocol SunForecast: Codable, Hashable {
+
+ /// A container that holds information about sunset and sunrise timestamps.
+ var sun: Sun { get }
+
+}
+
+public protocol MoonForecast: Codable, Hashable {
+
+ /// A container that holds information about moonrise and moonset timestamps.
+ var moon: Moon { get }
+
+}
diff --git a/Sources/HPOpenWeather/Models/Forecasts/HourlyForecast.swift b/Sources/HPOpenWeather/Models/Forecasts/HourlyForecast.swift
index 8a2ffbb..47b87f1 100644
--- a/Sources/HPOpenWeather/Models/Forecasts/HourlyForecast.swift
+++ b/Sources/HPOpenWeather/Models/Forecasts/HourlyForecast.swift
@@ -1,9 +1,9 @@
import Foundation
-public struct HourlyForecast: BasicWeatherResponse {
-
+public struct HourlyForecast: ForecastBase {
+
// MARK: - Coding Keys
-
+
enum CodingKeys: String, CodingKey {
case actualTemperature = "temp"
case feelsLikeTemperature = "feels_like"
@@ -19,11 +19,11 @@ public struct HourlyForecast: BasicWeatherResponse {
case windSpeed = "wind_speed"
case windGust = "wind_gust"
case windDirection = "wind_deg"
- case weatherArray = "weather"
+ case weather
}
-
+
// MARK: - Properties
-
+
public let timestamp: Date
public let pressure: Double?
public let humidity: Double?
@@ -33,32 +33,63 @@ 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)
+ public let temperature: Temperature
+ public let wind: Wind
+ public let condition: WeatherCondition
+
+ // MARK: - Init
+
+ public init(from decoder: any Decoder) throws {
+ let container = try decoder.container(keyedBy: CodingKeys.self)
+
+ let weatherArray = try container.decode([WeatherCondition].self, forKey: .weather)
+ guard let condition = weatherArray.first else {
+ throw OpenWeatherError.noCurrentConditionReturned
+ }
+ self.condition = condition
+
+ self.snow = try container.decodeIfPresent(Precipitation.self, forKey: .snow)
+ self.rain = try container.decodeIfPresent(Precipitation.self, forKey: .rain)
+ self.timestamp = try container.decode(Date.self, forKey: .timestamp)
+ self.pressure = try container.decodeIfPresent(Double.self, forKey: .pressure)
+ self.humidity = try container.decodeIfPresent(Double.self, forKey: .humidity)
+ self.dewPoint = try container.decodeIfPresent(Double.self, forKey: .dewPoint)
+ self.uvIndex = try container.decodeIfPresent(Double.self, forKey: .uvIndex)
+ self.cloudCoverage = try container.decodeIfPresent(Double.self, forKey: .cloudCoverage)
+ self.visibility = try container.decodeIfPresent(Double.self, forKey: .visibility)
+
+ let actualTemperature = try container.decode(Double.self, forKey: .actualTemperature)
+ let feelsLikeTemperature = try container.decode(Double.self, forKey: .feelsLikeTemperature)
+ self.temperature = Temperature(actual: actualTemperature, feelsLike: feelsLikeTemperature)
+
+ let windSpeed = try container.decodeIfPresent(Double.self, forKey: .windSpeed)
+ let windGust = try container.decodeIfPresent(Double.self, forKey: .windGust)
+ let windDirection = try container.decodeIfPresent(Double.self, forKey: .windDirection)
+ self.wind = Wind(speed: windSpeed, gust: windGust, degrees: windDirection)
}
-
- // Weather
-
- private let weatherArray: [WeatherCondition]
-
- public var weather: [WeatherCondition] {
- weatherArray
+
+ // MARK: - Encoding
+
+ public func encode(to encoder: any Encoder) throws {
+ var container = encoder.container(keyedBy: CodingKeys.self)
+
+ try container.encodeIfPresent(timestamp, forKey: .timestamp)
+ try container.encodeIfPresent(pressure, forKey: .pressure)
+ try container.encodeIfPresent(humidity, forKey: .humidity)
+ try container.encodeIfPresent(dewPoint, forKey: .dewPoint)
+ try container.encodeIfPresent(uvIndex, forKey: .uvIndex)
+ try container.encodeIfPresent(visibility, forKey: .visibility)
+ try container.encodeIfPresent(cloudCoverage, forKey: .cloudCoverage)
+ try container.encodeIfPresent(rain, forKey: .rain)
+ try container.encodeIfPresent(snow, forKey: .snow)
+ try container.encodeIfPresent([condition], forKey: .weather)
+
+ try container.encodeIfPresent(temperature.actual, forKey: .actualTemperature)
+ try container.encodeIfPresent(temperature.feelsLike, forKey: .feelsLikeTemperature)
+
+ try container.encodeIfPresent(wind.gust, forKey: .windGust)
+ try container.encodeIfPresent(wind.speed, forKey: .windSpeed)
+ try container.encodeIfPresent(wind.degrees, forKey: .windDirection)
}
-
+
}
diff --git a/Sources/HPOpenWeather/Models/Forecasts/MinutelyForecast.swift b/Sources/HPOpenWeather/Models/Forecasts/MinutelyForecast.swift
new file mode 100644
index 0000000..f5bd859
--- /dev/null
+++ b/Sources/HPOpenWeather/Models/Forecasts/MinutelyForecast.swift
@@ -0,0 +1,17 @@
+import Foundation
+
+public struct MinutelyForecast: Codable, Equatable, Hashable {
+
+ // MARK: - Nested Types
+
+ enum CodingKeys: String, CodingKey {
+ case timestamp = "dt"
+ case precipitation
+ }
+
+ // MARK: - Properties
+
+ public let timestamp: Date
+ public let precipitation: Double
+
+}
diff --git a/Sources/HPOpenWeather/Models/Moon.swift b/Sources/HPOpenWeather/Models/Moon.swift
new file mode 100644
index 0000000..0986e02
--- /dev/null
+++ b/Sources/HPOpenWeather/Models/Moon.swift
@@ -0,0 +1,11 @@
+import Foundation
+
+/// Type that holds information about moonset and moonrise times in UTC time.
+public struct Moon: Codable, Equatable, Hashable {
+
+ /// Moonset time.
+ public let moonset: Date
+ /// Moonrise time.
+ public let moonrise: Date
+
+}
diff --git a/Sources/HPOpenWeather/Models/Precipitation.swift b/Sources/HPOpenWeather/Models/Precipitation.swift
index fcdccc7..3e9472a 100644
--- a/Sources/HPOpenWeather/Models/Precipitation.swift
+++ b/Sources/HPOpenWeather/Models/Precipitation.swift
@@ -1,29 +1,33 @@
import Foundation
-/// Type that holds information about recent precipitation
+/// Type that holds information about recent precipitation.
public struct Precipitation: Codable, Equatable, Hashable {
+ // MARK: - Nested Types
+
enum CodingKeys: String, CodingKey {
case lastHour = "1h"
case lastThreeHours = "3h"
}
- /// Precipitation volume for the last 1 hour, measured in mm
+ // MARK: - Properties
+
+ /// Precipitation volume for the last 1 hour, measured in mm.
public var lastHour: Double?
- /// Precipitation volume for the last 3 hours, measured in mm
+ /// Precipitation volume for the last 3 hours, measured in mm.
public var lastThreeHours: Double?
- /// A convertible measurement of how much precipitation occured in the last hour if any
+ /// A convertible measurement of how much precipitation occured in the last hour if any.
public var lastHourMeasurement: Measurement? {
- guard let lastHour = lastHour else {
+ guard let lastHour else {
return nil
}
return Measurement(value: lastHour, unit: .millimeters)
}
- /// A convertible measurement of how much precipitation occured in the last three hours if any
+ /// A convertible measurement of how much precipitation occured in the last three hours if any.
public var lastThreeHoursMeasurement: Measurement? {
- guard let lastThreeHours = lastThreeHours else {
+ guard let 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 778dbfa..139dc7a 100644
--- a/Sources/HPOpenWeather/Models/Sun.swift
+++ b/Sources/HPOpenWeather/Models/Sun.swift
@@ -1,11 +1,11 @@
import Foundation
-/// Type that holds information about sunrise and sunset times in UTC time
+/// Type that holds information about sunrise and sunset times in UTC time.
public struct Sun: Codable, Equatable, Hashable {
-
- /// Sunset time
+
+ /// Sunset time.
public let sunset: Date
- /// Sunrise time
+ /// Sunrise time.
public let sunrise: Date
-
+
}
diff --git a/Sources/HPOpenWeather/Models/Temperature.swift b/Sources/HPOpenWeather/Models/Temperature.swift
index f2bc8fd..d663a3a 100644
--- a/Sources/HPOpenWeather/Models/Temperature.swift
+++ b/Sources/HPOpenWeather/Models/Temperature.swift
@@ -1,32 +1,24 @@
import Foundation
-/// Type that holds information about daily temperature changes
-public struct Temperature: Codable, Equatable, Hashable {
+/// Type that holds information about daily temperature changes.
+public struct Temperature: 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
- /// A convertible measurement of the actually measured temperature
- public var actualMeasurement: Measurement {
- actualMeasurement(units: OpenWeather.shared.units)
- }
-
- /// 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 {
+ /// A convertible measurement of the actually measured temperature.
+ /// - Parameter units: The units to use when formatting the `actual` property. This should be the same as what you used when making the request.
+ /// - Returns: a measurement in the provided unit
+ public func actualMeasurement(units: WeatherUnits) -> Measurement {
Measurement(value: actual, unit: units.temperatureUnit)
}
- /// A convertible measurement of how the actually measured temperature feels like
- public var feelsLikeMeasurement: Measurement {
- feelsLikeMeasurement(units: OpenWeather.shared.units)
- }
-
- /// 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 {
+ /// A convertible measurement of how the actually measured temperature feels like.
+ /// - Parameter units: The units to use when formatting the `feelsLike` property. This should be the same as what you used when making the request.
+ /// - Returns: a measurement in the provided unit
+ public func feelsLikeMeasurement(units: WeatherUnits) -> Measurement {
Measurement(value: feelsLike, unit: units.temperatureUnit)
}
diff --git a/Sources/HPOpenWeather/Models/Weather+Language.swift b/Sources/HPOpenWeather/Models/Weather+Language.swift
new file mode 100644
index 0000000..ce207b0
--- /dev/null
+++ b/Sources/HPOpenWeather/Models/Weather+Language.swift
@@ -0,0 +1,52 @@
+import Foundation
+
+/// The language that should be used in API responses for example for weather condition descriptions.
+public enum WeatherLanguage: 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/Models/Weather+Units.swift b/Sources/HPOpenWeather/Models/Weather+Units.swift
new file mode 100644
index 0000000..7415fde
--- /dev/null
+++ b/Sources/HPOpenWeather/Models/Weather+Units.swift
@@ -0,0 +1,35 @@
+import Foundation
+
+/// The units that should the data in the API responses should be formatted in.
+public enum WeatherUnits: 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
+
+ var temperatureUnit: UnitTemperature {
+ switch self {
+ case .standard:
+ return .kelvin
+ case .metric:
+ return .celsius
+ case .imperial:
+ return .fahrenheit
+ }
+ }
+
+ var windSpeedUnit: UnitSpeed {
+ switch self {
+ case .standard:
+ return .metersPerSecond
+ case .metric:
+ return .metersPerSecond
+ case .imperial:
+ return .milesPerHour
+ }
+ }
+
+}
diff --git a/Sources/HPOpenWeather/Models/Weather.swift b/Sources/HPOpenWeather/Models/Weather.swift
new file mode 100644
index 0000000..2ac48bc
--- /dev/null
+++ b/Sources/HPOpenWeather/Models/Weather.swift
@@ -0,0 +1,72 @@
+import Foundation
+
+public struct Weather: Codable, Equatable, Hashable {
+
+ // MARK: - Nested Types
+
+ enum CodingKeys: String, CodingKey {
+ case timezoneIdentifier = "timezone"
+ case currentWeather = "current"
+ case minutelyForecasts = "minutely"
+ case hourlyForecasts = "hourly"
+ case dailyForecasts = "daily"
+ case alerts
+
+ // These keys are not actually present in the response from the OpenWeather API.
+ // We inject them manually after decoding the response in order to persist these settings
+ // if you want to cache the response for example.
+ case language
+ case units
+ }
+
+ // MARK: - Properties
+
+ public let timezone: TimeZone
+ public let currentWeather: CurrentWeather?
+ public let minutelyForecasts: [MinutelyForecast]?
+ 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: WeatherLanguage?
+ public internal(set) var units: WeatherUnits?
+
+ // MARK: - Init
+
+ public init(from decoder: any Decoder) throws {
+ let container = try decoder.container(keyedBy: CodingKeys.self)
+
+ let timezoneIdentifier = try container.decode(String.self, forKey: .timezoneIdentifier)
+ guard let timezone = TimeZone(identifier: timezoneIdentifier) else {
+ throw OpenWeatherError.invalidTimeZoneIdentifier(timezoneIdentifier)
+ }
+ self.timezone = timezone
+
+ self.currentWeather = try container.decodeIfPresent(CurrentWeather.self, forKey: .currentWeather)
+ self.minutelyForecasts = try container.decodeIfPresent([MinutelyForecast].self, forKey: .minutelyForecasts)
+ self.hourlyForecasts = try container.decodeIfPresent([HourlyForecast].self, forKey: .hourlyForecasts)
+ self.dailyForecasts = try container.decodeIfPresent([DailyForecast].self, forKey: .dailyForecasts)
+ self.alerts = try container.decodeIfPresent([WeatherAlert].self, forKey: .alerts)
+
+ self.language = try container.decodeIfPresent(WeatherLanguage.self, forKey: .language)
+ self.units = try container.decodeIfPresent(WeatherUnits.self, forKey: .units)
+ }
+
+ // MARK: - Encoding
+
+ public func encode(to encoder: Encoder) throws {
+ var container = encoder.container(keyedBy: CodingKeys.self)
+
+ try container.encodeIfPresent(timezone.identifier, forKey: .timezoneIdentifier)
+ try container.encodeIfPresent(currentWeather, forKey: .currentWeather)
+ try container.encodeIfPresent(minutelyForecasts, forKey: .minutelyForecasts)
+ try container.encodeIfPresent(hourlyForecasts, forKey: .hourlyForecasts)
+ try container.encodeIfPresent(dailyForecasts, forKey: .dailyForecasts)
+ try container.encodeIfPresent(alerts, forKey: .alerts)
+
+ try container.encodeIfPresent(language, forKey: .language)
+ try container.encodeIfPresent(units, forKey: .units)
+ }
+
+}
diff --git a/Sources/HPOpenWeather/Models/WeatherAlert.swift b/Sources/HPOpenWeather/Models/WeatherAlert.swift
index 7659d38..987d3b2 100644
--- a/Sources/HPOpenWeather/Models/WeatherAlert.swift
+++ b/Sources/HPOpenWeather/Models/WeatherAlert.swift
@@ -1,18 +1,9 @@
import Foundation
-/// Type that holds information about weather alerts
+/// 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
+ // MARK: - Nested Types
enum CodingKeys: String, CodingKey {
case senderName = "sender_name"
@@ -22,4 +13,19 @@ public struct WeatherAlert: Codable, Hashable, Equatable {
case description
}
+ // MARK: - Properties
+
+ /// Name of the alert source.
+ ///
+ /// A full list of possible sources can be found [here](https://openweathermap.org/api/one-call-3#listsource)
+ 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
+
}
diff --git a/Sources/HPOpenWeather/Models/WeatherCondition.swift b/Sources/HPOpenWeather/Models/WeatherCondition.swift
index 7f95d66..7d714ee 100644
--- a/Sources/HPOpenWeather/Models/WeatherCondition.swift
+++ b/Sources/HPOpenWeather/Models/WeatherCondition.swift
@@ -1,15 +1,15 @@
import Foundation
-/// Type that holds information about weather conditions
+/// Type that holds information about weather conditions.
public struct WeatherCondition: Codable, Equatable, Hashable {
- /// The weather condition ID
+ /// The weather condition ID.
public let id: Int
- /// Group of weather parameters
+ /// Group of weather parameters.
public let main: String
- /// The weather condition within the group
+ /// The weather condition within the group.
public let description: String
- /// The ID of the corresponding weather icon
+ /// The ID of the corresponding weather icon.
public let icon: WeatherIcon
}
diff --git a/Sources/HPOpenWeather/Models/WeatherIcon.swift b/Sources/HPOpenWeather/Models/WeatherIcon.swift
index bbb1a14..39d8e0a 100644
--- a/Sources/HPOpenWeather/Models/WeatherIcon.swift
+++ b/Sources/HPOpenWeather/Models/WeatherIcon.swift
@@ -1,9 +1,10 @@
+import Foundation
+
#if canImport(UIKit)
import UIKit
#elseif canImport(AppKit)
import AppKit
#endif
-import Foundation
public enum WeatherIcon: String, Codable, CaseIterable {
@@ -29,37 +30,37 @@ public enum WeatherIcon: String, Codable, CaseIterable {
}
@available(iOS 13.0, macOS 11.0, tvOS 13.0, watchOS 6.0, *)
-public extension WeatherIcon {
+extension WeatherIcon {
- var systemImageName: String {
+ public var systemImageName: String {
makeIconName(filled: false)
}
- var systemImageNameFilled: String {
+ public var systemImageNameFilled: String {
makeIconName(filled: true)
}
-#if canImport(UIKit)
+ #if canImport(UIKit)
- func filledUIImage(withConfiguration configuration: UIImage.Configuration? = nil) -> UIImage? {
+ public func filledUIImage(withConfiguration configuration: UIImage.Configuration? = nil) -> UIImage? {
UIImage(systemName: systemImageNameFilled, withConfiguration: configuration)
}
- func outlineUIImage(withConfiguration configuration: UIImage.Configuration? = nil) -> UIImage? {
+ public func outlineUIImage(withConfiguration configuration: UIImage.Configuration? = nil) -> UIImage? {
UIImage(systemName: systemImageName, withConfiguration: configuration)
}
-#elseif canImport(AppKit)
+ #elseif canImport(AppKit)
- func filledNSImage(accessibilityDescription: String? = nil) -> NSImage? {
+ public func filledNSImage(accessibilityDescription: String? = nil) -> NSImage? {
NSImage(systemSymbolName: systemImageNameFilled, accessibilityDescription: accessibilityDescription)
}
- func outlineNSImage(accessibilityDescription: String? = nil) -> NSImage? {
+ public func outlineNSImage(accessibilityDescription: String? = nil) -> NSImage? {
NSImage(systemSymbolName: systemImageName, accessibilityDescription: accessibilityDescription)
}
-#endif
+ #endif
private func makeIconName(filled: Bool) -> String {
let iconName: String
diff --git a/Sources/HPOpenWeather/Models/Wind.swift b/Sources/HPOpenWeather/Models/Wind.swift
index 414e32d..cc5b7ac 100644
--- a/Sources/HPOpenWeather/Models/Wind.swift
+++ b/Sources/HPOpenWeather/Models/Wind.swift
@@ -1,24 +1,20 @@
import Foundation
-/// Type that holds information about wind speed and direction measured in degrees
+/// Type that holds information about wind speed and direction measured in degrees.
public struct Wind: Codable, Equatable, Hashable {
- /// The current wind speed depending on the request's unit (metric: meter/second, imperial: miles/hour)
+ /// The current wind speed depending on the request's unit (metric: meter/second, imperial: miles/hour).
public let speed: Double?
- /// Wind gust speed (metric: meter/sec, imperial: miles/hour)
+ /// Wind gust speed (metric: meter/sec, imperial: miles/hour).
public let gust: Double?
- /// The wind direction measured in degrees from North
+ /// 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 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 {
+ /// 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. This should be the same as what you used when making the request.
+ /// - Returns: a measurement in the provided unit
+ public func speedMeasurement(units: WeatherUnits) -> Measurement? {
+ guard let speed else {
return nil
}
return Measurement(value: speed, unit: units.windSpeedUnit)
diff --git a/Sources/HPOpenWeather/OpenWeather.swift b/Sources/HPOpenWeather/OpenWeather.swift
index 3ab6e95..ddd6bc2 100644
--- a/Sources/HPOpenWeather/OpenWeather.swift
+++ b/Sources/HPOpenWeather/OpenWeather.swift
@@ -1,26 +1,26 @@
import CoreLocation
import Foundation
-/// A type to request current weather conditions and forecasts
+/// 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
+ /// 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
- /// 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
+ /// The API key to use for weather requests.
+ let apiKey: String
+ /// The language that will be used in weather responses.
+ let language: WeatherLanguage
+ /// The units that will be used in weather responses.
+ let units: WeatherUnits
+
+ /// 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) {
+ public init(apiKey: String, language: WeatherLanguage = .english, units: WeatherUnits = .metric) {
self.language = language
self.units = units
self.apiKey = apiKey
@@ -29,25 +29,22 @@ public final class OpenWeather {
// MARK: - Properties
- /// A shared instance of the weather client
- public static let shared = OpenWeather()
-
- /// The OpenWeatherMap API key to authorize requests
- public var apiKey : String?
- /// The language that should be used in API responses
- public var language: WeatherResponse.Language = .english
- /// The units that should be used to format the API responses
- public var units: WeatherResponse.Units = .metric
+ /// The OpenWeather API key to authorize requests.
+ public var apiKey: String?
+ /// The language that should be used in API responses.
+ public var language: WeatherLanguage = .english
+ /// The units that should be used to format the API responses.
+ public var units: WeatherUnits = .metric
// MARK: - Init
- /// Initialised a new instance of `OpenWeather` and applies the specified API key
+ /// 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
+ /// Initialised a new instance of `OpenWeather` and applies the specified settings.
/// - Parameter settings: the settings to apply, including API key, language and units
public init(settings: Settings) {
self.apiKey = settings.apiKey
@@ -57,57 +54,59 @@ public final class OpenWeather {
// MARK: - Sending Requests
- /// Sends the specified request to the OpenWeather API
+ /// Requests a weather forecast for the specified location.
/// - 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,
+ /// - Throws: If no API key was provided, the request was misconfigured, the networking failed or the response failed to decode
+ public func weather(
+ for coordinate: CLLocationCoordinate2D,
+ excludedFields: [ExcludableField]? = nil,
date: Date? = nil,
urlSession: URLSession = .shared
- ) async throws -> WeatherResponse {
+ ) async throws -> Weather {
+ guard let apiKey, !apiKey.isEmpty else {
+ throw OpenWeatherError.invalidAPIKey
+ }
+
+ let settings = Settings(apiKey: apiKey, language: language, units: units)
let request = WeatherRequest(
coordinate: coordinate,
excludedFields: excludedFields,
- date: date
+ date: date,
+ settings: settings
)
- 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
- }
-
- 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
+ return try await request.response(urlSession: urlSession).output
}
- /// Sends the specified request to the OpenWeather API
+ /// Requests a weather forecast for the specified location.
/// - Parameters:
- /// - request: The request object that holds information about request location, date, etc.
+ /// - 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
/// - 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 {
+ public func requestWeather(
+ for coordinate: CLLocationCoordinate2D,
+ excludedFields: [ExcludableField]? = nil,
+ date: Date? = nil,
+ urlSession: URLSession = .shared,
+ completion: @escaping @Sendable (Result) -> Void
+ ) -> Task {
Task {
do {
- let response = try await weatherResponse(request, urlSession: urlSession)
+ let response = try await weather(
+ for: coordinate,
+ excludedFields: excludedFields,
+ date: date,
+ urlSession: urlSession
+ )
completion(.success(response))
} catch {
completion(.failure(error))
@@ -115,14 +114,4 @@ public final class OpenWeather {
}
}
- // 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
- }
-
}
diff --git a/Sources/HPOpenWeather/OpenWeatherAPIError.swift b/Sources/HPOpenWeather/OpenWeatherAPIError.swift
new file mode 100644
index 0000000..ac40104
--- /dev/null
+++ b/Sources/HPOpenWeather/OpenWeatherAPIError.swift
@@ -0,0 +1,23 @@
+import Foundation
+
+/// An error that is thrown when the API returns an error.
+public struct OpenWeatherAPIError: Error, Decodable {
+
+ // MARK: - Nested Types
+
+ enum CodingKeys: String, CodingKey {
+ case code = "cod"
+ case message
+ case parameters
+ }
+
+ // MARK: - Properties
+
+ /// The error code, such as 400, 404 or 5xx.
+ public let code: Int
+ /// The error message or description.
+ public let message: String
+ /// List of request parameters names that are related to this particular error.
+ public let parameters: [String]?
+
+}
diff --git a/Sources/HPOpenWeather/OpenWeatherError.swift b/Sources/HPOpenWeather/OpenWeatherError.swift
new file mode 100644
index 0000000..4a49b78
--- /dev/null
+++ b/Sources/HPOpenWeather/OpenWeatherError.swift
@@ -0,0 +1,23 @@
+import Foundation
+
+public enum OpenWeatherError: LocalizedError, Equatable {
+
+ case invalidRequestTimestamp
+ case invalidAPIKey
+ case noCurrentConditionReturned
+ case invalidTimeZoneIdentifier(_ identifier: String)
+
+ public var errorDescription: String? {
+ switch self {
+ case .invalidRequestTimestamp:
+ return "The request timestamp is invalid"
+ case .invalidAPIKey:
+ return "The API key is missing or empty"
+ case .noCurrentConditionReturned:
+ return "No current condition was returned"
+ case .invalidTimeZoneIdentifier(let identifier):
+ return "The timezone identifier '\(identifier)' is invalid"
+ }
+ }
+
+}
diff --git a/Sources/HPOpenWeather/Requests/APINetworkRequest.swift b/Sources/HPOpenWeather/Requests/APINetworkRequest.swift
deleted file mode 100644
index 9e799ec..0000000
--- a/Sources/HPOpenWeather/Requests/APINetworkRequest.swift
+++ /dev/null
@@ -1,30 +0,0 @@
-import Foundation
-import HPNetwork
-
-struct APINetworkRequest: DecodableRequest {
-
- typealias Output = WeatherResponse
-
- 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]
-
- var decoder: JSONDecoder {
- APINetworkRequest.decoder
- }
-
- 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/ExcludableField.swift b/Sources/HPOpenWeather/Requests/ExcludableField.swift
new file mode 100644
index 0000000..3969d2d
--- /dev/null
+++ b/Sources/HPOpenWeather/Requests/ExcludableField.swift
@@ -0,0 +1,9 @@
+import Foundation
+
+public enum ExcludableField: String, Codable {
+ case current
+ case minutely
+ case hourly
+ case daily
+ case alerts
+}
diff --git a/Sources/HPOpenWeather/Requests/WeatherRequest+Combine.swift b/Sources/HPOpenWeather/Requests/WeatherRequest+Combine.swift
deleted file mode 100644
index d32b1f5..0000000
--- a/Sources/HPOpenWeather/Requests/WeatherRequest+Combine.swift
+++ /dev/null
@@ -1,25 +0,0 @@
-import Combine
-import Foundation
-
-public extension WeatherRequest {
-
- func publisher(
- apiKey: String,
- language: WeatherResponse.Language = .english,
- units: WeatherResponse.Units = .metric,
- urlSession: URLSession = .shared,
- finishingQueue: DispatchQueue = .main) -> AnyPublisher