Compare commits

...

44 commits
v0.1.0 ... main

Author SHA1 Message Date
Winni Neessen d6ce76b1ad
Remove sudo usage for jq installation in sonarqube.yml
All checks were successful
Codecov workflow / run (push) Successful in 1m39s
golangci-lint / lint (push) Successful in 48s
REUSE Compliance Check / test (push) Successful in 4s
SonarQube / Build (push) Successful in 1m40s
The use of sudo keyword for installing 'jq' in the SonarQube workflow file, sonarqube.yml, has been removed. This change is due to the fact that the root permission is not necessary during the installation process, adhering better to the principle of least privilege.
2024-02-14 00:50:25 +01:00
Winni Neessen 072a2aed5d
Remove sudo usage for jq installation in sonarqube.yml
All checks were successful
golangci-lint / lint (push) Successful in 47s
REUSE Compliance Check / test (push) Successful in 4s
SonarQube / Build (push) Successful in 1m37s
The use of sudo keyword for installing 'jq' in the SonarQube workflow file, sonarqube.yml, has been removed. This change is due to the fact that the root permission is not necessary during the installation process, adhering better to the principle of least privilege.
2024-02-14 00:46:07 +01:00
Winni Neessen a536ac3bde
Remove sudo usage for jq installation in sonarqube.yml
Some checks failed
golangci-lint / lint (push) Successful in 47s
REUSE Compliance Check / test (push) Successful in 4s
SonarQube / Build (push) Failing after 1m12s
The use of sudo keyword for installing 'jq' in the SonarQube workflow file, sonarqube.yml, has been removed. This change is due to the fact that the root permission is not necessary during the installation process, adhering better to the principle of least privilege.
2024-02-14 00:39:16 +01:00
Winni Neessen eb675388f0
reports. Simultaneously, the installation of 'jq' has been removed from the sonarqube.yml workflow as it's no longer needed there.
Some checks failed
golangci-lint / lint (push) Successful in 48s
REUSE Compliance Check / test (push) Successful in 4s
SonarQube / Build (push) Failing after 1m12s
2024-02-14 00:36:34 +01:00
Winni Neessen e4cbab4e43
Install jq in the codecov workflow
Some checks failed
golangci-lint / lint (push) Successful in 47s
REUSE Compliance Check / test (push) Successful in 4s
SonarQube / Build (push) Failing after 1m29s
This commit adds a new step to the codecov.yml workflow. This step installs 'jq', a command-line JSON processor, which might be necessary for the processing of test results or coverage
2024-02-14 00:33:51 +01:00
Winni Neessen 8923b88e07
Simplify workflow settings and upgrade setup-go to v4
Some checks failed
REUSE Compliance Check / test (push) Successful in 4s
SonarQube / Build (push) Failing after 1m29s
golangci-lint / lint (push) Failing after 11m20s
This commit simplifies the Forgejo workflow settings by hardcoding the 'docker' and '1.22' options for the 'runs-on' and 'go-version' respectively, instead of using a matrix. The version of setup-go action is also upgraded to v4.
2024-02-13 23:33:37 +01:00
Winni Neessen 94eca2f3fe
Fix Forgejo workflows
Some checks failed
golangci-lint / lint (push) Successful in 47s
REUSE Compliance Check / test (push) Successful in 19s
SonarQube / Build (push) Failing after 1m30s
2024-02-13 22:25:23 +01:00
Winni Neessen 759300d066
Move Github workflows to Forgejo workflows
Some checks failed
REUSE Compliance Check / test (push) Waiting to run
SonarQube / Build (push) Waiting to run
golangci-lint / lint (push) Successful in 47s
Codecov workflow / run (1.20, macos-latest) (push) Has been cancelled
Codecov workflow / run (1.20, ubuntu-latest) (push) Has been cancelled
Codecov workflow / run (1.20, windows-latest) (push) Has been cancelled
2024-02-13 22:21:35 +01:00
Winni Neessen 3c69f36748
Update Go version setup in golangci-lint workflow
All checks were successful
golangci-lint / lint (push) Successful in 48s
The workflow file for golangci-lint has been updated to use the newer version of setup-go action. This change moves from version v3 to v4
2024-02-13 19:51:27 +01:00
Winni Neessen 0b88d11fdf
Update Go version setup in golangci-lint workflow
Some checks failed
golangci-lint / lint (push) Has been cancelled
The workflow file for golangci-lint has been updated to use the newer version of setup-go action. This change moves from version v3 to v4 under
2024-02-13 19:45:53 +01:00
Winni Neessen d6b551cc5e
Correct URL format in Github Actions of golangci-lint workflow
Some checks failed
golangci-lint / lint (push) Has been cancelled
The URLs in the golangci-lint workflow file have been corrected. The extraneous "https://github.com/" part of each URL has been removed in each 'uses' section to ensure the correct working of Github actions. Now, actions/setup-go@v3, actions/checkout@v3, and golangci/g
2024-02-13 19:26:11 +01:00
Winni Neessen 5297342603
Update golangci-lint workflow's 'runs-on' value
Some checks failed
golangci-lint / lint (push) Has been cancelled
The 'runs-on' value of the golangci-lint workflow file has been updated from 'ubuntu-latest' to 'docker'. This change improves the replication of the production environment conditions for linting, ensuring more accurate code quality checks.
2024-02-13 18:58:18 +01:00
Winni Neessen c3af61791e
Update golangci-lint workflow's 'runs-on' value
Some checks failed
golangci-lint / lint (push) Failing after 40s
The 'runs-on' value of the golangci-lint workflow file has been updated from 'ubuntu-latest' to 'docker'. This change improves the replication of the production environment conditions for linting, ensuring more accurate code quality checks.
2024-02-13 18:56:24 +01:00
Winni Neessen b8c117c056
Add golangci-lint workflow
Some checks are pending
golangci-lint / lint (push) Waiting to run
A new Github Actions workflow file named golangci-lint.yml has been added to the workflows directory. This will ensure that the linting tool golangci-lint is run for every push and pull request to this project, thus improving the code quality.
2024-02-13 18:55:16 +01:00
Winni Neessen 3f7cccb511 Merge pull request 'Update URLs to point to new repository domain' (#12) from switch-gh-to-forgejo into main
Reviewed-on: #12
2024-02-11 15:28:32 +01:00
Winni Neessen c9fa7eb1d7
Add mirror repository information to README
A new section called "Mirror" was added to the README.md file indicating that the Github repository is only a mirror of the main repository. The main repository is located at "https://src.neessen.cloud/wneessen/go-meteologix". This change improves project transparency and informs contributors of the primary source of the code.
2024-02-11 15:27:34 +01:00
Winni Neessen 878c4dfe71
Update URLs to point to new repository domain
The URLs in all the files that reference the old domain (github.com) have been updated to reference the new domain (src.neessen.cloud). This includes URLs in badges, links in the README.md, import paths in .go files, and module paths in go.mod and .golangci.toml.
2024-02-11 15:24:46 +01:00
Winni Neessen 27c8542f76
Merge pull request #11 from wneessen/bearer-auth
Add bearer token authentication
2023-08-02 16:13:09 +02:00
Winni Neessen a5667bb828
Add bearer token authentication
In this commit, we've added the capability to authenticate via bearer token to Meteologix's HTTP client. A new method "WithBearerToken" has been implemented, allowing a bearer token to be set in 'meteologix.go'. Additionally, in 'httpclient.go', the token is attached to the "Authorization" header for API requests. Tests asserting token setting functionality have been added in 'meteologix_test.go'. These changes open up an alternative authentication option for our API users. The bearer auth is not public yet, so there is no way for us to test this auth
2023-08-02 16:08:38 +02:00
Winni Neessen 173c6eb8ec
Update Go versions in CI workflow
Removed versions 1.18 and 1.19 from the Go matrix in the codecov.yml file. Now, CI workflow only tests for Go version '1.20'. This change simplifies the CI testing process and removes the change of running into CI failures due to rate limiting.
2023-06-28 10:23:33 +02:00
Winni Neessen a51644fbb6
Remove redundant package comment
The comment referencing bindings to the Meteologix/Kachelmann-Wetter weather API was incorrectly placed in the astroinfo.go file. This comment was irrelevant to the respective package and therefore, has been removed for accuracy and clarity.
2023-06-27 19:37:26 +02:00
Winni Neessen f48392d553
Merge pull request #10 from wneessen/more_curweather_datapoints
More CurrentWeather datapoints and refactoring
2023-06-27 19:31:44 +02:00
Winni Neessen 85c3c1aff3
Update package version in doc.go
The version constant in doc.go was incremented from "0.1.0" to "0.1.1". This reflects recent minor changes or bug fixes made to the meteologix package.
2023-06-27 19:26:02 +02:00
Winni Neessen abd200177f
Update DateTime comment for clarity
The comment explaining the function DateTime in temperature.go was modified. The original comment was inaccurate, suggesting it returned boolean when in fact it returns the exact time the temperature data was obtained. The comment was updated to accurately reflect what the function does.
2023-06-27 19:25:19 +02:00
Winni Neessen e7f8662347
Update precision constants in station.go
Refined precision constants in station.go for better clarity and maintainability. Comment descriptions for each precision level have been expanded for better understanding. Strings have been introduced as constants to represent each precision level, enhancing code readability and preventing inconsistencies. Changes are also done to the UnmarshalJSON() and String() of the Precision type to use these new string constants improving overall code quality.
2023-06-27 19:21:24 +02:00
Winni Neessen 54cc672dfc
Refine comment for DateTime function in speed.go
The comment for the DateTime function in the speed.go file was slightly confusing and inaccurate. It has been updated to more accurately reflect its purpose and functionality. It now clearly states that the function returns the DateTime when the Speed was checked, as originally intended.
2023-06-27 19:12:35 +02:00
Winni Neessen e3756a5466
Update comment for DateTime function in radiation.go
The comment for the DateTime function in radiation.go was updated to more accurately describe its functionality. The previous version of the comment suggested that the function returns a boolean value indicating the availability of a radiation value at the time of a query. However, the function actually returns a time.Time object representing the date and time of the query. The comment was thus updated to reflect this.
2023-06-27 19:01:37 +02:00
Winni Neessen 513d7b863f
Refine DateTime function description in pressure.go
The previous description of the DateTime function in pressure.go was misleading as it implied that the function would return a boolean. The function actually returns the date and time of a Pressure reading. The description has therefore been updated for clarity and accuracy.
2023-06-27 18:59:13 +02:00
Winni Neessen c9d95300c2
Refine Precipitation's DateTime method comment
The Precipitation's DateTime method comment was adjusted to better reflect its functionality. Instead of stating it returns true if the precipitation data was available at the time of the query or not, it was clarified that the method actually returns the DateTime when the Precipitation value was recorded.
2023-06-27 18:57:33 +02:00
Winni Neessen 84ba2feda9
Refine method comments in humidity.go
Comments for DateTime and Value methods in humidity.go file were updated to better describe their functionalities. This clarification should improve understandability for future maintenance/code reading.
2023-06-27 18:49:32 +02:00
Winni Neessen 65d065dd59
Improve error reporting in station_test.go
Modified the error handling sections within the StationSearchByLocation test function to also output the error messages. Changes help in debugging by providing more context whenever the test fails.
2023-06-27 18:31:41 +02:00
Winni Neessen 3eb6a76f5d
Refactor HTTP client for improved error handling and readability
A few changes were made to `httpclient.go`. We swapped the `os` package for `log` to standardize error logging. Instead of having the HTTP transport as a value, it is now a pointer in the HTTP client instantiation function, aligning it with the client itself. We also altered error handling: we now just return an error when our server response is `nil`, and changed `sr.Body.Close()` error reporting to use `log` instead of `fmt`.

To streamline the code, user authentication function `setAuthHeader` was renamed to `setAuthentication`, and the copy of our HTTP response body to buffer now only happens after the status check. We also replaced the `Flush` error to be handled properly.

Successful requests return bytes instead of the buffer itself. As a result, these changes have led to more readable and effective code.
2023-06-27 18:31:13 +02:00
Winni Neessen 9c65eca128
Fix Height conversion and update DateTime comment
- Update the comment for DateTime() to accurately describe its functionality
- Fix the Height conversion (CentiMeter and MilliMeter) calculations by changing the division to multiplication

This commit ensures that the conversion functions work as intended and improves the description for DateTime().
2023-06-27 17:18:15 +02:00
Winni Neessen a4f19380ff
Handle error in GetGeoLocationByName
Return error when fetching GeoLocations fails or the result is empty. This ensures that an error is properly returned and handled when either an error occurs during the API call or no locations are found for the given city name.
2023-06-27 17:15:07 +02:00
Winni Neessen d0905266e1
Fix typo and improve direction calculations
Correct typos related to Angle naming and update calculations for start and end ranges in direction mapping. This ensures accurate and valid directional values are used in the application.
2023-06-27 16:49:22 +02:00
Winni Neessen 28479be939
Add test for findDirection function
Add a new test file, direction_test.go, to test the findDirection function with various input cases. This change helps ensure the reliability and expected functionality of the findDirection function.
2023-06-27 16:48:29 +02:00
Winni Neessen 914327de85
Improve Density type documentation and comments
Update Density type's comments to clarify its purpose and usage, including units and methods, for better code understanding and maintainability.
2023-06-27 16:13:13 +02:00
Winni Neessen a10ec1c0f9
Update DateTime String method to use Value()
Refactor the DateTime String method to use the Value() function instead of accessing the internal field directly, for better encapsulation and readability.
2023-06-27 16:12:59 +02:00
Winni Neessen d7567d4b2b
Remove unused and redundant weather fields
Removed several unused and redundant weather fields like DewPointMean, GlobalRadiation, TemperatureMean, Temperature5cm, etc. from curweather.go to reduce complexity and improve readability.
2023-06-27 16:12:38 +02:00
Winni Neessen c1e054d9a3
Reformat ConditionMap for better readability
Refactor the ConditionMap by aligning the values, which improves code readability and adheres to the code style guidelines.
2023-06-27 16:12:22 +02:00
Winni Neessen 4318599eb0
Replace hardcoded date format with constant
Replace the hardcoded "2006-01-02" date format with the DateFormat constant in both SunsetByDateString and SunriseByDateString functions to ensure consistency and easier future updates.
2023-06-27 16:12:06 +02:00
Winni Neessen a0b67b0367
Add client timeout and improve error handling
- Set HTTPClientTimeout for HTTP client to prevent hanging requests
- Check for http.StatusOK instead of a generic error-range
- Use json.NewDecoder to decode error JSON for better memory usage
2023-06-27 15:20:16 +02:00
Winni Neessen 681c53c23d
Fix humidity check in curweather.go
Check for HumidityRelative instead of Dewpoint in the HumidityRelative() function to return correct "not available" status.
2023-06-27 15:19:41 +02:00
Winni Neessen 8cb9754f69
Added SnowHeight and corrected comments 2023-06-26 12:21:36 +02:00
31 changed files with 458 additions and 149 deletions

6
.forgejo/FUNDING.yml Normal file
View file

@ -0,0 +1,6 @@
# SPDX-FileCopyrightText: 2023 Winni Neessen <wn@neessen.dev>
#
# SPDX-License-Identifier: CC0-1.0
github: wneessen
ko_fi: winni

View file

@ -10,7 +10,7 @@ on:
paths:
- '**.go'
- 'go.*'
- '.github/**'
- '.forgejo/**'
- 'codecov.yml'
pull_request:
branches:
@ -18,29 +18,24 @@ on:
paths:
- '**.go'
- 'go.*'
- '.github/**'
- '.forgejo/**'
- 'codecov.yml'
env:
API_KEY: ${{ secrets.API_KEY }}
jobs:
run:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
go: [1.18, 1.19, '1.20']
runs-on: docker
steps:
- name: Checkout Code
uses: actions/checkout@master
- name: Setup go
uses: actions/setup-go@v3
uses: actions/setup-go@v4
with:
go-version: ${{ matrix.go }}
go-version: '1.22'
- name: Run Tests
run: |
go test -v -shuffle=on -race --coverprofile=coverage.coverprofile --covermode=atomic ./...
- name: Upload coverage to Codecov
if: success() && matrix.go == '1.20' && matrix.os == 'ubuntu-latest'
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos

View file

@ -17,11 +17,11 @@ permissions:
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
runs-on: docker
steps:
- uses: actions/setup-go@v3
- uses: actions/setup-go@v4
with:
go-version: '1.20'
go-version: '1.21'
- uses: actions/checkout@v3
- name: golangci-lint
uses: golangci/golangci-lint-action@v3

View file

@ -8,7 +8,7 @@ on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
runs-on: docker
steps:
- uses: actions/checkout@v2
- name: REUSE Compliance Check

View file

@ -15,7 +15,7 @@ env:
jobs:
build:
name: Build
runs-on: ubuntu-latest
runs-on: docker
steps:
- uses: actions/checkout@v2
with:
@ -24,12 +24,16 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: '1.20.x'
go-version: '1.22'
- name: Run unit Tests
run: |
go test -v -shuffle=on -race --coverprofile=./cov.out ./...
- name: Install jq
run: |
apt-get update; apt-get -y install jq; which jq
- uses: sonarsource/sonarqube-scan-action@master
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

View file

@ -11,5 +11,5 @@ enable = ["stylecheck", "whitespace", "containedctx", "contextcheck", "decorder"
"errname", "errorlint", "gofmt", "gofumpt", "goimports"]
[linters-settings.goimports]
local-prefixes = "github.com/wneessen/go-meteologix"
local-prefixes = "src.neessen.cloud/wneessen/go-meteologix"

View file

@ -6,11 +6,11 @@ SPDX-License-Identifier: CC0-1.0
# go-meteologix - Go packages for accessing Meteologix/Kachelmann Wetter/WeatherUS data
[![GoDoc](https://godoc.org/github.com/wneessen/go-mail?status.svg)](https://pkg.go.dev/github.com/wneessen/go-meteologix)
[![GoDoc](https://godoc.org/src.neessen.cloud/wneessen/go-mail?status.svg)](https://pkg.go.dev/src.neessen.cloud/wneessen/go-meteologix)
[![codecov](https://codecov.io/gh/wneessen/go-meteologix/branch/main/graph/badge.svg?token=W4QI1RMR4L)](https://codecov.io/gh/wneessen/go-meteologix)
[![Go Report Card](https://goreportcard.com/badge/github.com/wneessen/go-meteologix)](https://goreportcard.com/report/github.com/wneessen/go-meteologix)
[![Go Report Card](https://goreportcard.com/badge/src.neessen.cloud/wneessen/go-meteologix)](https://goreportcard.com/report/src.neessen.cloud/wneessen/go-meteologix)
[![#go-meteologix on Discord](https://img.shields.io/badge/Discord-%23gometeologix-blue.svg)](https://discord.gg/TvNMuDh4pK)
[![REUSE status](https://api.reuse.software/badge/github.com/wneessen/go-meteologix)](https://api.reuse.software/info/github.com/wneessen/go-meteologix)
[![REUSE status](https://api.reuse.software/badge/src.neessen.cloud/wneessen/go-meteologix)](https://api.reuse.software/info/src.neessen.cloud/wneessen/go-meteologix)
<a href="https://ko-fi.com/D1D24V9IX"><img src="https://uploads-ssl.webflow.com/5c14e387dab576fe667689cf/5cbed8a4ae2b88347c06c923_BuyMeACoffee_blue.png" height="20" alt="buy ma a coffee"></a>
<p align="center"><img src="./assets/gopher43.svg" width="250" alt="go-meteologx logo"/></p>
@ -32,7 +32,7 @@ For Geolocation lookups, the package makes use of the
## Usage
The library is fully documented using the execellent GoDoc functionality. Check out
the [full reference on pkg.go.dev](https://pkg.go.dev/github.com/wneessen/go-hibp) for
the [full reference on pkg.go.dev](https://pkg.go.dev/src.neessen.cloud/wneessen/go-hibp) for
details.
## Examples
@ -48,7 +48,7 @@ import (
"fmt"
"os"
"github.com/wneessen/go-meteologix"
"src.neessen.cloud/wneessen/go-meteologix"
)
func main() {
@ -76,7 +76,7 @@ import (
"fmt"
"os"
"github.com/wneessen/go-meteologix"
"src.neessen.cloud/wneessen/go-meteologix"
)
func main() {
@ -106,7 +106,7 @@ import (
"math"
"os"
"github.com/wneessen/go-meteologix"
"src.neessen.cloud/wneessen/go-meteologix"
)
func main() {
@ -139,7 +139,7 @@ import (
"fmt"
"os"
"github.com/wneessen/go-meteologix"
"src.neessen.cloud/wneessen/go-meteologix"
)
func main() {
@ -158,8 +158,14 @@ func main() {
```
## Authors/Contributors
go-meteologix was authored and developed by [Winni Neessen](https://github.com/wneessen/).
go-meteologix was authored and developed by [Winni Neessen](https://src.neessen.cloud/wneessen/).
Big thanks to the following people, for contributing to the go-meteologix project
(either in form of code, reviewing code, writing documenation or contributing in any other form):
* [Maria Letta](https://github.com/MariaLetta) (designed the go-meteologix logo)
## Mirror
Please note that the repository on Github is just a mirror of
[https://src.neessen.cloud/wneessen/go-meteologix](https://src.neessen.cloud/wneessen/go-meteologix)
for ease of access and reachability.

View file

@ -2,7 +2,6 @@
//
// SPDX-License-Identifier: MIT
// Package meteologix provides bindings to the Meteologix/Kachelmann-Wetter weather API
package meteologix
import (
@ -140,7 +139,7 @@ func (a *AstronomicalInfo) Sunset() DateTime {
// available in the AstronomicalInfo it will return DateTime in
// which the "not available" field will be true.
func (a *AstronomicalInfo) SunsetByDateString(ds string) DateTime {
t, err := time.Parse("2006-01-02", ds)
t, err := time.Parse(DateFormat, ds)
if err != nil {
return DateTime{na: true}
}
@ -206,7 +205,7 @@ func (a *AstronomicalInfo) Sunrise() DateTime {
// available in the AstronomicalInfo it will return DateTime in
// which the "not available" field will be true.
func (a *AstronomicalInfo) SunriseByDateString(ds string) DateTime {
t, err := time.Parse("2006-01-02", ds)
t, err := time.Parse(DateFormat, ds)
if err != nil {
return DateTime{na: true}
}

View file

@ -53,11 +53,21 @@ const (
// ConditionMap is a map to associate a specific ConditionType to a nicely
// formatted, human readable string
var ConditionMap = map[ConditionType]string{
CondCloudy: "Cloudy", CondFog: "Fog", CondFreezingRain: "Freezing rain",
CondOvercast: "Overcast", CondPartlyCloudy: "Partly cloudy", CondRain: "Rain",
CondRainHeavy: "Heavy rain", CondShowers: "Showers", CondShowersHeavy: "Heavy showers",
CondSnow: "Snow", CondSnowHeavy: "Heavy snow", CondSnowRain: "Sleet",
CondSunshine: "Clear sky", CondThunderStorm: "Thunderstorm", CondUnknown: "Unknown",
CondCloudy: "Cloudy",
CondFog: "Fog",
CondFreezingRain: "Freezing rain",
CondOvercast: "Overcast",
CondPartlyCloudy: "Partly cloudy",
CondRain: "Rain",
CondRainHeavy: "Heavy rain",
CondShowers: "Showers",
CondShowersHeavy: "Heavy showers",
CondSnow: "Snow",
CondSnowHeavy: "Heavy snow",
CondSnowRain: "Sleet",
CondSunshine: "Clear sky",
CondThunderStorm: "Thunderstorm",
CondUnknown: "Unknown",
}
// Condition is a type wrapper of an WeatherData for holding

View file

@ -50,6 +50,8 @@ type APICurrentWeatherData struct {
PressureQFE *APIFloat `json:"pressure,omitempty"`
// SnowAmount represents the the amount of snow in kg/m3
SnowAmount *APIFloat `json:"snowAmount,omitempty"`
// SnowHeight represents the the height of snow in m
SnowHeight *APIFloat `json:"snowHeight,omitempty"`
// Temperature represents the temperature in °C
Temperature *APIFloat `json:"temp,omitempty"`
// WindDirection represents the direction from which the wind
@ -62,31 +64,6 @@ type APICurrentWeatherData struct {
// WeatherSymbol is a text representation of the current weather
// conditions
WeatherSymbol *APIString `json:"weatherSymbol,omitempty"`
/*
// DewPointMean represents the mean dewpoint in °C
DewpointMean *APIFloat `json:"dewpointMean,omitempty"`
// GlobalRadiation10m represents the sum of global radiation over the last
// 10 minutes in kJ/m²
GlobalRadiation10m *APIFloat `json:"globalRadiation10m,omitempty"`
// GlobalRadiation1h represents the sum of global radiation over the last
// 1 hour in kJ/m²
GlobalRadiation1h *APIFloat `json:"globalRadiation1h,omitempty"`
// GlobalRadiation24h represents the sum of global radiation over the last
// 24 hour in kJ/m²
GlobalRadiation24h *APIFloat `json:"globalRadiation24h,omitempty"`
// TemperatureMax represents the maximum temperature in °C
TemperatureMax *APIFloat `json:"tempMax,omitempty"`
// TemperatureMean represents the mean temperature in °C
TemperatureMean *APIFloat `json:"tempMean,omitempty"`
// TemperatureMin represents the minimum temperature in °C
TemperatureMin *APIFloat `json:"tempMin,omitempty"`
// Temperature5cm represents the temperature 5cm above ground in °C
Temperature5cm *APIFloat `json:"temp5cm,omitempty"`
// Temperature5cm represents the minimum temperature 5cm above
// ground in °C
Temperature5cmMin *APIFloat `json:"temp5cmMin,omitempty"`
*/
}
// CurrentWeatherByCoordinates returns the CurrentWeather values for the given coordinates
@ -146,7 +123,7 @@ func (cw CurrentWeather) Dewpoint() Temperature {
// If the data point is not available in the CurrentWeather it will return
// Humidity in which the "not available" field will be true.
func (cw CurrentWeather) HumidityRelative() Humidity {
if cw.Data.Dewpoint == nil {
if cw.Data.HumidityRelative == nil {
return Humidity{na: true}
}
v := Humidity{
@ -249,7 +226,7 @@ func (cw CurrentWeather) PressureQFE() Pressure {
return v
}
// SnowAmount returns the temperature data point as Density.
// SnowAmount returns the amount of snow data point as Density.
// If the data point is not available in the CurrentWeather it will return
// Density in which the "not available" field will be true.
func (cw CurrentWeather) SnowAmount() Density {
@ -268,6 +245,25 @@ func (cw CurrentWeather) SnowAmount() Density {
return v
}
// SnowHeight returns the snow height data point as Height.
// If the data point is not available in the CurrentWeather it will return
// Height in which the "not available" field will be true.
func (cw CurrentWeather) SnowHeight() Height {
if cw.Data.SnowHeight == nil {
return Height{na: true}
}
v := Height{
dt: cw.Data.SnowHeight.DateTime,
n: FieldSnowHeight,
s: SourceUnknown,
fv: cw.Data.SnowHeight.Value,
}
if cw.Data.SnowHeight.Source != nil {
v.s = StringToSource(*cw.Data.SnowHeight.Source)
}
return v
}
// Temperature returns the temperature data point as Temperature.
// If the data point is not available in the CurrentWeather it will return
// Temperature in which the "not available" field will be true.

View file

@ -626,6 +626,103 @@ func TestClient_CurrentWeatherByLocation_SnowAmount(t *testing.T) {
}
}
func TestClient_CurrentWeatherByLocation_SnowHeight(t *testing.T) {
tt := []struct {
// Location name
loc string
// CurWeather height
h *Height
}{
{"Ehrenfeld, Germany", &Height{
dt: time.Date(2023, 5, 23, 6, 0, 0, 0, time.UTC),
s: SourceAnalysis,
fv: 1.23,
}},
{"Berlin, Germany", &Height{
dt: time.Date(2023, 5, 23, 6, 0, 0, 0, time.UTC),
s: SourceAnalysis,
fv: 0.003,
}},
{"Neermoor, Germany", nil},
}
c := New(withMockAPI())
if c == nil {
t.Errorf("failed to create new Client, got nil")
return
}
for _, tc := range tt {
t.Run(tc.loc, func(t *testing.T) {
cw, err := c.CurrentWeatherByLocation(tc.loc)
if err != nil {
t.Errorf("CurrentWeatherByLocation failed: %s", err)
return
}
if tc.h != nil && tc.h.String() != cw.SnowHeight().String() {
t.Errorf("CurrentWeatherByLocation failed, expected snow height "+
"string: %s, got: %s", tc.h.String(), cw.SnowHeight())
}
if tc.h != nil && tc.h.Value() != cw.SnowHeight().Value() {
t.Errorf("CurrentWeatherByLocation failed, expected snow height "+
"float: %f, got: %f", tc.h.Value(), cw.SnowHeight().Value())
}
if tc.h != nil && tc.h.MeterString() != cw.SnowHeight().MeterString() {
t.Errorf("CurrentWeatherByLocation failed, expected snow height "+
"string: %s, got: %s", tc.h.MeterString(), cw.SnowHeight().MeterString())
}
if tc.h != nil && tc.h.Meter() != cw.SnowHeight().Meter() {
t.Errorf("CurrentWeatherByLocation failed, expected snow height "+
"float: %f, got: %f", tc.h.Meter(), cw.SnowHeight().Meter())
}
if tc.h != nil && tc.h.CentiMeterString() != cw.SnowHeight().CentiMeterString() {
t.Errorf("CurrentWeatherByLocation failed, expected snow height "+
"string: %s, got: %s", tc.h.CentiMeterString(), cw.SnowHeight().CentiMeterString())
}
if tc.h != nil && tc.h.CentiMeter() != cw.SnowHeight().CentiMeter() {
t.Errorf("CurrentWeatherByLocation failed, expected snow height "+
"float: %f, got: %f", tc.h.CentiMeter(), cw.SnowHeight().CentiMeter())
}
if tc.h != nil && tc.h.MilliMeterString() != cw.SnowHeight().MilliMeterString() {
t.Errorf("CurrentWeatherByLocation failed, expected snow height "+
"string: %s, got: %s", tc.h.MilliMeterString(), cw.SnowHeight().MilliMeterString())
}
if tc.h != nil && tc.h.MilliMeter() != cw.SnowHeight().MilliMeter() {
t.Errorf("CurrentWeatherByLocation failed, expected snow height "+
"float: %f, got: %f", tc.h.MilliMeter(), cw.SnowHeight().MilliMeter())
}
if tc.h != nil && cw.SnowHeight().Source() != tc.h.s {
t.Errorf("CurrentWeatherByLocation failed, expected source: %s, but got: %s",
tc.h.s, cw.SnowHeight().Source())
}
if tc.h != nil && tc.h.dt.Unix() != cw.SnowHeight().DateTime().Unix() {
t.Errorf("CurrentWeatherByLocation failed, expected datetime: %s, got: %s",
tc.h.dt.Format(time.RFC3339), cw.SnowHeight().DateTime().Format(time.RFC3339))
}
if tc.h == nil {
if cw.SnowHeight().IsAvailable() {
t.Errorf("CurrentWeatherByLocation failed, expected snow height "+
"to have no data, but got: %s", cw.SnowHeight())
}
if !math.IsNaN(cw.SnowHeight().Value()) {
t.Errorf("CurrentWeatherByLocation failed, expected snow height "+
"to return NaN, but got: %s", cw.SnowHeight().String())
}
if !math.IsNaN(cw.SnowHeight().Meter()) {
t.Errorf("CurrentWeatherByLocation failed, expected snow height "+
"to return NaN, but got: %f", cw.SnowHeight().Meter())
}
if !math.IsNaN(cw.SnowHeight().CentiMeter()) {
t.Errorf("CurrentWeatherByLocation failed, expected snow height "+
"to return NaN, but got: %f", cw.SnowHeight().CentiMeter())
}
if !math.IsNaN(cw.SnowHeight().MilliMeter()) {
t.Errorf("CurrentWeatherByLocation failed, expected snow height "+
"to return NaN, but got: %f", cw.SnowHeight().MilliMeter())
}
}
})
}
}
func TestClient_CurrentWeatherByLocation_Temperature(t *testing.T) {
tt := []struct {
// Location name

View file

@ -46,6 +46,8 @@ const (
FieldPressureQFE
// FieldSnowAmount represents the SnowAmount data point
FieldSnowAmount
// FieldSnowHeight represents the SnowHeight data point
FieldSnowHeight
// FieldSunrise represents the Sunrise data point
FieldSunrise
// FieldSunset represents the Sunset data point

View file

@ -36,5 +36,5 @@ func (dt DateTime) Value() time.Time {
// String satisfies the fmt.Stringer interface for the DateTime type
// The date will returned as time.RFC3339 format
func (dt DateTime) String() string {
return dt.dv.Format(time.RFC3339)
return dt.Value().Format(time.RFC3339)
}

View file

@ -10,8 +10,8 @@ import (
"time"
)
// Density is a type wrapper of an WeatherData for holding density
// values in WeatherData
// Density is a type wrapper of WeatherData for holding density
// values in kg/m³ in WeatherData
type Density WeatherData
// IsAvailable returns true if an Density value was
@ -20,8 +20,7 @@ func (d Density) IsAvailable() bool {
return !d.na
}
// DateTime returns true if an Density value was
// available at time of query
// DateTime returns the DateTime of the queried Density value
func (d Density) DateTime() time.Time {
return d.dt
}
@ -38,7 +37,7 @@ func (d Density) Source() Source {
}
// Value returns the float64 value of an Density
// If the Density is not available in the Observation
// If the Density is not available in the WeatherData
// Vaule will return math.NaN instead.
func (d Density) Value() float64 {
if d.na {

View file

@ -12,10 +12,10 @@ import (
)
const (
// DirectionMinAngel is the minimum angel for a direction
DirectionMinAngel = 0
// DirectionMaxAngel is the maximum angel for a direction
DirectionMaxAngel = 360
// DirectionMinAngle is the minimum angel for a direction
DirectionMinAngle = 0
// DirectionMaxAngle is the maximum angel for a direction
DirectionMaxAngle = 360
)
// WindDirAbbrMap is a map to associate a wind direction degree value with
@ -62,7 +62,7 @@ func (d Direction) DateTime() time.Time {
}
// Value returns the float64 value of an Direction in degrees
// If the Direction is not available in the Observation
// If the Direction is not available in the WeatherData
// Vaule will return math.NaN instead.
func (d Direction) Value() float64 {
if d.na {
@ -84,7 +84,7 @@ func (d Direction) Source() Source {
// Direction returns the abbreviation string for a given Direction type
func (d Direction) Direction() string {
if d.fv < DirectionMinAngel || d.fv > DirectionMaxAngel {
if d.fv < DirectionMinAngle || d.fv > DirectionMaxAngle {
return ErrUnsupportedDirection
}
if ds, ok := WindDirAbbrMap[d.fv]; ok {
@ -95,7 +95,7 @@ func (d Direction) Direction() string {
// DirectionFull returns the full string for a given Direction type
func (d Direction) DirectionFull() string {
if d.fv < DirectionMinAngel || d.fv > DirectionMaxAngel {
if d.fv < DirectionMinAngle || d.fv > DirectionMaxAngle {
return ErrUnsupportedDirection
}
if ds, ok := WindDirFullMap[d.fv]; ok {
@ -125,8 +125,8 @@ func findDirection(v float64, m map[float64]string) string {
break
}
}
sr := math.Mod(v, sv)
er := math.Mod(ev, v)
sr := v - sv
er := ev - v
if er > sr {
return m[sv]
}

37
direction_test.go Normal file
View file

@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: 2023 Winni Neessen <wn@neessen.dev>
//
// SPDX-License-Identifier: MIT
package meteologix
import (
"testing"
)
func TestFindDirection(t *testing.T) {
// Prepare test cases
tt := []struct {
v float64
dm map[float64]string
er string
}{
{15, WindDirAbbrMap, "NbE"},
{47, WindDirAbbrMap, "NE"},
{200, WindDirAbbrMap, "SSW"},
{330, WindDirAbbrMap, "NWbN"},
{15, WindDirFullMap, "North by East"},
{47, WindDirFullMap, "Northeast"},
{200, WindDirFullMap, "South-Southwest"},
{330, WindDirFullMap, "Northwest by North"},
}
// Run tests
for _, tc := range tt {
t.Run("", func(t *testing.T) {
r := findDirection(tc.v, tc.dm)
if tc.er != r {
t.Errorf("findDirection failed, expected: %s, got: %s", tc.er, r)
}
})
}
}

2
doc.go
View file

@ -6,4 +6,4 @@
package meteologix
// VERSION represents the current version of the package
const VERSION = "0.1.0"
const VERSION = "0.1.1"

View file

@ -43,7 +43,7 @@ type GeoLocation struct {
// This method makes use of the OSM Nominatim API
func (c *Client) GetGeoLocationByName(ci string) (GeoLocation, error) {
ga, err := c.GetGeoLocationsByName(ci)
if len(ga) < 1 {
if err != nil || len(ga) < 1 {
return GeoLocation{}, err
}
return ga[0], nil

2
go.mod
View file

@ -2,6 +2,6 @@
//
// SPDX-License-Identifier: MIT
module github.com/wneessen/go-meteologix
module src.neessen.cloud/wneessen/go-meteologix
go 1.20

85
height.go Normal file
View file

@ -0,0 +1,85 @@
// SPDX-FileCopyrightText: 2023 Winni Neessen <wn@neessen.dev>
//
// SPDX-License-Identifier: MIT
package meteologix
import (
"fmt"
"math"
"time"
)
// Height is a type wrapper of an WeatherData for holding height
// values in WeatherData (based on meters a default unit)
type Height WeatherData
// IsAvailable returns true if an Height value was
// available at time of query
func (h Height) IsAvailable() bool {
return !h.na
}
// DateTime returns the timestamp associated with the Height value
func (h Height) DateTime() time.Time {
return h.dt
}
// String satisfies the fmt.Stringer interface for the Height type
func (h Height) String() string {
return fmt.Sprintf("%.3fm", h.fv)
}
// Source returns the Source of Height
// If the Source is not available it will return SourceUnknown
func (h Height) Source() Source {
return h.s
}
// Value returns the float64 value of an Height
// If the Height is not available in the WeatherData
// Vaule will return math.NaN instead.
func (h Height) Value() float64 {
if h.na {
return math.NaN()
}
return h.fv
}
// Meter returns the Height type value as float64 in meters.
// This is an alias for the Value() method
func (h Height) Meter() float64 {
return h.Value()
}
// MeterString returns the Height type as formatted string in meters
// This is an alias for the String() method
func (h Height) MeterString() string {
return h.String()
}
// CentiMeter returns the Height type value as float64 in centimeters.
func (h Height) CentiMeter() float64 {
if h.na {
return math.NaN()
}
return h.fv * 100
}
// CentiMeterString returns the Height type as formatted string in centimeters
func (h Height) CentiMeterString() string {
return fmt.Sprintf("%.3fcm", h.CentiMeter())
}
// MilliMeter returns the Height type value as float64 in milliimeters.
func (h Height) MilliMeter() float64 {
if h.na {
return math.NaN()
}
return h.fv * 1000
}
// MilliMeterString returns the Height type as formatted string in millimeters
func (h Height) MilliMeterString() string {
return fmt.Sprintf("%.3fmm", h.MilliMeter())
}

View file

@ -13,9 +13,9 @@ import (
"errors"
"fmt"
"io"
"log"
"net/http"
"net/url"
"os"
"strings"
"time"
)
@ -49,11 +49,13 @@ type APIError struct {
// NewHTTPClient returns a new HTTP client
func NewHTTPClient(c *Config) *HTTPClient {
tc := &tls.Config{
MaxVersion: tls.VersionTLS12,
MinVersion: tls.VersionTLS12,
}
ht := http.Transport{TLSClientConfig: tc}
hc := &http.Client{Transport: &ht}
ht := &http.Transport{TLSClientConfig: tc}
hc := &http.Client{
Timeout: HTTPClientTimeout,
Transport: ht,
}
return &HTTPClient{c, hc}
}
@ -78,31 +80,28 @@ func (hc *HTTPClient) GetWithTimeout(u string, t time.Duration) ([]byte, error)
// User authentication (only required for Meteologix API calls)
if strings.HasPrefix(u, APIBaseURL) {
hc.setAuthHeader(hr)
hc.setAuthentication(hr)
}
sr, err := hc.Do(hr)
if err != nil {
return nil, err
}
defer func() {
if err = sr.Body.Close(); err != nil {
_, _ = fmt.Fprintln(os.Stderr, "failed to close HTTP request body", err)
if sr == nil {
return nil, errors.New("nil response received")
}
defer func(b io.ReadCloser) {
if err = b.Close(); err != nil {
log.Printf("failed to close HTTP request body: %s", err)
}
}()
}(sr.Body)
if !strings.HasPrefix(sr.Header.Get("Content-Type"), MIMETypeJSON) {
return nil, ErrNonJSONResponse
}
buf := &bytes.Buffer{}
bw := bufio.NewWriter(buf)
_, err = io.Copy(bw, sr.Body)
if err != nil {
return nil, fmt.Errorf("failed to copy HTTP response body to buffer: %w", err)
}
if sr.StatusCode >= 400 {
var ae APIError
if err = json.Unmarshal(buf.Bytes(), &ae); err != nil {
if sr.StatusCode >= http.StatusBadRequest {
ae := new(APIError)
if err = json.NewDecoder(sr.Body).Decode(ae); err != nil {
return nil, fmt.Errorf("failed to unmarshal error JSON: %w", err)
}
if ae.Code < 1 {
@ -111,19 +110,33 @@ func (hc *HTTPClient) GetWithTimeout(u string, t time.Duration) ([]byte, error)
if ae.Details == "" {
ae.Details = sr.Status
}
return nil, ae
return nil, *ae
}
buf := &bytes.Buffer{}
bw := bufio.NewWriter(buf)
_, err = io.Copy(bw, sr.Body)
if err != nil {
return nil, fmt.Errorf("failed to copy HTTP response body to buffer: %w", err)
}
if err = bw.Flush(); err != nil {
return nil, fmt.Errorf("failed to flush buffer: %w", err)
}
return buf.Bytes(), nil
}
// setAuthHeader sets the corresponding user authentication header. If an API Key is set, this
// setAuthentication sets the corresponding user authentication header. If an API Key is set, this
// will be preferred, alternatively a username/authPass combination for HTTP Basic auth can
// be used
func (hc *HTTPClient) setAuthHeader(hr *http.Request) {
func (hc *HTTPClient) setAuthentication(hr *http.Request) {
if hc.apiKey != "" {
hr.Header.Set("X-API-Key", hc.Config.apiKey)
return
}
if hc.bearerToken != "" {
hr.Header.Set("Authorization", "Bearer"+hc.bearerToken)
return
}
if hc.authUser != "" && hc.authPass != "" {
hr.SetBasicAuth(url.QueryEscape(hc.authUser), url.QueryEscape(hc.authPass))
}

View file

@ -20,8 +20,8 @@ func (h Humidity) IsAvailable() bool {
return !h.na
}
// DateTime returns true if an Humidity value was
// available at time of query
// DateTime returns the timestamp of when the humidity
// measurement was taken.
func (h Humidity) DateTime() time.Time {
return h.dt
}
@ -38,8 +38,8 @@ func (h Humidity) Source() Source {
}
// Value returns the float64 value of an Humidity
// If the Humidity is not available in the Observation
// Vaule will return math.NaN instead.
// If the Humidity is not available in the WeatherData
// Value will return math.NaN instead.
func (h Humidity) Value() float64 {
if h.na {
return math.NaN()

View file

@ -22,7 +22,7 @@ const (
// DefaultUserAgent is the default User-Agent presented by the HTTPClient
var DefaultUserAgent = fmt.Sprintf("go-meteologix/fv%s (%s; %s; "+
"+https://github.com/wneessen/go-meteologix)", VERSION, runtime.GOOS,
"+https://src.neessen.cloud/wneessen/go-meteologix)", VERSION, runtime.GOOS,
runtime.Version())
// Client represents the Meteologix API Client
@ -46,6 +46,8 @@ type Config struct {
authPass string
// authUser holds the (optional) username for the API user authentication
authUser string
// bearerToken holds the (optional) bearer token for the API authentication
bearerToken string
// userAgent represents an alternative User-Agent HTTP header string
userAgent string
}
@ -97,6 +99,17 @@ func WithAPIKey(k string) Option {
}
}
// WithBearerToken uses a bearer token for the client authentication of the
// HTTP client
func WithBearerToken(t string) Option {
if t == "" {
return nil
}
return func(co *Config) {
co.bearerToken = t
}
}
// WithPassword sets the HTTP Basic auth authPass for the HTTP client
func WithPassword(p string) Option {
if p == "" {

View file

@ -66,6 +66,28 @@ func TestNew_WithAPIKey(t *testing.T) {
}
}
func TestNew_WithBearerToken(t *testing.T) {
e := "BEARER-TOKEN"
c := New(WithBearerToken(e))
if c == nil {
t.Errorf("NewWithBearerToken failed, expected Client, got nil")
return
}
if c.config.bearerToken != e {
t.Errorf("NewWithBearerToken failed, expected token value: %s, got: %s", e,
c.config.bearerToken)
}
c = New(WithBearerToken(""))
if c == nil {
t.Errorf("NewWithBearerToken failed, expected Client, got nil")
return
}
if c.config.bearerToken != "" {
t.Errorf("NewWithBearerToken failed, expected empty token, got: %s",
c.config.bearerToken)
}
}
func TestNew_WithUsername(t *testing.T) {
e := "username"
c := New(WithUsername(e))

View file

@ -20,8 +20,7 @@ func (p Precipitation) IsAvailable() bool {
return !p.na
}
// DateTime returns true if an Precipitation value was
// available at time of query
// DateTime returns the DateTime when the Precipitation value was recorded
func (p Precipitation) DateTime() time.Time {
return p.dt
}
@ -38,7 +37,7 @@ func (p Precipitation) Source() Source {
}
// Value returns the float64 value of an Precipitation
// If the Precipitation is not available in the Observation
// If the Precipitation is not available in the WeatherData
// Vaule will return math.NaN instead.
func (p Precipitation) Value() float64 {
if p.na {

View file

@ -20,8 +20,7 @@ func (p Pressure) IsAvailable() bool {
return !p.na
}
// DateTime returns true if an Pressure value was
// available at time of query
// DateTime returns the date and time of the Pressure reading
func (p Pressure) DateTime() time.Time {
return p.dt
}
@ -38,7 +37,7 @@ func (p Pressure) Source() Source {
}
// Value returns the float64 value of an Pressure
// If the Pressure is not available in the Observation
// If the Pressure is not available in the WeatherData
// Vaule will return math.NaN instead.
func (p Pressure) Value() float64 {
if p.na {

View file

@ -20,14 +20,14 @@ func (r Radiation) IsAvailable() bool {
return !r.na
}
// DateTime returns true if an Radiation value was
// available at time of query
// DateTime returns the time.Time object representing the date and time
// at which the Radiation value was queried
func (r Radiation) DateTime() time.Time {
return r.dt
}
// Value returns the float64 value of an Radiation
// If the Radiation is not available in the Observation
// If the Radiation is not available in the WeatherData
// Vaule will return math.NaN instead.
func (r Radiation) Value() float64 {
if r.na {

View file

@ -29,15 +29,14 @@ func (s Speed) IsAvailable() bool {
return !s.na
}
// DateTime returns true if an Speed value was
// available at time of query
// DateTime returns the DateTime when the Speed was checked
func (s Speed) DateTime() time.Time {
return s.dt
}
// Value returns the float64 value of an Speed in meters
// per second.
// If the Speed is not available in the Observation
// If the Speed is not available in the WeatherData
// Vaule will return math.NaN instead.
func (s Speed) Value() float64 {
if s.na {

View file

@ -18,16 +18,42 @@ import (
const DefaultRadius int = 10
const (
// PrecisionHigh is a high precision weather station
PrecisionHigh Precision = iota
// PrecisionMedium is a medium precision weather station
PrecisionMedium
// PrecisionLow is a low precision weather station
PrecisionLow
// PrecisionUnknown is weather station of unknown precision
// PrecisionSuperHigh represents the precision level of data corresponding
// to a resolution of less than or approximately equal to 4 kilometers.
// This is the highest level of precision, usually associated with highly
// detailed measurements or observations.
PrecisionSuperHigh Precision = iota
// PrecisionHigh represents the precision level of data corresponding to a
// resolution between 4 kilometers and 10 kilometers. This is a high precision
// level, suitable for most operational needs that require a balance between
// detail and processing requirements.
PrecisionHigh
// PrecisionStandard represents the precision level of data corresponding to
// a resolution of 10 kilometers or more. This is the standard level of
// precision, generally used for large-scale analysis and modeling.
PrecisionStandard
// PrecisionUnknown is used when the precision level of a weather station
// is unknown. This constant can be used as a placeholder when the resolution
// data is not available.
PrecisionUnknown
)
// Precision levels defined as strings to allow for clear, consistent
// use throughout the application.
const (
// PrecisionStringSuperHigh represents the super high precision level string.
PrecisionStringSuperHigh = "SUPER_HIGH"
// PrecisionStringHigh represents the high precision level string.
PrecisionStringHigh = "HIGH"
// PrecisionStringStandard represents the standard precision level string.
PrecisionStringStandard = "STANDARD"
// PrecisionStringUnknown represents an unknown precision level string.
PrecisionStringUnknown = "UNKNOWN"
)
var (
// ErrRadiusTooSmall is returned if a given radius value is too small
ErrRadiusTooSmall = errors.New("given radius is too small")
@ -156,13 +182,13 @@ func (c *Client) StationSearchByCoordinatesWithinRadius(la, lo float64, ra int)
func (p *Precision) UnmarshalJSON(s []byte) error {
v := string(s)
v = strings.ReplaceAll(v, `"`, ``)
switch strings.ToLower(v) {
case "high":
switch strings.ToUpper(v) {
case PrecisionStringSuperHigh:
*p = PrecisionSuperHigh
case PrecisionStringHigh:
*p = PrecisionHigh
case "medium":
*p = PrecisionMedium
case "low":
*p = PrecisionLow
case PrecisionStringStandard:
*p = PrecisionStandard
default:
*p = PrecisionUnknown
}
@ -172,15 +198,15 @@ func (p *Precision) UnmarshalJSON(s []byte) error {
// String satisfies the fmt.Stringer interface for the Precision type
func (p *Precision) String() string {
switch *p {
case PrecisionSuperHigh:
return PrecisionStringSuperHigh
case PrecisionHigh:
return "HIGH"
case PrecisionMedium:
return "MEDIUM"
case PrecisionLow:
return "LOW"
return PrecisionStringHigh
case PrecisionStandard:
return PrecisionStringStandard
case PrecisionUnknown:
return "UNKNOWN"
return PrecisionStringUnknown
default:
return "UNKNOWN"
return PrecisionStringUnknown
}
}

View file

@ -46,7 +46,8 @@ func TestClient_StationSearchByLocation_Fail(t *testing.T) {
t.Errorf("StationSearchByLocation was supposed to fail but didn't")
}
if err != nil && !errors.As(err, &APIError{}) {
t.Errorf("StationSearchByLocation was supposed to throw a APIError but didn't")
t.Errorf("StationSearchByLocation was supposed to throw a APIError but didn't: %s",
err)
}
c = New(WithAPIKey("invalid"))
_, err = c.StationSearchByLocation("Cologne, Germany")
@ -55,7 +56,8 @@ func TestClient_StationSearchByLocation_Fail(t *testing.T) {
return
}
if err != nil && !errors.As(err, &APIError{}) {
t.Errorf("StationSearchByLocation was supposed to throw a APIError but didn't")
t.Errorf("StationSearchByLocation was supposed to throw a APIError but didn't: %s",
err)
}
}
@ -164,16 +166,16 @@ func TestPrecision_UnmarshalJSON(t *testing.T) {
// Should fail
sf bool
}{
{
"Super high precision", []byte(`{"precision":"SUPER_HIGH"}`), PrecisionSuperHigh,
false,
},
{
"High precision", []byte(`{"precision":"HIGH"}`), PrecisionHigh,
false,
},
{
"Medium precision", []byte(`{"precision":"MEDIUM"}`), PrecisionMedium,
false,
},
{
"Low precision", []byte(`{"precision":"LOW"}`), PrecisionLow,
"Standard precision", []byte(`{"precision":"STANDARD"}`), PrecisionStandard,
false,
},
{
@ -209,9 +211,9 @@ func TestPrecision_String(t *testing.T) {
// Expected string
es string
}{
{"Super high precision", PrecisionSuperHigh, "SUPER_HIGH"},
{"High precision", PrecisionHigh, "HIGH"},
{"Medium precision", PrecisionMedium, "MEDIUM"},
{"Low precision", PrecisionLow, "LOW"},
{"Standard precision", PrecisionStandard, "STANDARD"},
{"Unknown precision", PrecisionUnknown, "UNKNOWN"},
{"Undefined precision", 999, "UNKNOWN"},
}

View file

@ -20,14 +20,14 @@ func (t Temperature) IsAvailable() bool {
return !t.na
}
// DateTime returns true if an Temperature value was
// available at time of query
// DateTime returns the time at which the temperature data was
// generated or fetched
func (t Temperature) DateTime() time.Time {
return t.dt
}
// Value returns the float64 value of an Temperature
// If the Temperature is not available in the Observation
// If the Temperature is not available in the WeatherData
// Vaule will return math.NaN instead.
func (t Temperature) Value() float64 {
if t.na {