diff --git a/.travis.yml b/.travis.yml index 28614c4..729630c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: go go: - - 1.5.4 - - 1.6.2 + - 1.15.8 + - 1.16.2 env: - GOARCH: amd64 - GOARCH: 386 diff --git a/Makefile b/Makefile index 2398262..7305bbe 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,4 @@ -GOCMD = go -GOBUILD = $(GOCMD) build -GOGET = $(GOCMD) get -v -GOCLEAN = $(GOCMD) clean -GOINSTALL = $(GOCMD) install -GOTEST = $(GOCMD) test +GO = go .PHONY: all @@ -11,12 +6,5 @@ all: test .PHONY: test test: - $(GOTEST) -v -covermode=count -coverprofile=coverage.out ./... + $(GO) test -v -covermode=count -coverprofile=coverage.out ./... -.PHONY: build -build: test - $(GOBUILD) - -.PHONY: install -install: test - $(GOINSTALL) diff --git a/README.md b/README.md index cfe1db1..0662e6a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![GoDoc](https://godoc.org/github.com/briandowns/openweathermap?status.svg)](https://godoc.org/github.com/briandowns/openweathermap) [![Build Status](https://travis-ci.org/briandowns/openweathermap.svg?branch=master)](https://travis-ci.org/briandowns/openweathermap) [![Coverage Status](https://coveralls.io/repos/github/briandowns/openweathermap/badge.svg?branch=master)](https://coveralls.io/github/briandowns/openweathermap?branch=master) -Go (golang) package for use with openweathermap.org's API. +Go (golang) package for use with openweathermap.org's HTTP API. For more detail about the library and its features, reference your local godoc once installed. @@ -71,7 +71,25 @@ Gain access to OpenWeatherMap icons and condition codes. ## Supported Languages -English - en, Russian - ru, Italian - it, Spanish - es (or sp), Ukrainian - uk (or ua), German - de, Portuguese - pt, Romanian - ro, Polish - pl, Finnish - fi, Dutch - nl, French - fr, Bulgarian - bg, Swedish - sv (or se), Chinese Traditional - zh_tw, Chinese Simplified - zh (or zh_cn), Turkish - tr, Croatian - hr, Catalan - ca +- English - en +- Russian - ru +- Italian - it +- Spanish - es (or sp) +- Ukrainian - uk (or ua) +- German - de +- Portuguese - pt +- Romanian - ro +- Polish - pl +- Finnish - fi +- Dutch - nl +- French - fr +- Bulgarian - bg +- Swedish - sv (or se) +- Chinese Traditional - zh_tw +- Chinese Simplified - zh (or zh_cn) +- Turkish - tr +- Croatian - hr +- Catalan - ca ## Installation @@ -81,7 +99,7 @@ go get github.com/briandowns/openweathermap ## Examples -There are a few full examples in the examples directory that can be referenced. 1 is a command line application and 1 is a simple web application. +Full, simple example. ```Go package main @@ -91,189 +109,100 @@ import ( "fmt" "os" - // Shortening the import reference name seems to make it a bit easier - owm "github.com/briandowns/openweathermap" + "github.com/briandowns/openweathermap" ) -var apiKey = os.Getenv("OWM_API_KEY") - func main() { - w, err := owm.NewCurrent("F", "ru", apiKey) // fahrenheit (imperial) with Russian output + opts := openweathermap.Opts{ + Lang: "EN", + Unit: "F", + Client: &http.Client{ + Timeout: time.Second * 5, + }, + } + owm, err := openweathermap.New(&opts) if err != nil { log.Fatalln(err) } - w.CurrentByName("Phoenix") - fmt.Println(w) -} - -``` - -### Current Conditions by location name - -```Go -func main() { - w, err := owm.NewCurrent("K", "EN", apiKey) // (internal - OpenWeatherMap reference for kelvin) with English output - if err != nil { - log.Fatalln(err) - } - - w.CurrentByName("Phoenix,AZ") - fmt.Println(w) + cbn, err := owm.CurrentByName("Philadelphia") + if err != nil { + log.Fatalln(err) + } + fmt.Printf("%#v\n", cbn) } ``` ### Forecast Conditions in imperial (fahrenheit) by coordinates ```Go -func main() { - w, err := owm.NewForecast("5", "F", "FI", apiKey) // valid options for first parameter are "5" and "16" - if err != nil { - log.Fatalln(err) - } - - w.DailyByCoordinates( - &owm.Coordinates{ - Longitude: -112.07, - Latitude: 33.45, - }, - 5 // five days forecast - ) - fmt.Println(w) +fdfbc, err := owm.FiveDayForecastByCoordinates(&openweathermap.Coordinates{Longitude: -75.1638, Latitude: 39.9523}, 10) +if err != nil { + log.Fatalln(err) } +fmt.Printf("%#v\n", fdfbc) ``` ### Current conditions in metric (celsius) by location ID ```Go -func main() { - w, err := owm.NewCurrent("C", "PL", apiKey) - if err != nil { - log.Fatalln(err) - } - - w.CurrentByID(2172797) - fmt.Println(w) +owm.Unit = "C" +cbi, err := owm.CurrentByID(4560349) +if err != nil { + log.Fatalln(err) } +fmt.Printf("%#v\n", cbi) ``` ### Current conditions by zip code. 2 character country code required ```Go -func main() { - w, err := owm.NewCurrent("F", "EN", apiKey) - if err != nil { - log.Fatalln(err) - } - - w.CurrentByZip(19125, "US") - fmt.Println(w) +cbz, err := owm.CurrentByZip("19127", "") +if err != nil { + log.Fatalln(err) } +fmt.Printf("%#v\n", cbz) ``` -### Configure http client +### History by Name ```Go -func main() { - client := &http.Client{} - w, err := owm.NewCurrent("F", "EN", apiKey, owm.WithHttpClient(client)) - if err != nil { - log.Fatalln(err) - } +hbn, err := owm.HistoryByName("Philadelphia", &openweathermap.HistoricalParameters{ + Start: 1369728000, + End: 1369789200, + Cnt: 4, +}) +if err != nil { + log.Fatalln(err) } +fmt.Printf("%#v\n", hbn) ``` ### Current UV conditions ```Go -func main() { - uv, err := owm.NewUV(apiKey) - if err != nil { - log.Fatalln(err) - } - - coord := &owm.Coordinates{ - Longitude: 53.343497, - Latitude: -6.288379, - } - - if err := uv.Current(coord); err != nil { - log.Fatalln(err) - } - - fmt.Println(coord) -} -``` - -### Historical UV conditions - -```Go -func main() { - uv, err := owm.NewUV(apiKey) - if err != nil { - log.Fatalln(err) - } - - coord := &owm.Coordinates{ - Longitude: 54.995656, - Latitude: -7.326834, - } - - end := time.Now().UTC() - start := time.Now().UTC().Add(-time.Hour * time.Duration(24)) - - if err := uv.Historical(coord, start, end); err != nil { - log.Fatalln(err) - } -} -``` - -### UV Information - -```Go -func main() { - uv, err := owm.NewUV(apiKey) - if err != nil { - log.Fatalln(err) - } - - coord := &owm.Coordinates{ - Longitude: 53.343497, - Latitude: -6.288379, - } - - if err := uv.Current(coord); err != nil { - log.Fatalln(err) - } - - info, err := uv.UVInformation() - if err != nil { - log.Fatalln(err) - } - - fmt.Println(info) +uv, err := owm.UVCurrent(&openweathermap.Coordinates{ + Latitude: 39.9523, + Longitude: -75.1638, +}) +if err != nil { + log.Fatalln(err) } +fmt.Printf("%#v\n", uv) ``` ### Pollution Information ```Go -func main() { - pollution, err := owm.NewPollution(apiKey) - if err != nil { - log.Fatalln(err) - } - - params := &owm.PollutionParameters{ - Location: owm.Coordinates{ - Latitude: 0.0, - Longitude: 10.0, - }, - Datetime: "current", - } - - if err := pollution.PollutionByParams(params); err != nil { - log.Fatalln(err) - } +p, err := owm.PollutionByParams(&openweathermap.PollutionParameters{ + Location: openweathermap.Coordinates{ + Latitude: 39.9523, + Longitude: -75.1638, + }, + Datetime: "2006-01-02T15:04:05-0700", +}) +if err != nil { + log.Fatalln(err) } -``` +fmt.Printf("%#v\n", p) +``` \ No newline at end of file diff --git a/_examples/cli/weather.go b/_examples/cli/weather.go deleted file mode 100644 index 72db125..0000000 --- a/_examples/cli/weather.go +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright 2015 Brian J. Downs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// -// weather.go -// -// This application will go out and get the weather for the given -// location and display it in the given data units (fahrenheit, -// celcius, or kelvin). If the string "here" is provided as an -// argument to the -l flag, the app will try to figure out where -// it's being executed from based on geolocation from the IP address. -// -// Examples: -// go run weather.go --help -// go run weather.go -w Philadelphia -u f -l en # fahrenheit, English -// go run weather.go -w here -u f -l ru # fahrenheit, Russian -// go run weather.go -w Dublin -u c -l fi # celcius, Finnish -// go run weather.go -w "Las Vegas" -u k -l es # kelvin, Spanish -package main - -import ( - "encoding/json" - "flag" - owm "github.com/briandowns/openweathermap" // "owm" for easier use - "io/ioutil" - "log" - "net/http" - "os" - "strings" - "text/template" -) - -// URL is a constant that contains where to find the IP locale info -const URL = "http://ip-api.com/json" - -// template used for output -const weatherTemplate = `Current weather for {{.Name}}: - Conditions: {{range .Weather}} {{.Description}} {{end}} - Now: {{.Main.Temp}} {{.Unit}} - High: {{.Main.TempMax}} {{.Unit}} - Low: {{.Main.TempMin}} {{.Unit}} -` - -const forecastTemplate = `Weather Forecast for {{.City.Name}}: -{{range .List}}Date & Time: {{.DtTxt}} -Conditions: {{range .Weather}}{{.Main}} {{.Description}}{{end}} -Temp: {{.Main.Temp}} -High: {{.Main.TempMax}} -Low: {{.Main.TempMin}} - -{{end}} -` - -// Pointers to hold the contents of the flag args. -var ( - whereFlag = flag.String("w", "", "Location to get weather. If location has a space, wrap the location in double quotes.") - unitFlag = flag.String("u", "", "Unit of measure to display temps in") - langFlag = flag.String("l", "", "Language to display temps in") - whenFlag = flag.String("t", "current", "current | forecast") -) - -// Data will hold the result of the query to get the IP -// address of the caller. -type Data struct { - Status string `json:"status"` - Country string `json:"country"` - CountryCode string `json:"countryCode"` - Region string `json:"region"` - RegionName string `json:"regionName"` - City string `json:"city"` - Zip string `json:"zip"` - Lat float64 `json:"lat"` - Lon float64 `json:"lon"` - Timezone string `json:"timezone"` - ISP string `json:"isp"` - ORG string `json:"org"` - AS string `json:"as"` - Message string `json:"message"` - Query string `json:"query"` -} - -// getLocation will get the location details for where this -// application has been run from. -func getLocation() (*Data, error) { - response, err := http.Get(URL) - if err != nil { - return nil, err - } - defer response.Body.Close() - - result, err := ioutil.ReadAll(response.Body) - if err != nil { - return nil, err - } - - r := &Data{} - if err = json.Unmarshal(result, &r); err != nil { - return nil, err - } - return r, nil -} - -// getCurrent gets the current weather for the provided -// location in the units provided. -func getCurrent(location, units, lang string) (*owm.CurrentWeatherData, error) { - w, err := owm.NewCurrent(units, lang, os.Getenv("OWM_API_KEY")) - if err != nil { - return nil, err - } - w.CurrentByName(location) - return w, nil -} -func getForecast5(location, units, lang string) (*owm.Forecast5WeatherData, error) { - w, err := owm.NewForecast("5", units, lang, os.Getenv("OWM_API_KEY")) - if err != nil { - return nil, err - } - w.DailyByName(location, 5) - forecast := w.ForecastWeatherJson.(*owm.Forecast5WeatherData) - return forecast, err -} - -func main() { - flag.Parse() - - // If there's any funkiness with cli args, tuck and roll... - if len(*whereFlag) <= 1 || len(*unitFlag) != 1 || len(*langFlag) != 2 || len(*whenFlag) <= 1 { - flag.Usage() - os.Exit(1) - } - - // Process request for location of "here" - if strings.ToLower(*whereFlag) == "here" { - loc, err := getLocation() - if err != nil { - log.Fatalln(err) - } - w, err := getCurrent(loc.City, *unitFlag, *langFlag) - if err != nil { - log.Fatalln(err) - } - tmpl, err := template.New("weather").Parse(weatherTemplate) - if err != nil { - log.Fatalln(err) - } - - // Render the template and display - err = tmpl.Execute(os.Stdout, w) - if err != nil { - log.Fatalln(err) - } - os.Exit(0) - } - - if *whenFlag == "current" { - // Process request for the given location - w, err := getCurrent(*whereFlag, *unitFlag, *langFlag) - if err != nil { - log.Fatalln(err) - } - tmpl, err := template.New("weather").Parse(weatherTemplate) - if err != nil { - log.Fatalln(err) - } - // Render the template and display - if err := tmpl.Execute(os.Stdout, w); err != nil { - log.Fatalln(err) - } - } else { //forecast - w, err := getForecast5(*whereFlag, *unitFlag, *langFlag) - if err != nil { - log.Fatalln(err) - } - tmpl, err := template.New("forecast").Parse(forecastTemplate) - if err != nil { - log.Fatalln(err) - } - // Render the template and display - if err := tmpl.Execute(os.Stdout, w); err != nil { - log.Fatalln(err) - } - } - - os.Exit(0) -} diff --git a/_examples/web/static/img/01d.png b/_examples/web/static/img/01d.png deleted file mode 100644 index 7d2f792..0000000 Binary files a/_examples/web/static/img/01d.png and /dev/null differ diff --git a/_examples/web/templates/here.html b/_examples/web/templates/here.html deleted file mode 100644 index edb1fd6..0000000 --- a/_examples/web/templates/here.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - {{.Name}} - - - - - - -
- -

{{range .Weather}}weather icon {{.Description}} {{end}}

- - - - - - - - - -
NowHighLow
{{.Main.Temp}} {{.Unit}}{{.Main.TempMax}} {{.Unit}}{{.Main.TempMin}} {{.Unit}}
-
- - - diff --git a/_examples/web/weatherweb.go b/_examples/web/weatherweb.go deleted file mode 100644 index 3519189..0000000 --- a/_examples/web/weatherweb.go +++ /dev/null @@ -1,105 +0,0 @@ -// Example of creating a web based application purely using -// the net/http package to display weather information and -// Twitter Bootstrap so it doesn't look like it's '92. -// -// To start the app, run: -// go run weatherweb.go -// -// Accessible via: http://localhost:8888/here -package main - -import ( - "encoding/json" - "fmt" - "html/template" - - owm "github.com/briandowns/openweathermap" - // "io/ioutil" - - "net/http" - "os" -) - -// URL is a constant that contains where to find the IP locale info -const URL = "http://ip-api.com/json" - -// Data will hold the result of the query to get the IP -// address of the caller. -type Data struct { - Status string `json:"status"` - Country string `json:"country"` - CountryCode string `json:"countryCode"` - Region string `json:"region"` - RegionName string `json:"regionName"` - City string `json:"city"` - Zip string `json:"zip"` - Lat float64 `json:"lat"` - Lon float64 `json:"lon"` - Timezone string `json:"timezone"` - ISP string `json:"isp"` - ORG string `json:"org"` - AS string `json:"as"` - Message string `json:"message"` - Query string `json:"query"` -} - -// getLocation will get the location details for where this -// application has been run from. -func getLocation() (*Data, error) { - response, err := http.Get(URL) - if err != nil { - return nil, err - } - defer response.Body.Close() - r := &Data{} - if err = json.NewDecoder(response.Body).Decode(&r); err != nil { - return nil, err - } - return r, nil -} - -// getCurrent gets the current weather for the provided location in -// the units provided. -func getCurrent(l, u, lang string) (*owm.CurrentWeatherData, error) { - w, err := owm.NewCurrent(u, lang, os.Getenv("OWM_API_KEY")) // Create the instance with the given unit - if err != nil { - return nil, err - } - w.CurrentByName(l) // Get the actual data for the given location - return w, nil -} - -// hereHandler will take are of requests coming in for the "/here" route. -func hereHandler(w http.ResponseWriter, r *http.Request) { - location, err := getLocation() - if err != nil { - fmt.Fprint(w, http.StatusInternalServerError) - return - } - wd, err := getCurrent(location.City, "F", "en") - if err != nil { - fmt.Fprint(w, http.StatusInternalServerError) - return - } - // Process our template - t, err := template.ParseFiles("templates/here.html") - if err != nil { - fmt.Fprint(w, http.StatusInternalServerError) - return - } - // We're doin' naughty things below... Ignoring icon file size and possible errors. - _, _ = owm.RetrieveIcon("static/img", wd.Weather[0].Icon+".png") - - // Write out the template with the given data - t.Execute(w, wd) -} - -// Run the app -func main() { - http.HandleFunc("/here", hereHandler) - // Make sure we can serve our icon files once retrieved - http.HandleFunc("/static/", func(w http.ResponseWriter, r *http.Request) { - http.ServeFile(w, r, r.URL.Path[1:]) - }) - http.ListenAndServe(":8888", nil) -} diff --git a/bulk_downloading.go b/bulk_downloading.go new file mode 100644 index 0000000..49cc28b --- /dev/null +++ b/bulk_downloading.go @@ -0,0 +1 @@ +package openweathermap diff --git a/conditions.go b/conditions.go index e73ecec..1e35a44 100644 --- a/conditions.go +++ b/conditions.go @@ -1,4 +1,4 @@ -// Copyright 2015 Brian J. Downs +// Copyright 2021 Brian J. Downs // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -66,8 +66,8 @@ func RetrieveIcon(destination, iconFile string) (int64, error) { return 0, nil } -// IconList is a slice of IconData pointers -var IconList = []*IconData{ +// IconList is a slice of IconData. +var IconList = []IconData{ {Condition: "clear sky", Day: "01d.png", Night: "01n.png"}, {Condition: "few clouds", Day: "02d.png", Night: "02n.png"}, {Condition: "scattered clouds", Day: "03d.png", Night: "03n.png"}, @@ -79,8 +79,8 @@ var IconList = []*IconData{ {Condition: "mist", Day: "50d.png", Night: "50n.png"}, } -// ThunderstormConditions is a slice of ConditionData pointers -var ThunderstormConditions = []*ConditionData{ +// ThunderstormConditions is a slice of ConditionData. +var ThunderstormConditions = []ConditionData{ {ID: 200, Meaning: "thunderstorm with light rain", Icon1: "11d.png"}, {ID: 201, Meaning: "thunderstorm with rain", Icon1: "11d.png"}, {ID: 202, Meaning: "thunderstorm with heavy rain", Icon1: "11d.png"}, @@ -93,8 +93,8 @@ var ThunderstormConditions = []*ConditionData{ {ID: 232, Meaning: "thunderstorm with heavy drizzle", Icon1: "11d.png"}, } -// DrizzleConditions is a slice of ConditionData pointers -var DrizzleConditions = []*ConditionData{ +// DrizzleConditions is a slice of ConditionData. +var DrizzleConditions = []ConditionData{ {ID: 300, Meaning: "light intensity drizzle", Icon1: "09d.png"}, {ID: 301, Meaning: "drizzle", Icon1: "09d.png"}, {ID: 302, Meaning: "heavy intensity drizzle", Icon1: "09d.png"}, @@ -106,8 +106,8 @@ var DrizzleConditions = []*ConditionData{ {ID: 321, Meaning: "shower drizzle", Icon1: "09d.png"}, } -// RainConditions is a slice of ConditionData pointers -var RainConditions = []*ConditionData{ +// RainConditions is a slice of ConditionData. +var RainConditions = []ConditionData{ {ID: 500, Meaning: "light rain", Icon1: "09d.png"}, {ID: 501, Meaning: "moderate rain", Icon1: "09d.png"}, {ID: 502, Meaning: "heavy intensity rain", Icon1: "09d.png"}, @@ -120,8 +120,8 @@ var RainConditions = []*ConditionData{ {ID: 531, Meaning: "ragged shower rain", Icon1: "09d.png"}, } -// SnowConditions is a slice of ConditionData pointers -var SnowConditions = []*ConditionData{ +// SnowConditions is a slice of ConditionData. +var SnowConditions = []ConditionData{ {ID: 600, Meaning: "light snow", Icon1: "13d.png"}, {ID: 601, Meaning: "snow", Icon1: "13d.png"}, {ID: 602, Meaning: "heavy snow", Icon1: "13d.png"}, @@ -134,8 +134,8 @@ var SnowConditions = []*ConditionData{ {ID: 622, Meaning: "heavy shower snow", Icon1: "13d.png"}, } -// AtmosphereConditions is a slice of ConditionData pointers -var AtmosphereConditions = []*ConditionData{ +// AtmosphereConditions is a slice of ConditionData. +var AtmosphereConditions = []ConditionData{ {ID: 701, Meaning: "mist", Icon1: "50d.png"}, {ID: 711, Meaning: "smoke", Icon1: "50d.png"}, {ID: 721, Meaning: "haze", Icon1: "50d.png"}, @@ -148,8 +148,8 @@ var AtmosphereConditions = []*ConditionData{ {ID: 781, Meaning: "tornado", Icon1: "50d.png"}, } -// CloudConditions is a slice of ConditionData pointers -var CloudConditions = []*ConditionData{ +// CloudConditions is a slice of ConditionData. +var CloudConditions = []ConditionData{ {ID: 800, Meaning: "clear sky", Icon1: "01d.png", Icon2: "01n.png"}, {ID: 801, Meaning: "few clouds", Icon1: "02d.png", Icon2: " 02n.png"}, {ID: 802, Meaning: "scattered clouds", Icon1: "03d.png", Icon2: "03d.png"}, @@ -157,8 +157,8 @@ var CloudConditions = []*ConditionData{ {ID: 804, Meaning: "overcast clouds", Icon1: "04d.png", Icon2: "04d.png"}, } -// ExtremeConditions is a slice of ConditionData pointers -var ExtremeConditions = []*ConditionData{ +// ExtremeConditions is a slice of ConditionData. +var ExtremeConditions = []ConditionData{ {ID: 900, Meaning: "tornado", Icon1: ""}, {ID: 901, Meaning: "tropical storm", Icon1: ""}, {ID: 902, Meaning: "hurricane", Icon1: ""}, @@ -168,8 +168,8 @@ var ExtremeConditions = []*ConditionData{ {ID: 906, Meaning: "hail", Icon1: ""}, } -// AdditionalConditions is a slive of ConditionData pointers -var AdditionalConditions = []*ConditionData{ +// AdditionalConditions is a slicee of ConditionData. +var AdditionalConditions = []ConditionData{ {ID: 951, Meaning: "calm", Icon1: ""}, {ID: 952, Meaning: "light breeze", Icon1: ""}, {ID: 953, Meaning: "gentle breeze", Icon1: ""}, diff --git a/conditions_test.go b/conditions_test.go index e54e848..fa17710 100644 --- a/conditions_test.go +++ b/conditions_test.go @@ -1,4 +1,4 @@ -// Copyright 2015 Brian J. Downs +// Copyright 2021 Brian J. Downs // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/current.go b/current.go index 584903d..2fc8fb8 100644 --- a/current.go +++ b/current.go @@ -1,4 +1,4 @@ -// Copyright 2015 Brian J. Downs +// Copyright 2021 Brian J. Downs // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,16 +15,15 @@ package openweathermap import ( - "encoding/json" + "errors" "fmt" "net/url" - "strings" ) // CurrentWeatherData struct contains an aggregate view of the structs // defined above for JSON to be unmarshaled into. type CurrentWeatherData struct { - GeoPos Coordinates `json:"coord"` + GeoPos Coordinates `json:"coord" xml:"coord"` Sys Sys `json:"sys"` Base string `json:"base"` Weather []Weather `json:"weather"` @@ -33,112 +32,71 @@ type CurrentWeatherData struct { Clouds Clouds `json:"clouds"` Rain Rain `json:"rain"` Snow Snow `json:"snow"` - Dt int `json:"dt"` - ID int `json:"id"` + Dt int64 `json:"dt"` + ID int64 `json:"id"` Name string `json:"name"` Cod int `json:"cod"` Timezone int `json:"timezone"` - Unit string - Lang string - Key string - *Settings } -// NewCurrent returns a new CurrentWeatherData pointer with the supplied parameters -func NewCurrent(unit, lang, key string, options ...Option) (*CurrentWeatherData, error) { - unitChoice := strings.ToUpper(unit) - langChoice := strings.ToUpper(lang) +// CurrentByName will provide the current weather with +// the provided location name. +func (o *OWM) CurrentByName(location string) (*CurrentWeatherData, error) { + base := fmt.Sprintf(baseURL, "appid=%s&q=%s&units=%s&lang=%s") + url := fmt.Sprintf(base, o.apiKey, url.QueryEscape(location), o.unit, o.lang) - c := &CurrentWeatherData{ - Settings: NewSettings(), - } - - if ValidDataUnit(unitChoice) { - c.Unit = DataUnits[unitChoice] - } else { - return nil, errUnitUnavailable - } - - if ValidLangCode(langChoice) { - c.Lang = langChoice - } else { - return nil, errLangUnavailable - } - var err error - c.Key, err = setKey(key) - if err != nil { + var cwd CurrentWeatherData + if err := o.call(url, &cwd); err != nil { return nil, err } - if err := setOptions(c.Settings, options); err != nil { - return nil, err - } - return c, nil + return &cwd, nil } -// CurrentByName will provide the current weather with the provided -// location name. -func (w *CurrentWeatherData) CurrentByName(location string) error { - response, err := w.client.Get(fmt.Sprintf(fmt.Sprintf(baseURL, "appid=%s&q=%s&units=%s&lang=%s"), w.Key, url.QueryEscape(location), w.Unit, w.Lang)) - if err != nil { - return err - } - defer response.Body.Close() +// CurrentByCoordinates will provide the current weather +// with the provided location coordinates. +func (o *OWM) CurrentByCoordinates(location *Coordinates) (*CurrentWeatherData, error) { + base := fmt.Sprintf(baseURL, "appid=%s&lat=%f&lon=%f&units=%s&lang=%s") + url := fmt.Sprintf(base, o.apiKey, location.Latitude, location.Longitude, o.unit, o.lang) - if err := json.NewDecoder(response.Body).Decode(&w); err != nil { - return err - } - - return nil -} - -// CurrentByCoordinates will provide the current weather with the -// provided location coordinates. -func (w *CurrentWeatherData) CurrentByCoordinates(location *Coordinates) error { - response, err := w.client.Get(fmt.Sprintf(fmt.Sprintf(baseURL, "appid=%s&lat=%f&lon=%f&units=%s&lang=%s"), w.Key, location.Latitude, location.Longitude, w.Unit, w.Lang)) - if err != nil { - return err - } - defer response.Body.Close() - - if err = json.NewDecoder(response.Body).Decode(&w); err != nil { - return err + var cwd CurrentWeatherData + if err := o.call(url, &cwd); err != nil { + return nil, err } - return nil + return &cwd, nil } // CurrentByID will provide the current weather with the // provided location ID. -func (w *CurrentWeatherData) CurrentByID(id int) error { - response, err := w.client.Get(fmt.Sprintf(fmt.Sprintf(baseURL, "appid=%s&id=%d&units=%s&lang=%s"), w.Key, id, w.Unit, w.Lang)) - if err != nil { - return err - } - defer response.Body.Close() +func (o *OWM) CurrentByID(id int) (*CurrentWeatherData, error) { + base := fmt.Sprintf(baseURL, "appid=%s&id=%d&units=%s&lang=%s") + url := fmt.Sprintf(base, o.apiKey, id, o.unit, o.lang) - if err = json.NewDecoder(response.Body).Decode(&w); err != nil { - return err + var cwd CurrentWeatherData + if err := o.call(url, &cwd); err != nil { + return nil, err } - return nil + return &cwd, nil } // CurrentByZip will provide the current weather for the // provided zip code. -func (w *CurrentWeatherData) CurrentByZip(zip int, countryCode string) error { - response, err := w.client.Get(fmt.Sprintf(fmt.Sprintf(baseURL, "appid=%s&zip=%d,%s&units=%s&lang=%s"), w.Key, zip, countryCode, w.Unit, w.Lang)) - if err != nil { - return err - } - defer response.Body.Close() - if err = json.NewDecoder(response.Body).Decode(&w); err != nil { - return err +func (o *OWM) CurrentByZip(zip, countryCode string) (*CurrentWeatherData, error) { + base := fmt.Sprintf(baseURL, "appid=%s&zip=%s,%s&units=%s&lang=%s") + url := fmt.Sprintf(base, o.apiKey, zip, countryCode, o.unit, o.lang) + + var cwd CurrentWeatherData + if err := o.call(url, &cwd); err != nil { + return nil, err } - return nil + return &cwd, nil } // CurrentByArea will provide the current weather for the // provided area. -func (w *CurrentWeatherData) CurrentByArea() {} +func (o *OWM) CurrentByArea() (*CurrentWeatherData, error) { + return nil, errors.New("unimplemented") +} diff --git a/current_test.go b/current_test.go index 8dc8d64..dc19e70 100644 --- a/current_test.go +++ b/current_test.go @@ -1,4 +1,4 @@ -// Copyright 2015 Brian J. Downs +// Copyright 2021 Brian J. Downs // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,227 +16,276 @@ package openweathermap import ( "net/http" - "os" "reflect" "testing" - "time" ) -// currentWeather holds the query and response -type currentWeather struct { - query string - weather CurrentWeatherData -} +// import ( +// "net/http" +// "os" +// "reflect" +// "testing" +// "time" +// ) -// TestValidLanguageCode will verify that the language code passed in is indeed -// a valid one for use with the API -func TestValidLanguageCode(t *testing.T) { - testCodes := []string{"EN", "DE", "blah"} - for _, i := range testCodes { - if !ValidLangCode(i) { - t.Log("received expected bad code") - } - } -} +// // currentWeather holds the query and response +// type currentWeather struct { +// query string +// weather CurrentWeatherData +// } -// TestNewCurrent will verify that a new instance of CurrentWeatherData is created -func TestNewCurrent(t *testing.T) { - t.Parallel() +// // TestValidLanguageCode will verify that the language code passed in is indeed +// // a valid one for use with the API +// func TestValidLanguageCode(t *testing.T) { +// testCodes := []string{"EN", "DE", "blah"} +// for _, i := range testCodes { +// if !ValidLangCode(i) { +// t.Log("received expected bad code") +// } +// } +// } - for d := range DataUnits { - t.Logf("Data unit: %s", d) +// // TestNewCurrent will verify that a new instance of CurrentWeatherData is created +// func TestNewCurrent(t *testing.T) { +// t.Parallel() - if ValidDataUnit(d) { - c, err := NewCurrent(d, "en", os.Getenv("OWM_API_KEY")) - if err != nil { - t.Error(err) - } +// for d := range DataUnits { +// t.Logf("Data unit: %s", d) - if _, err := NewCurrent(d, "blah", os.Getenv("OWM_API_KEY")); err != nil { - t.Log("received expected bad language code error") - } +// if ValidDataUnit(d) { +// c, err := NewCurrent(d, "en", os.Getenv("OWM_API_KEY")) +// if err != nil { +// t.Error(err) +// } - if reflect.TypeOf(c).String() != "*openweathermap.CurrentWeatherData" { - t.Error("incorrect data type returned") - } - } else { - t.Errorf("unusable data unit - %s", d) - } - } -} +// if _, err := NewCurrent(d, "blah", os.Getenv("OWM_API_KEY")); err != nil { +// t.Log("received expected bad language code error") +// } -// TestNewCurrentWithCustomHttpClient will verify that a new instance of CurrentWeatherData -// is created with custom http client -func TestNewCurrentWithCustomHttpClient(t *testing.T) { - hc := http.DefaultClient - hc.Timeout = time.Duration(1) * time.Second - c, err := NewCurrent("c", "en", os.Getenv("OWM_API_KEY"), WithHttpClient(hc)) - if err != nil { - t.Error(err) - } +// if reflect.TypeOf(c).String() != "*openweathermap.CurrentWeatherData" { +// t.Error("incorrect data type returned") +// } +// } else { +// t.Errorf("unusable data unit - %s", d) +// } +// } +// } - if reflect.TypeOf(c).String() != "*openweathermap.CurrentWeatherData" { - t.Error("incorrect data type returned") - } +// // TestNewCurrentWithCustomHttpClient will verify that a new instance of CurrentWeatherData +// // is created with custom http client +// func TestNewCurrentWithCustomHttpClient(t *testing.T) { +// hc := http.DefaultClient +// hc.Timeout = time.Duration(1) * time.Second +// c, err := NewCurrent("c", "en", os.Getenv("OWM_API_KEY"), WithHttpClient(hc)) +// if err != nil { +// t.Error(err) +// } - expected := time.Duration(1) * time.Second - if c.client.Timeout != expected { - t.Errorf("Expected Duration %v, but got %v", expected, c.client.Timeout) - } -} +// if reflect.TypeOf(c).String() != "*openweathermap.CurrentWeatherData" { +// t.Error("incorrect data type returned") +// } -// TestNewCurrentWithInvalidOptions will verify that returns an error with -// invalid option -func TestNewCurrentWithInvalidOptions(t *testing.T) { - optionsPattern := [][]Option{ - {nil}, - {nil, nil}, - {WithHttpClient(&http.Client{}), nil}, - {nil, WithHttpClient(&http.Client{})}, - } +// expected := time.Duration(1) * time.Second +// if c.client.Timeout != expected { +// t.Errorf("Expected Duration %v, but got %v", expected, c.client.Timeout) +// } +// } - for _, options := range optionsPattern { - c, err := NewCurrent("c", "en", os.Getenv("OWM_API_KEY"), options...) - if err == errInvalidOption { - t.Logf("Received expected invalid option error. message: %s", err.Error()) - } else if err != nil { - t.Errorf("Expected %v, but got %v", errInvalidOption, err) - } - if c != nil { - t.Errorf("Expected nil, but got %v", c) - } - } -} +// // TestNewCurrentWithInvalidOptions will verify that returns an error with +// // invalid option +// func TestNewCurrentWithInvalidOptions(t *testing.T) { +// optionsPattern := [][]Option{ +// {nil}, +// {nil, nil}, +// {WithHttpClient(&http.Client{}), nil}, +// {nil, WithHttpClient(&http.Client{})}, +// } -// TestNewCurrentWithInvalidHttpClient will verify that returns an error with -// invalid http client -func TestNewCurrentWithInvalidHttpClient(t *testing.T) { +// for _, options := range optionsPattern { +// c, err := NewCurrent("c", "en", os.Getenv("OWM_API_KEY"), options...) +// if err == errInvalidOption { +// t.Logf("Received expected invalid option error. message: %s", err.Error()) +// } else if err != nil { +// t.Errorf("Expected %v, but got %v", errInvalidOption, err) +// } +// if c != nil { +// t.Errorf("Expected nil, but got %v", c) +// } +// } +// } - c, err := NewCurrent("c", "en", os.Getenv("OWM_API_KEY"), WithHttpClient(nil)) - if err == errInvalidHttpClient { - t.Logf("Received expected bad client error. message: %s", err.Error()) - } else if err != nil { - t.Errorf("Expected %v, but got %v", errInvalidHttpClient, err) - } - if c != nil { - t.Errorf("Expected nil, but got %v", c) - } -} +// // TestNewCurrentWithInvalidHttpClient will verify that returns an error with +// // invalid http client +// func TestNewCurrentWithInvalidHttpClient(t *testing.T) { -// TestCurrentByName will verify that current data can be retrieved for a give -// location by name -func TestCurrentByName(t *testing.T) { - t.Parallel() - - testCities := []currentWeather{ - { - query: "Philadelphia", - weather: CurrentWeatherData{ - ID: 4560349, - Name: "Philadelphia", - Main: Main{ - Temp: 35.6, - }, - }, - }, - { - query: "Newark", - weather: CurrentWeatherData{ - ID: 5101798, - Name: "Newark", - Main: Main{ - Temp: 36.36, - }, - }, - }, - { - query: "Helena", - weather: CurrentWeatherData{ - ID: 5656882, - Name: "Helena", - Main: Main{ - Temp: 42.8, - }, - }, - }, - { - query: "San Diego, CA", - weather: CurrentWeatherData{ - ID: 5391811, - Name: "San Diego", - Main: Main{ - Temp: 56.53, - }, - }, - }, - } +// c, err := NewCurrent("c", "en", os.Getenv("OWM_API_KEY"), WithHttpClient(nil)) +// if err == errInvalidHttpClient { +// t.Logf("Received expected bad client error. message: %s", err.Error()) +// } else if err != nil { +// t.Errorf("Expected %v, but got %v", errInvalidHttpClient, err) +// } +// if c != nil { +// t.Errorf("Expected nil, but got %v", c) +// } +// } - testBadCities := []string{"nowhere_", "somewhere_over_the_"} +// // TestCurrentByName will verify that current data can be retrieved for a give +// // location by name +// func TestCurrentByName(t *testing.T) { +// t.Parallel() - c, err := NewCurrent("f", "ru", os.Getenv("OWM_API_KEY")) - if err != nil { - t.Error(err) - } +// testCities := []currentWeather{ +// { +// query: "Philadelphia", +// weather: CurrentWeatherData{ +// ID: 4560349, +// Name: "Philadelphia", +// Main: Main{ +// Temp: 35.6, +// }, +// }, +// }, +// { +// query: "Newark", +// weather: CurrentWeatherData{ +// ID: 5101798, +// Name: "Newark", +// Main: Main{ +// Temp: 36.36, +// }, +// }, +// }, +// { +// query: "Helena", +// weather: CurrentWeatherData{ +// ID: 5656882, +// Name: "Helena", +// Main: Main{ +// Temp: 42.8, +// }, +// }, +// }, +// { +// query: "San Diego, CA", +// weather: CurrentWeatherData{ +// ID: 5391811, +// Name: "San Diego", +// Main: Main{ +// Temp: 56.53, +// }, +// }, +// }, +// } - for _, city := range testCities { - c.CurrentByName(city.query) +// testBadCities := []string{"nowhere_", "somewhere_over_the_"} - if os.Getenv("RTCP_HOST") != "" { - if c.ID != city.weather.ID { - t.Errorf("Excpect CityID %d, got %d", city.weather.ID, c.ID) - } - if c.Name != city.weather.Name { - t.Errorf("Excpect City %s, got %s", city.weather.Name, c.Name) - } - if c.Main.Temp != city.weather.Main.Temp { - t.Errorf("Excpect Temp %.2f, got %.2f", city.weather.Main.Temp, c.Main.Temp) - } - } - } +// c, err := NewCurrent("f", "ru", os.Getenv("OWM_API_KEY")) +// if err != nil { +// t.Error(err) +// } - for _, badCity := range testBadCities { - if err := c.CurrentByName(badCity); err != nil { - t.Log("received expected failure for bad city by name") - } - } -} +// for _, city := range testCities { +// c.CurrentByName(city.query) -// TestCurrentByCoordinates will verify that current data can be retrieved for a -// given set of coordinates -func TestCurrentByCoordinates(t *testing.T) { - t.Parallel() - c, err := NewCurrent("f", "DE", os.Getenv("OWM_API_KEY")) - if err != nil { - t.Error("Error creating instance of CurrentWeatherData") - } - c.CurrentByCoordinates( - &Coordinates{ - Longitude: -112.07, - Latitude: 33.45, - }, - ) -} +// if os.Getenv("RTCP_HOST") != "" { +// if c.ID != city.weather.ID { +// t.Errorf("Excpect CityID %d, got %d", city.weather.ID, c.ID) +// } +// if c.Name != city.weather.Name { +// t.Errorf("Excpect City %s, got %s", city.weather.Name, c.Name) +// } +// if c.Main.Temp != city.weather.Main.Temp { +// t.Errorf("Excpect Temp %.2f, got %.2f", city.weather.Main.Temp, c.Main.Temp) +// } +// } +// } -// TestCurrentByID will verify that current data can be retrieved for a given -// location id -func TestCurrentByID(t *testing.T) { - t.Parallel() - c, err := NewCurrent("c", "ZH", os.Getenv("OWM_API_KEY")) - if err != nil { - t.Error("Error creating instance of CurrentWeatherData") - } - c.CurrentByID(5344157) -} +// for _, badCity := range testBadCities { +// if err := c.CurrentByName(badCity); err != nil { +// t.Log("received expected failure for bad city by name") +// } +// } +// } -func TestCurrentByZip(t *testing.T) { - w, err := NewCurrent("F", "EN", os.Getenv("OWM_API_KEY")) - if err != nil { - t.Error(err) - } +// // TestCurrentByCoordinates will verify that current data can be retrieved for a +// // given set of coordinates +// func TestCurrentByCoordinates(t *testing.T) { +// t.Parallel() +// c, err := NewCurrent("f", "DE", os.Getenv("OWM_API_KEY")) +// if err != nil { +// t.Error("Error creating instance of CurrentWeatherData") +// } +// c.CurrentByCoordinates( +// &Coordinates{ +// Longitude: -112.07, +// Latitude: 33.45, +// }, +// ) +// } - if err := w.CurrentByZip(19125, "US"); err != nil { - t.Error(err) +// // TestCurrentByID will verify that current data can be retrieved for a given +// // location id +// func TestCurrentByID(t *testing.T) { +// t.Parallel() +// c, err := NewCurrent("c", "ZH", os.Getenv("OWM_API_KEY")) +// if err != nil { +// t.Error("Error creating instance of CurrentWeatherData") +// } +// c.CurrentByID(5344157) +// } + +// func TestCurrentByZip(t *testing.T) { +// w, err := NewCurrent("F", "EN", os.Getenv("OWM_API_KEY")) +// if err != nil { +// t.Error(err) +// } + +// if err := w.CurrentByZip(19125, "US"); err != nil { +// t.Error(err) +// } +// } + +func TestOWM_CurrentByName(t *testing.T) { + type fields struct { + mode string + unit string + lang string + apiKey string + username string + password string + client *http.Client + } + type args struct { + location string + } + tests := []struct { + name string + fields fields + args args + want *CurrentWeatherData + wantErr bool + }{ + // + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + o := &OWM{ + mode: tt.fields.mode, + unit: tt.fields.unit, + lang: tt.fields.lang, + apiKey: tt.fields.apiKey, + username: tt.fields.username, + password: tt.fields.password, + client: tt.fields.client, + } + got, err := o.CurrentByName(tt.args.location) + if (err != nil) != tt.wantErr { + t.Errorf("OWM.CurrentByName() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("OWM.CurrentByName() = %v, want %v", got, tt.want) + } + }) } } - -func TestCurrentByArea(t *testing.T) {} diff --git a/doc.go b/doc.go index 39a41ff..a45ff80 100644 --- a/doc.go +++ b/doc.go @@ -1,4 +1,4 @@ -// Copyright 2015 Brian J. Downs +// Copyright 2021 Brian J. Downs // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/forecast.go b/forecast.go index a5f240c..bf10ecd 100644 --- a/forecast.go +++ b/forecast.go @@ -1,4 +1,4 @@ -// Copyright 2015 Brian J. Downs +// Copyright 2021 Brian J. Downs // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,18 +16,16 @@ package openweathermap import ( "fmt" - "io" "net/url" "strconv" - "strings" ) -// ForecastSys area population +// ForecastSys area population. type ForecastSys struct { Population int `json:"population"` } -// Temperature holds returned termperate sure stats +// Temperature holds returned termperate sure stats. type Temperature struct { Day float64 `json:"day"` Min float64 `json:"min"` @@ -37,126 +35,165 @@ type Temperature struct { Morn float64 `json:"morn"` } -// City data for given location +// City data for given location. type City struct { ID int `json:"id"` Name string `json:"name"` Coord Coordinates `json:"coord"` Country string `json:"country"` - Population int `json:"population"` - Sys ForecastSys `json:"sys"` + Population int64 `json:"population"` + Timezone int64 `json:"timezome"` + Sunrise int64 `json:"sunrise"` + Sunset int64 `json:"sunset"` } -type ForecastWeather interface { - DailyByName(location string, days int) error - DailyByCoordinates(location *Coordinates, days int) error - DailyByID(id, days int) error +// Forecast5Weather holds specific query data. +type ForecastFiveWeather struct { + Dt int64 `json:"dt"` + Main Main `json:"main"` + Weather []Weather `json:"weather"` + Clouds Clouds `json:"clouds"` + Wind Wind `json:"wind"` + Visibility int64 `json:"visibility"` + Pop float64 `json:"pop"` + Rain Rain `json:"rain"` + Sys Sys `json:"sys"` + Snow Snow `json:"snow"` + DtText string `json:"dt_text"` } -// json served by OWM API can take different forms, so all of them must be matched -// by corresponding data type and unmarshall method -type ForecastWeatherJson interface { - Decode(r io.Reader) error +// ForecastFiveWeatherData will hold returned data from queries. +type ForecastFiveWeatherData struct { + COD string `json:"cod"` + Message int64 `json:"message"` + Cnt int64 `json:"cnt"` + List []ForecastFiveWeather `json:"list"` + City City `json:"city"` } -type ForecastWeatherData struct { - Unit string - Lang string - Key string - baseURL string - *Settings - ForecastWeatherJson +// ForecastSixteenWeather holds specific query data. +type ForecastSixteenWeather struct { + Dt int64 `json:"dt"` + Temp Temperature `json:"temp"` + Pressure float64 `json:"pressure"` + Humidity int64 `json:"humidity"` + Weather []Weather `json:"weather"` + Speed float64 `json:"speed"` + Deg int64 `json:"deg"` + Clouds int64 `json:"clouds"` + Snow float64 `json:"snow"` + Rain float64 `json:"rain"` + FeelsLike FeelsLikeFullDay `json:"feels_like"` + Pop float64 `json:"pop"` } -// NewForecast returns a new HistoricalWeatherData pointer with -// the supplied arguments. -func NewForecast(forecastType, unit, lang, key string, options ...Option) (*ForecastWeatherData, error) { - unitChoice := strings.ToUpper(unit) - langChoice := strings.ToUpper(lang) +// ForecastSixteenWeatherData will hold returned data from queries. +type ForecastSixteenWeatherData struct { + COD string `json:"cod"` + Message float64 `json:"message"` + City City `json:"city"` + Cnt int64 `json:"cnt"` + List []ForecastSixteenWeather `json:"list"` +} - if forecastType != "16" && forecastType != "5" { - return nil, errForecastUnavailable - } +// FiveDayForecastByName will provide a five day forecast for the given location. +func (o *OWM) FiveDayForecastByName(location string, cnt int) (*ForecastFiveWeatherData, error) { + url := fmt.Sprintf(forecastFiveBase, o.apiKey, "q="+url.QueryEscape(location), o.unit, o.lang, cnt) - if !ValidDataUnit(unitChoice) { - return nil, errUnitUnavailable + var fwd ForecastFiveWeatherData + if err := o.call(url, &fwd); err != nil { + return nil, err } + fmt.Printf("%#v\n", fwd) + return &fwd, nil +} - if !ValidLangCode(langChoice) { - return nil, errLangUnavailable - } +// FiveDayForecastByCoordinates will provide a five day forecast for the given coordinates. +func (o *OWM) FiveDayForecastByCoordinates(location *Coordinates, cnt int) (*ForecastFiveWeatherData, error) { + pos := fmt.Sprintf("lat=%f&lon=%f", location.Latitude, location.Longitude) + url := fmt.Sprintf(forecastFiveBase, o.apiKey, pos, o.unit, o.lang, cnt) - settings := NewSettings() - if err := setOptions(settings, options); err != nil { + var fwd ForecastFiveWeatherData + if err := o.call(url, &fwd); err != nil { return nil, err } - var err error - k, err := setKey(key) - if err != nil { + return &fwd, nil +} + +// FiveDayForecastByID will provide a forecast for the given location ID. +func (o *OWM) FiveDayForecastByID(id, cnt int) (*ForecastFiveWeatherData, error) { + idq := fmt.Sprintf("id=%s", strconv.Itoa(id)) + url := fmt.Sprintf(forecastFiveBase, o.apiKey, idq, o.unit, o.lang, cnt) + + var fwd ForecastFiveWeatherData + if err := o.call(url, &fwd); err != nil { return nil, err } - forecastData := ForecastWeatherData{ - Unit: DataUnits[unitChoice], - Lang: langChoice, - Key: k, - Settings: settings, - } - if forecastType == "16" { - forecastData.baseURL = forecast16Base - forecastData.ForecastWeatherJson = &Forecast16WeatherData{} - } else { - forecastData.baseURL = forecast5Base - forecastData.ForecastWeatherJson = &Forecast5WeatherData{} + return &fwd, nil +} + +// FiveDayForecastByZip will provide a forecast for the given zip code. +func (o *OWM) FiveDayForecastByZip(zip, countryCode string, cnt int) (*ForecastFiveWeatherData, error) { + zipq := fmt.Sprintf("zip=%s,%s", zip, countryCode) + url := fmt.Sprintf(forecastFiveBase, o.apiKey, zipq, o.unit, o.lang, cnt) + + var fwd ForecastFiveWeatherData + if err := o.call(url, &fwd); err != nil { + return nil, err } - return &forecastData, nil + return &fwd, nil } -// DailyByName will provide a forecast for the location given for the -// number of days given. -func (f *ForecastWeatherData) DailyByName(location string, days int) error { - response, err := f.client.Get(fmt.Sprintf(f.baseURL, f.Key, fmt.Sprintf("%s=%s", "q", url.QueryEscape(location)), f.Unit, f.Lang, days)) - if err != nil { - return err - } - defer response.Body.Close() +// SixteenDayForecastByName will provide a sixteen day forecast for the given location. +func (o *OWM) SixteenDayForecastByName(location string, cnt int) (*ForecastSixteenWeatherData, error) { + url := fmt.Sprintf(forecastSixteenBase, o.apiKey, "q="+url.QueryEscape(location), o.unit, o.lang, cnt) - return f.ForecastWeatherJson.Decode(response.Body) + var fwd ForecastSixteenWeatherData + if err := o.call(url, &fwd); err != nil { + return nil, err + } + fmt.Printf("%#v\n", fwd) + return &fwd, nil } -// DailyByCoordinates will provide a forecast for the coordinates ID give -// for the number of days given. -func (f *ForecastWeatherData) DailyByCoordinates(location *Coordinates, days int) error { - response, err := f.client.Get(fmt.Sprintf(f.baseURL, f.Key, fmt.Sprintf("lat=%f&lon=%f", location.Latitude, location.Longitude), f.Unit, f.Lang, days)) - if err != nil { - return err +// SixteenDayForecastByCoordinates will provide a sixteen day forecast for the given coordinates. +func (o *OWM) SixteenDayForecastByCoordinates(location *Coordinates, cnt int) (*ForecastSixteenWeatherData, error) { + pos := fmt.Sprintf("lat=%f&lon=%f", location.Latitude, location.Longitude) + url := fmt.Sprintf(forecastSixteenBase, o.apiKey, pos, o.unit, o.lang, cnt) + + var fwd ForecastSixteenWeatherData + if err := o.call(url, &fwd); err != nil { + return nil, err } - defer response.Body.Close() - return f.ForecastWeatherJson.Decode(response.Body) + return &fwd, nil } -// DailyByID will provide a forecast for the location ID give for the -// number of days given. -func (f *ForecastWeatherData) DailyByID(id, days int) error { - response, err := f.client.Get(fmt.Sprintf(f.baseURL, f.Key, fmt.Sprintf("%s=%s", "id", strconv.Itoa(id)), f.Unit, f.Lang, days)) - if err != nil { - return err +// SixteenDayForecastByID will provide a forecast for the given location ID. +func (o *OWM) SixteenDayForecastByID(id, cnt int) (*ForecastSixteenWeatherData, error) { + idq := fmt.Sprintf("id=%s", strconv.Itoa(id)) + url := fmt.Sprintf(forecastSixteenBase, o.apiKey, idq, o.unit, o.lang, cnt) + + var fwd ForecastSixteenWeatherData + if err := o.call(url, &fwd); err != nil { + return nil, err } - defer response.Body.Close() - return f.ForecastWeatherJson.Decode(response.Body) + return &fwd, nil } -// DailyByZip will provide a forecast for the provided zip code. -func (f *ForecastWeatherData) DailyByZip(zip int, countryCode string, days int) error { - response, err := f.client.Get(fmt.Sprintf(f.baseURL, f.Key, fmt.Sprintf("zip=%d,%s", zip, countryCode), f.Unit, f.Lang, days)) - if err != nil { - return err +// SixteenDayForecastByZip will provide a forecast for the given zip code. +func (o *OWM) SixteenDayForecastByZip(zip, countryCode string, cnt int) (*ForecastSixteenWeatherData, error) { + zipq := fmt.Sprintf("zip=%s,%s", zip, countryCode) + url := fmt.Sprintf(forecastSixteenBase, o.apiKey, zipq, o.unit, o.lang, cnt) + + var fwd ForecastSixteenWeatherData + if err := o.call(url, &fwd); err != nil { + return nil, err } - defer response.Body.Close() - return f.ForecastWeatherJson.Decode(response.Body) + return &fwd, nil } diff --git a/forecast16.go b/forecast16.go deleted file mode 100644 index 3ba9f51..0000000 --- a/forecast16.go +++ /dev/null @@ -1,36 +0,0 @@ -package openweathermap - -import ( - "encoding/json" - "io" -) - -// Forecast16WeatherList holds specific query data -type Forecast16WeatherList struct { - Dt int `json:"dt"` - Temp Temperature `json:"temp"` - Pressure float64 `json:"pressure"` - Humidity int `json:"humidity"` - Weather []Weather `json:"weather"` - Speed float64 `json:"speed"` - Deg int `json:"deg"` - Clouds int `json:"clouds"` - Snow float64 `json:"snow"` - Rain float64 `json:"rain"` -} - -// Forecast16WeatherData will hold returned data from queries -type Forecast16WeatherData struct { - COD int `json:"cod"` - Message string `json:"message"` - City City `json:"city"` - Cnt int `json:"cnt"` - List []Forecast16WeatherList `json:"list"` -} - -func (f *Forecast16WeatherData) Decode(r io.Reader) error { - if err := json.NewDecoder(r).Decode(&f); err != nil { - return err - } - return nil -} diff --git a/forecast5.go b/forecast5.go deleted file mode 100644 index 9e52c20..0000000 --- a/forecast5.go +++ /dev/null @@ -1,50 +0,0 @@ -package openweathermap - -import ( - "encoding/json" - "io" - "strings" - "time" -) - -type DtTxt struct { - time.Time -} - -func (dt *DtTxt) UnmarshalJSON(b []byte) error { - t, err := time.Parse("2006-01-02 15:04:05", strings.Trim(string(b), "\"")) - dt.Time = t - return err -} - -func (t *DtTxt) MarshalJSON() ([]byte, error) { - return json.Marshal(t) -} - -// Forecast5WeatherList holds specific query data -type Forecast5WeatherList struct { - Dt int `json:"dt"` - Main Main `json:"main"` - Weather []Weather `json:"weather"` - Clouds Clouds `json:"clouds"` - Wind Wind `json:"wind"` - Rain Rain `json:"rain"` - Snow Snow `json:"snow"` - DtTxt DtTxt `json:"dt_txt"` -} - -// Forecast5WeatherData will hold returned data from queries -type Forecast5WeatherData struct { - // COD string `json:"cod"` - // Message float64 `json:"message"` - City City `json:"city"` - Cnt int `json:"cnt"` - List []Forecast5WeatherList `json:"list"` -} - -func (f *Forecast5WeatherData) Decode(r io.Reader) error { - if err := json.NewDecoder(r).Decode(&f); err != nil { - return err - } - return nil -} diff --git a/forecast_test.go b/forecast_test.go index bcd8dc0..3699839 100644 --- a/forecast_test.go +++ b/forecast_test.go @@ -1,4 +1,4 @@ -// Copyright 2015 Brian J. Downs +// Copyright 2021 Brian J. Downs // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,167 +14,167 @@ package openweathermap -import ( - "net/http" - "os" - "reflect" - "testing" - "time" -) - -var forecastRange = []int{3, 7, 10} - -// TestNewForecast will make sure the a new instance of Forecast is returned -func TestNewForecast(t *testing.T) { - t.Parallel() - - for d := range DataUnits { - t.Logf("Data unit: %s", d) - - if ValidDataUnit(d) { - c5, err := NewForecast("5", d, "ru", os.Getenv("OWM_API_KEY")) - if err != nil { - t.Error(err) - } - - if reflect.TypeOf(c5).String() != "*openweathermap.ForecastWeatherData" { - t.Error("incorrect data type returned") - } - - c16, err := NewForecast("16", d, "ru", os.Getenv("OWM_API_KEY")) - if err != nil { - t.Error(err) - } - - if reflect.TypeOf(c16).String() != "*openweathermap.ForecastWeatherData" { - t.Error("incorrect data type returned") - } - } else { - t.Errorf("unusable data unit - %s", d) - } - } - - _, err := NewForecast("", "asdf", "en", os.Getenv("OWM_API_KEY")) - if err == nil { - t.Error("created instance when it shouldn't have") - } -} - -// TestNewForecastWithCustomHttpClient will verify that a new instance of ForecastWeatherData -// is created with custom http client -func TestNewForecastWithCustomHttpClient(t *testing.T) { - - hc := http.DefaultClient - hc.Timeout = time.Duration(1) * time.Second - f, err := NewForecast("5", "c", "en", os.Getenv("OWM_API_KEY"), WithHttpClient(hc)) - if err != nil { - t.Error(err) - } - - if reflect.TypeOf(f).String() != "*openweathermap.ForecastWeatherData" { - t.Error("incorrect data type returned") - } - - // expected := time.Duration(1) * time.Second - // if f.client.Timeout != expected { - // t.Errorf("Expected Duration %v, but got %v", expected, f.client.Timeout) - // } -} - -// TestNewForecastWithInvalidOptions will verify that returns an error with -// invalid option -func TestNewForecastWithInvalidOptions(t *testing.T) { - - optionsPattern := [][]Option{ - {nil}, - {nil, nil}, - {WithHttpClient(&http.Client{}), nil}, - {nil, WithHttpClient(&http.Client{})}, - } - - for _, options := range optionsPattern { - c, err := NewForecast("5", "c", "en", os.Getenv("OWM_API_KEY"), options...) - if err == errInvalidOption { - t.Logf("Received expected invalid option error. message: %s", err.Error()) - } else if err != nil { - t.Errorf("Expected %v, but got %v", errInvalidOption, err) - } - if c != nil { - t.Errorf("Expected nil, but got %v", c) - } - } -} - -// TestNewForecastWithCustomHttpClient will verify that returns an error with -// invalid http client -func TestNewForecastWithInvalidHttpClient(t *testing.T) { - - f, err := NewForecast("5", "c", "en", os.Getenv("OWM_API_KEY"), WithHttpClient(nil)) - if err == errInvalidHttpClient { - t.Logf("Received expected bad client error. message: %s", err.Error()) - } else if err != nil { - t.Errorf("Expected %v, but got %v", errInvalidHttpClient, err) - } - if f != nil { - t.Errorf("Expected nil, but got %v", f) - } -} - -// TestDailyByName will verify that a daily forecast can be retrieved for -// a given named location -func TestDailyByName(t *testing.T) { - t.Parallel() - - f, err := NewForecast("5", "f", "fi", os.Getenv("OWM_API_KEY")) - if err != nil { - t.Error(err) - } - - for _, d := range forecastRange { - err = f.DailyByName("Dubai", d) - if err != nil { - t.Error(err) - } - } -} - -// TestDailyByCooridinates will verify that a daily forecast can be retrieved -// for a given set of coordinates -func TestDailyByCoordinates(t *testing.T) { - t.Parallel() - - f, err := NewForecast("5", "f", "PL", os.Getenv("OWM_API_KEY")) - if err != nil { - t.Error(err) - } - - for _, d := range forecastRange { - err = f.DailyByCoordinates( - &Coordinates{ - Longitude: -112.07, - Latitude: 33.45, - }, d, - ) - if err != nil { - t.Error(err) - } - } -} - -// TestDailyByID will verify that a daily forecast can be retrieved for a -// given location ID -func TestDailyByID(t *testing.T) { - t.Parallel() - - f, err := NewForecast("5", "c", "fr", os.Getenv("OWM_API_KEY")) - if err != nil { - t.Error(err) - } - - for _, d := range forecastRange { - err = f.DailyByID(524901, d) - if err != nil { - t.Error(err) - } - } -} +// import ( +// "net/http" +// "os" +// "reflect" +// "testing" +// "time" +// ) + +// var forecastRange = []int{3, 7, 10} + +// // TestNewForecast will make sure the a new instance of Forecast is returned +// func TestNewForecast(t *testing.T) { +// t.Parallel() + +// for d := range DataUnits { +// t.Logf("Data unit: %s", d) + +// if ValidDataUnit(d) { +// c5, err := NewForecast("5", d, "ru", os.Getenv("OWM_API_KEY")) +// if err != nil { +// t.Error(err) +// } + +// if reflect.TypeOf(c5).String() != "*openweathermap.ForecastWeatherData" { +// t.Error("incorrect data type returned") +// } + +// c16, err := NewForecast("16", d, "ru", os.Getenv("OWM_API_KEY")) +// if err != nil { +// t.Error(err) +// } + +// if reflect.TypeOf(c16).String() != "*openweathermap.ForecastWeatherData" { +// t.Error("incorrect data type returned") +// } +// } else { +// t.Errorf("unusable data unit - %s", d) +// } +// } + +// _, err := NewForecast("", "asdf", "en", os.Getenv("OWM_API_KEY")) +// if err == nil { +// t.Error("created instance when it shouldn't have") +// } +// } + +// // TestNewForecastWithCustomHttpClient will verify that a new instance of ForecastWeatherData +// // is created with custom http client +// func TestNewForecastWithCustomHttpClient(t *testing.T) { + +// hc := http.DefaultClient +// hc.Timeout = time.Duration(1) * time.Second +// f, err := NewForecast("5", "c", "en", os.Getenv("OWM_API_KEY"), WithHttpClient(hc)) +// if err != nil { +// t.Error(err) +// } + +// if reflect.TypeOf(f).String() != "*openweathermap.ForecastWeatherData" { +// t.Error("incorrect data type returned") +// } + +// // expected := time.Duration(1) * time.Second +// // if f.client.Timeout != expected { +// // t.Errorf("Expected Duration %v, but got %v", expected, f.client.Timeout) +// // } +// } + +// // TestNewForecastWithInvalidOptions will verify that returns an error with +// // invalid option +// func TestNewForecastWithInvalidOptions(t *testing.T) { + +// optionsPattern := [][]Option{ +// {nil}, +// {nil, nil}, +// {WithHttpClient(&http.Client{}), nil}, +// {nil, WithHttpClient(&http.Client{})}, +// } + +// for _, options := range optionsPattern { +// c, err := NewForecast("5", "c", "en", os.Getenv("OWM_API_KEY"), options...) +// if err == errInvalidOption { +// t.Logf("Received expected invalid option error. message: %s", err.Error()) +// } else if err != nil { +// t.Errorf("Expected %v, but got %v", errInvalidOption, err) +// } +// if c != nil { +// t.Errorf("Expected nil, but got %v", c) +// } +// } +// } + +// // TestNewForecastWithCustomHttpClient will verify that returns an error with +// // invalid http client +// func TestNewForecastWithInvalidHttpClient(t *testing.T) { + +// f, err := NewForecast("5", "c", "en", os.Getenv("OWM_API_KEY"), WithHttpClient(nil)) +// if err == errInvalidHttpClient { +// t.Logf("Received expected bad client error. message: %s", err.Error()) +// } else if err != nil { +// t.Errorf("Expected %v, but got %v", errInvalidHttpClient, err) +// } +// if f != nil { +// t.Errorf("Expected nil, but got %v", f) +// } +// } + +// // TestDailyByName will verify that a daily forecast can be retrieved for +// // a given named location +// func TestDailyByName(t *testing.T) { +// t.Parallel() + +// f, err := NewForecast("5", "f", "fi", os.Getenv("OWM_API_KEY")) +// if err != nil { +// t.Error(err) +// } + +// for _, d := range forecastRange { +// err = f.DailyByName("Dubai", d) +// if err != nil { +// t.Error(err) +// } +// } +// } + +// // TestDailyByCooridinates will verify that a daily forecast can be retrieved +// // for a given set of coordinates +// func TestDailyByCoordinates(t *testing.T) { +// t.Parallel() + +// f, err := NewForecast("5", "f", "PL", os.Getenv("OWM_API_KEY")) +// if err != nil { +// t.Error(err) +// } + +// for _, d := range forecastRange { +// err = f.DailyByCoordinates( +// &Coordinates{ +// Longitude: -112.07, +// Latitude: 33.45, +// }, d, +// ) +// if err != nil { +// t.Error(err) +// } +// } +// } + +// // TestDailyByID will verify that a daily forecast can be retrieved for a +// // given location ID +// func TestDailyByID(t *testing.T) { +// t.Parallel() + +// f, err := NewForecast("5", "c", "fr", os.Getenv("OWM_API_KEY")) +// if err != nil { +// t.Error(err) +// } + +// for _, d := range forecastRange { +// err = f.DailyByID(524901, d) +// if err != nil { +// t.Error(err) +// } +// } +// } diff --git a/global_weather_alerts.go b/global_weather_alerts.go new file mode 100644 index 0000000..49cc28b --- /dev/null +++ b/global_weather_alerts.go @@ -0,0 +1 @@ +package openweathermap diff --git a/history.go b/history.go index bcac611..7c70c8c 100644 --- a/history.go +++ b/history.go @@ -1,4 +1,4 @@ -// Copyright 2015 Brian J. Downs +// Copyright 2021 Brian J. Downs // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,10 +15,8 @@ package openweathermap import ( - "encoding/json" "fmt" "net/url" - "strings" ) // HistoricalParameters struct holds the (optional) fields to be @@ -61,89 +59,43 @@ type HistoricalWeatherData struct { CalcTime float64 `json:"calctime"` Cnt int `json:"cnt"` List []WeatherHistory `json:"list"` - Unit string - Key string - *Settings } -// NewHistorical returns a new HistoricalWeatherData pointer with -//the supplied arguments. -func NewHistorical(unit, key string, options ...Option) (*HistoricalWeatherData, error) { - h := &HistoricalWeatherData{ - Settings: NewSettings(), - } - - unitChoice := strings.ToUpper(unit) - if !ValidDataUnit(unitChoice) { - return nil, errUnitUnavailable - } - h.Unit = DataUnits[unitChoice] +// HistoryByName will return the history for the provided location. +func (o *OWM) HistoryByName(location string, hp *HistoricalParameters) (*HistoricalWeatherData, error) { + base := fmt.Sprintf(historyURL, "appid=%s&%s&type=hour&mode=json&units=%s&lang=%s&cnt=%d&start=%d&end=%d") + url := fmt.Sprintf(base, o.apiKey, "q="+url.QueryEscape(location), o.unit, o.lang, hp.Cnt, hp.Start, hp.End) - var err error - h.Key, err = setKey(key) - if err != nil { + var hwd HistoricalWeatherData + if err := o.call(url, &hwd); err != nil { return nil, err } - if err := setOptions(h.Settings, options); err != nil { - return nil, err - } - return h, nil + return &hwd, nil } -// HistoryByName will return the history for the provided location -func (h *HistoricalWeatherData) HistoryByName(location string) error { - response, err := h.client.Get(fmt.Sprintf(fmt.Sprintf(historyURL, "city?appid=%s&q=%s"), h.Key, url.QueryEscape(location))) - if err != nil { - return err - } - defer response.Body.Close() - - if err = json.NewDecoder(response.Body).Decode(&h); err != nil { - return err - } +// HistoryByID will return the history for the provided id. +func (o *OWM) HistoryByID(id int, hp *HistoricalParameters) (*HistoricalWeatherData, error) { + base := fmt.Sprintf(historyURL, "appid=%s&id=%d&type=hour&mode=json&units=%s&lang=%s&cnt=%d&start=%d&end=%d") + url := fmt.Sprintf(base, o.apiKey, id, o.unit, o.lang, hp.Cnt, hp.Start, hp.End) - return nil -} - -// HistoryByID will return the history for the provided location ID -func (h *HistoricalWeatherData) HistoryByID(id int, hp ...*HistoricalParameters) error { - if len(hp) > 0 { - response, err := h.client.Get(fmt.Sprintf(fmt.Sprintf(historyURL, "city?appid=%s&id=%d&type=hour&start%d&end=%d&cnt=%d"), h.Key, id, hp[0].Start, hp[0].End, hp[0].Cnt)) - if err != nil { - return err - } - defer response.Body.Close() - - if err = json.NewDecoder(response.Body).Decode(&h); err != nil { - return err - } - } - - response, err := h.client.Get(fmt.Sprintf(fmt.Sprintf(historyURL, "city?appid=%s&id=%d"), h.Key, id)) - if err != nil { - return err - } - defer response.Body.Close() - - if err = json.NewDecoder(response.Body).Decode(&h); err != nil { - return err + var hwd HistoricalWeatherData + if err := o.call(url, &hwd); err != nil { + return nil, err } - return nil + return &hwd, nil } // HistoryByCoord will return the history for the provided coordinates -func (h *HistoricalWeatherData) HistoryByCoord(location *Coordinates, hp *HistoricalParameters) error { - response, err := h.client.Get(fmt.Sprintf(fmt.Sprintf(historyURL, "appid=%s&lat=%f&lon=%f&start=%d&end=%d"), h.Key, location.Latitude, location.Longitude, hp.Start, hp.End)) - if err != nil { - return err - } - defer response.Body.Close() +func (o *OWM) HistoryByCoord(location *Coordinates, hp *HistoricalParameters) (*HistoricalWeatherData, error) { + base := fmt.Sprintf(historyURL, "appid=%s&lat=%f&lon=%f&start=%d&end=%d") + url := fmt.Sprintf(base, o.apiKey, location.Latitude, location.Longitude, hp.Start, hp.End) - if err = json.NewDecoder(response.Body).Decode(&h); err != nil { - return err + var hwd HistoricalWeatherData + if err := o.call(url, &hwd); err != nil { + return nil, err } - return nil + return &hwd, nil } diff --git a/history_test.go b/history_test.go index 6ac2a47..7e2835e 100644 --- a/history_test.go +++ b/history_test.go @@ -1,4 +1,4 @@ -// Copyright 2015 Brian J. Downs +// Copyright 2021 Brian J. Downs // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,146 +14,146 @@ package openweathermap -import ( - "net/http" - "os" - "reflect" - "testing" - "time" -) - -// TestNewHistory verifies NewHistorical does as advertised -func TestNewHistory(t *testing.T) { - t.Parallel() - - for d := range DataUnits { - t.Logf("Data unit: %s", d) - - if ValidDataUnit(d) { - c, err := NewHistorical(d, os.Getenv("OWM_API_KEY")) - if err != nil { - t.Error(err) - } - if reflect.TypeOf(c).String() != "*openweathermap.HistoricalWeatherData" { - t.Error("incorrect data type returned") - } - } else { - t.Errorf("unusable data unit - %s", d) - } - } - - _, err := NewHistorical("asdf", os.Getenv("OWM_API_KEY")) - if err == nil { - t.Error("created instance when it shouldn't have") - } -} - -// TestNewHistoryWithCustomHttpClient will verify that a new instance of HistoricalWeatherData -// is created with custom http client -func TestNewHistoryWithCustomHttpClient(t *testing.T) { - - hc := http.DefaultClient - hc.Timeout = time.Duration(1) * time.Second - h, err := NewHistorical("c", os.Getenv("OWM_API_KEY"), WithHttpClient(hc)) - if err != nil { - t.Error(err) - } - - if reflect.TypeOf(h).String() != "*openweathermap.HistoricalWeatherData" { - t.Error("incorrect data type returned") - } - - expected := time.Duration(1) * time.Second - if h.client.Timeout != expected { - t.Errorf("Expected Duration %v, but got %v", expected, h.client.Timeout) - } -} - -// TestNewHistoryWithInvalidOptions will verify that returns an error with -// invalid option -func TestNewHistoryWithInvalidOptions(t *testing.T) { - - optionsPattern := [][]Option{ - {nil}, - {nil, nil}, - {WithHttpClient(&http.Client{}), nil}, - {nil, WithHttpClient(&http.Client{})}, - } - - for _, options := range optionsPattern { - c, err := NewHistorical("c", os.Getenv("OWM_API_KEY"), options...) - if err == errInvalidOption { - t.Logf("Received expected invalid option error. message: %s", err.Error()) - } else if err != nil { - t.Errorf("Expected %v, but got %v", errInvalidOption, err) - } - if c != nil { - t.Errorf("Expected nil, but got %v", c) - } - } -} - -// TestNewHistoryWithInvalidHttpClient will verify that returns an error with -// invalid http client -func TestNewHistoryWithInvalidHttpClient(t *testing.T) { - - h, err := NewHistorical("c", os.Getenv("OWM_API_KEY"), WithHttpClient(nil)) - if err == errInvalidHttpClient { - t.Logf("Received expected bad client error. message: %s", err.Error()) - } else if err != nil { - t.Errorf("Expected %v, but got %v", errInvalidHttpClient, err) - } - if h != nil { - t.Errorf("Expected nil, but got %v", h) - } -} - -// TestHistoryByName -func TestHistoryByName(t *testing.T) { - t.Parallel() - h, err := NewHistorical("F", os.Getenv("OWM_API_KEY")) - if err != nil { - t.Error(err) - } - if err := h.HistoryByName("Vancouver"); err != nil { - t.Error(err) - } -} - -// TestHistoryByID -func TestHistoryByID(t *testing.T) { - t.Parallel() - h, err := NewHistorical("F", os.Getenv("OWM_API_KEY")) - if err != nil { - t.Error(err) - } - hp := &HistoricalParameters{ - Start: 1461598510, - End: 1461588510, - Cnt: 1, - } - if err := h.HistoryByID(5344157, hp); err != nil { - t.Error(err) - } -} - -// TestHistoryByCoord -func TestHistoryByCoord(t *testing.T) { - t.Parallel() - h, err := NewHistorical("F", os.Getenv("OWM_API_KEY")) - if err != nil { - t.Error(err) - } - coords := &Coordinates{ - Longitude: -112.07, - Latitude: 33.45, - } - hp := &HistoricalParameters{ - Start: 1461598510, - End: 1461588510, - Cnt: 1, - } - if err := h.HistoryByCoord(coords, hp); err != nil { - t.Error(err) - } -} +// import ( +// "net/http" +// "os" +// "reflect" +// "testing" +// "time" +// ) + +// // TestNewHistory verifies NewHistorical does as advertised +// func TestNewHistory(t *testing.T) { +// t.Parallel() + +// for d := range DataUnits { +// t.Logf("Data unit: %s", d) + +// if ValidDataUnit(d) { +// c, err := NewHistorical(d, os.Getenv("OWM_API_KEY")) +// if err != nil { +// t.Error(err) +// } +// if reflect.TypeOf(c).String() != "*openweathermap.HistoricalWeatherData" { +// t.Error("incorrect data type returned") +// } +// } else { +// t.Errorf("unusable data unit - %s", d) +// } +// } + +// _, err := NewHistorical("asdf", os.Getenv("OWM_API_KEY")) +// if err == nil { +// t.Error("created instance when it shouldn't have") +// } +// } + +// // TestNewHistoryWithCustomHttpClient will verify that a new instance of HistoricalWeatherData +// // is created with custom http client +// func TestNewHistoryWithCustomHttpClient(t *testing.T) { + +// hc := http.DefaultClient +// hc.Timeout = time.Duration(1) * time.Second +// h, err := NewHistorical("c", os.Getenv("OWM_API_KEY"), WithHttpClient(hc)) +// if err != nil { +// t.Error(err) +// } + +// if reflect.TypeOf(h).String() != "*openweathermap.HistoricalWeatherData" { +// t.Error("incorrect data type returned") +// } + +// expected := time.Duration(1) * time.Second +// if h.client.Timeout != expected { +// t.Errorf("Expected Duration %v, but got %v", expected, h.client.Timeout) +// } +// } + +// // TestNewHistoryWithInvalidOptions will verify that returns an error with +// // invalid option +// func TestNewHistoryWithInvalidOptions(t *testing.T) { + +// optionsPattern := [][]Option{ +// {nil}, +// {nil, nil}, +// {WithHttpClient(&http.Client{}), nil}, +// {nil, WithHttpClient(&http.Client{})}, +// } + +// for _, options := range optionsPattern { +// c, err := NewHistorical("c", os.Getenv("OWM_API_KEY"), options...) +// if err == errInvalidOption { +// t.Logf("Received expected invalid option error. message: %s", err.Error()) +// } else if err != nil { +// t.Errorf("Expected %v, but got %v", errInvalidOption, err) +// } +// if c != nil { +// t.Errorf("Expected nil, but got %v", c) +// } +// } +// } + +// // TestNewHistoryWithInvalidHttpClient will verify that returns an error with +// // invalid http client +// func TestNewHistoryWithInvalidHttpClient(t *testing.T) { + +// h, err := NewHistorical("c", os.Getenv("OWM_API_KEY"), WithHttpClient(nil)) +// if err == errInvalidHttpClient { +// t.Logf("Received expected bad client error. message: %s", err.Error()) +// } else if err != nil { +// t.Errorf("Expected %v, but got %v", errInvalidHttpClient, err) +// } +// if h != nil { +// t.Errorf("Expected nil, but got %v", h) +// } +// } + +// // TestHistoryByName +// func TestHistoryByName(t *testing.T) { +// t.Parallel() +// h, err := NewHistorical("F", os.Getenv("OWM_API_KEY")) +// if err != nil { +// t.Error(err) +// } +// if err := h.HistoryByName("Vancouver"); err != nil { +// t.Error(err) +// } +// } + +// // TestHistoryByID +// func TestHistoryByID(t *testing.T) { +// t.Parallel() +// h, err := NewHistorical("F", os.Getenv("OWM_API_KEY")) +// if err != nil { +// t.Error(err) +// } +// hp := &HistoricalParameters{ +// Start: 1461598510, +// End: 1461588510, +// Cnt: 1, +// } +// if err := h.HistoryByID(5344157, hp); err != nil { +// t.Error(err) +// } +// } + +// // TestHistoryByCoord +// func TestHistoryByCoord(t *testing.T) { +// t.Parallel() +// h, err := NewHistorical("F", os.Getenv("OWM_API_KEY")) +// if err != nil { +// t.Error(err) +// } +// coords := &Coordinates{ +// Longitude: -112.07, +// Latitude: 33.45, +// } +// hp := &HistoricalParameters{ +// Start: 1461598510, +// End: 1461588510, +// Cnt: 1, +// } +// if err := h.HistoryByCoord(coords, hp); err != nil { +// t.Error(err) +// } +// } diff --git a/one_call.go b/one_call.go new file mode 100644 index 0000000..af67124 --- /dev/null +++ b/one_call.go @@ -0,0 +1,136 @@ +package openweathermap + +import ( + "fmt" +) + +const ( + oneCallBaseURL = "https://api.openweathermap.org/data/2.5/onecall?%s" +) + +// OneTimeCurrent +type OneTimeCurrent struct { + Dt int64 `json:"dt"` + Sunrise int64 `json:"sunrise"` + Sunset int64 `json:"sunset"` + Temp float64 `json:"temp"` + FeelsLike float64 `json:"feels_like"` + Pressure float64 `json:"pressure"` + Humidity int64 `json:"humidity"` + DewPoint float64 `json:"dew_point"` + UVI int64 `json:"uvi"` + Clouds int64 `json:"clouds"` + Visibility int64 `json:"visibility"` + WindSpeed float64 `json:"wind_speed"` + WindDeg float64 `json:"wind_deg"` + Weather []Weather `json:"weather"` +} + +// OneTimeMinutely +type OneTimeMinutely struct { + Dt int64 `json:"dt"` + Precipitation int64 `json:"precipitation"` +} + +// OneTimeHourly +type OneTimeHourly struct { + Dt int64 `json:"dt"` + Temp float64 `json:"temp"` + FeelsLike float64 `json:"feels_like"` + Pressure float64 `json:"pressure"` + Humidity int64 `json:"humidity"` + DewPoint float64 `json:"dew_point"` + UVI int64 `json:"uvi"` + Clouds int64 `json:"clouds"` + Visibility int64 `json:"visibility"` + WindSpeed float64 `json:"wind_speed"` + WindDeg float64 `json:"wind_deg"` + Weather []Weather `json:"weather"` + Pop float64 `json:"pop"` +} + +// TempFullDay +type TempFullDay struct { + Day float64 `json:"day"` + Min float64 `json:"Min"` + Max float64 `json:"max"` + Night float64 `json:"night"` + Eve float64 `json:"eve"` + Morn float64 `json:"morn"` +} + +// FeelsLikeFullDay +type FeelsLikeFullDay struct { + Day float64 `json:"day"` + Night float64 `json:"night"` + Eve float64 `json:"eve"` + Morn float64 `json:"morn"` +} + +// OneTimeDaily +type OneTimeDaily struct { + Dt int64 `json:"dt"` + Sunrise int64 `json:"sunrise"` + Sunset int64 `json:"sunset"` + Temp *TempFullDay `json:"tmep"` + FeelsLike *FeelsLikeFullDay `json:"feels_like"` + Pressure float64 `json:"pressure"` + Humidity int64 `json:"humidity"` + DewPoint float64 `json:"dew_point"` + WindSpeed float64 `json:"wind_speed"` + WindDeg float64 `json:"wind_deg"` + Weather []Weather `json:"weather"` + Clouds int64 `json:"clouds"` + Pop float64 `json:"pop"` + UVI int64 `json:"uvi"` +} + +// Alert +type Alert struct { + SenderName string `json:"sender_name"` + Event string `json:"event"` + Start int64 `json:"start"` + End int64 `json:"end"` + Description string `json:"description"` + EventLeve string `json:"event_level"` +} + +// OneCallData +type OneCallData struct { + Longitude float64 `json:"lon"` + Latitude float64 `json:"lat"` + Timezone string `json:"timezone"` + TimezoneOffset int64 `json:"timezone_offset"` + Current *OneTimeCurrent `json:"current"` + Weather *Weather `json:"weather"` + Minutely []OneTimeMinutely `json:"minutely"` + Hourly []OneTimeHourly `json:"hourly"` + Daily []OneTimeDaily `json:"daily"` + Alerts []Alert `json:"alerts"` +} + +// OneCallCurrentAndForecast +func (o *OWM) OneCallCurrentAndForecast(location *Coordinates) (*OneCallData, error) { + base := fmt.Sprintf(oneCallBaseURL, "lat=%s&lon=%s&exclude=%s&lang=%s&appid=%s") + url := fmt.Sprintf(base, location.Latitude, location.Longitude, o.unit, o.lang, o.apiKey) + + var otc OneCallData + if err := o.call(url, &otc); err != nil { + return nil, err + } + + return &otc, nil +} + +// OneCallHistorical +func (o *OWM) OneCallHistorical(location *Coordinates, dt int64) (*OneCallData, error) { + base := fmt.Sprintf(oneCallBaseURL, "lat=%s&lon=%s&exclude=%s&lang=%s&appid=%s") + url := fmt.Sprintf(base, location.Latitude, location.Longitude, o.unit, o.lang, o.apiKey) + + var otc OneCallData + if err := o.call(url, &otc); err != nil { + return nil, err + } + + return &otc, nil +} diff --git a/openweathermap.go b/openweathermap.go index 7b9cb29..8b25e33 100644 --- a/openweathermap.go +++ b/openweathermap.go @@ -1,4 +1,4 @@ -// Copyright 2015 Brian J. Downs +// Copyright 2021 Brian J. Downs // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,31 +15,36 @@ package openweathermap import ( + "encoding/json" + "encoding/xml" "errors" + "fmt" + "io/ioutil" "net/http" + "os" + "strings" ) -var errUnitUnavailable = errors.New("unit unavailable") -var errLangUnavailable = errors.New("language unavailable") -var errInvalidKey = errors.New("invalid api key") -var errInvalidOption = errors.New("invalid option") -var errInvalidHttpClient = errors.New("invalid http client") -var errForecastUnavailable = errors.New("forecast unavailable") - -// DataUnits represents the character chosen to represent the temperature notation -var DataUnits = map[string]string{"C": "metric", "F": "imperial", "K": "internal"} -var ( - baseURL = "http://api.openweathermap.org/data/2.5/weather?%s" - iconURL = "http://openweathermap.org/img/w/%s" - stationURL = "http://api.openweathermap.org/data/2.5/station?id=%d" - forecast5Base = "http://api.openweathermap.org/data/2.5/forecast?appid=%s&%s&mode=json&units=%s&lang=%s&cnt=%d" - forecast16Base = "http://api.openweathermap.org/data/2.5/forecast/daily?appid=%s&%s&mode=json&units=%s&lang=%s&cnt=%d" - historyURL = "http://api.openweathermap.org/data/2.5/history/%s" - pollutionURL = "http://api.openweathermap.org/pollution/v1/co/" - uvURL = "http://api.openweathermap.org/data/2.5/" - dataPostURL = "http://openweathermap.org/data/post" +const ( + baseURL = "https://api.openweathermap.org/data/2.5/weather?%s" + iconURL = "https://openweathermap.org/img/w/%s" + stationURL = "https://api.openweathermap.org/data/2.5/station?id=%d" + forecastFiveBase = "https://api.openweathermap.org/data/2.5/forecast?appid=%s&%s&mode=json&units=%s&lang=%s&cnt=%d" + forecastSixteenBase = "https://api.openweathermap.org/data/2.5/forecast/daily?appid=%s&%s&mode=json&units=%s&lang=%s&cnt=%d" + historyURL = "https://api.openweathermap.org/data/2.5/history/city?%s" + pollutionURL = "https://api.openweathermap.org/pollution/v1/co/" + uvURL = "https://api.openweathermap.org/data/2.5/" + dataPostURL = "https://openweathermap.org/data/post" ) +// dataUnits represents the character chosen to represent +// the temperature notation +var dataUnits = map[string]string{ + "C": "metric", + "F": "imperial", + "K": "internal", +} + // LangCodes holds all supported languages to be used // inspried and sourced from @bambocher (github.com/bambocher) var LangCodes = map[string]string{ @@ -68,21 +73,121 @@ var LangCodes = map[string]string{ "ZH_CN": "Chinese Simplified", } -// Config will hold default settings to be passed into the +// Opts will hold default settings to be passed into the // "NewCurrent, NewForecast, etc}" functions. -type Config struct { - Mode string // user choice of JSON or XML - Unit string // measurement for results to be displayed. F, C, or K - Lang string // should reference a key in the LangCodes map - APIKey string // API Key for connecting to the OWM - Username string // Username for posting data - Password string // Pasword for posting data +type Opts struct { + Host string // + Mode string // user choice of JSON or XML + Unit string // measurement for results to be displayed. F, C, or K + Lang string // should reference a key in the LangCodes map + APIKey string // API Key for connecting to the OWM + Username string // Username for posting data + Password string // Pasword for posting data + Client *http.Client // HTTP client to use for calls to OWM +} + +// OWM +type OWM struct { + host string // + mode string // user choice of JSON or XML + unit string // measurement for results to be displayed. F, C, or K + lang string // should reference a key in the LangCodes map + apiKey string // API Key for connecting to the OWM + username string // Username for posting data + password string // Pasword for posting data + client *http.Client // HTTP client to use for calls to OWM +} + +// New +func New(opts *Opts) (*OWM, error) { + var owm OWM + + switch { + case opts.Host != "": + owm.host = "http://" + opts.Host + "/data/2.5/weather?%s" + default: + owm.host = baseURL + } + + switch opts.Mode { + case "JSON", "XML": + owm.mode = strings.ToLower(opts.Mode) + case "": + owm.mode = "json" + default: + return nil, fmt.Errorf("invalid serialization format: %s", opts.Mode) + } + + switch { + case validDataUnit(opts.Unit): + owm.unit = opts.Unit + case opts.Unit == "": + owm.unit = "F" + default: + return nil, fmt.Errorf("invalid unit: %s", opts.Unit) + } + + switch { + case validLangCode(opts.Lang): + owm.lang = opts.Lang + case opts.Lang == "": + owm.lang = "EN" + default: + return nil, fmt.Errorf("invalid language code: %s", opts.Lang) + } + + switch { + case opts.APIKey != "": + if validAPIKey(opts.APIKey) { + owm.apiKey = opts.APIKey + } + default: + if apiKey := os.Getenv("OWM_API_KEY"); apiKey != "" { + if validAPIKey(apiKey) { + owm.apiKey = apiKey + } else { + return nil, errors.New("invalid api key") + } + } else { + return nil, errors.New("an API key is required for use of the OWM api") + } + } + + switch { + case opts.Client != nil: + owm.client = opts.Client + default: + owm.client = http.DefaultClient + } + + return &owm, nil + } -// APIError returned on failed API calls. -type APIError struct { - Message string `json:"message"` - COD string `json:"cod"` +// call +func (o *OWM) call(url string, payload interface{}) error { + res, err := o.client.Get(url) + if err != nil { + return err + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + b, err := ioutil.ReadAll(res.Body) + if err != nil { + return err + } + return errors.New(string(b)) + } + + switch o.mode { + case "json": + return json.NewDecoder(res.Body).Decode(payload) + case "xml": + return xml.NewDecoder(res.Body).Decode(payload) + } + + return nil } // Coordinates struct holds longitude and latitude data in returned @@ -112,7 +217,7 @@ type Wind struct { // Weather struct holds high-level, basic info on the returned // data. type Weather struct { - ID int `json:"id"` + ID int64 `json:"id"` Main string `json:"main"` Description string `json:"description"` Icon string `json:"icon"` @@ -127,27 +232,19 @@ type Main struct { Pressure float64 `json:"pressure"` SeaLevel float64 `json:"sea_level"` GrndLevel float64 `json:"grnd_level"` - Humidity int `json:"humidity"` + Humidity int64 `json:"humidity"` + TempKF float64 `json:"tmep_kf"` } // Clouds struct holds data regarding cloud cover. type Clouds struct { - All int `json:"all"` -} - -// return key -// } -func setKey(key string) (string, error) { - if err := ValidAPIKey(key); err != nil { - return "", err - } - return key, nil + All int64 `json:"all"` } -// ValidDataUnit makes sure the string passed in is an accepted +// validDataUnit makes sure the string passed in is an accepted // unit of measure to be used for the return data. -func ValidDataUnit(u string) bool { - for d := range DataUnits { +func validDataUnit(u string) bool { + for d := range dataUnits { if u == d { return true } @@ -155,9 +252,9 @@ func ValidDataUnit(u string) bool { return false } -// ValidLangCode makes sure the string passed in is an +// validLangCode makes sure the string passed in is an // acceptable lang code. -func ValidLangCode(c string) bool { +func validLangCode(c string) bool { for d := range LangCodes { if c == d { return true @@ -166,10 +263,10 @@ func ValidLangCode(c string) bool { return false } -// ValidDataUnitSymbol makes sure the string passed in is an +// validDataUnitSymbol makes sure the string passed in is an // acceptable data unit symbol. -func ValidDataUnitSymbol(u string) bool { - for _, d := range DataUnits { +func validDataUnitSymbol(u string) bool { + for _, d := range dataUnits { if u == d { return true } @@ -177,53 +274,7 @@ func ValidDataUnitSymbol(u string) bool { return false } -// ValidAPIKey makes sure that the key given is a valid one -func ValidAPIKey(key string) error { - if len(key) != 32 { - return errors.New("invalid key") - } - return nil -} - -// CheckAPIKeyExists will see if an API key has been set. -func (c *Config) CheckAPIKeyExists() bool { return len(c.APIKey) > 1 } - -// Settings holds the client settings -type Settings struct { - client *http.Client -} - -// NewSettings returns a new Setting pointer with default http client. -func NewSettings() *Settings { - return &Settings{ - client: http.DefaultClient, - } -} - -// Optional client settings -type Option func(s *Settings) error - -// WithHttpClient sets custom http client when creating a new Client. -func WithHttpClient(c *http.Client) Option { - return func(s *Settings) error { - if c == nil { - return errInvalidHttpClient - } - s.client = c - return nil - } -} - -// setOptions sets Optional client settings to the Settings pointer -func setOptions(settings *Settings, options []Option) error { - for _, option := range options { - if option == nil { - return errInvalidOption - } - err := option(settings) - if err != nil { - return err - } - } - return nil +// validAPIKey makes sure that the key given is a valid one +func validAPIKey(key string) bool { + return len(key) != 33 } diff --git a/openweathermap_test.go b/openweathermap_test.go index 824d0ec..37ace40 100644 --- a/openweathermap_test.go +++ b/openweathermap_test.go @@ -1,4 +1,4 @@ -// Copyright 2015 Brian J. Downs +// Copyright 2021 Brian J. Downs // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,52 +14,52 @@ package openweathermap -import ( - "testing" -) +// import ( +// "testing" +// ) -// TestValidDataUnit tests whether or not ValidDataUnit provides -// the correct assertion on provided data unit. -func TestValidDataUnit(t *testing.T) { - for u := range DataUnits { - if !ValidDataUnit(u) { - t.Error("False positive on data unit") - } - } +// // TestValidDataUnit tests whether or not ValidDataUnit provides +// // the correct assertion on provided data unit. +// func TestValidDataUnit(t *testing.T) { +// for u := range DataUnits { +// if !ValidDataUnit(u) { +// t.Error("False positive on data unit") +// } +// } - if ValidDataUnit("anything") { - t.Error("Invalid data unit") - } -} +// if ValidDataUnit("anything") { +// t.Error("Invalid data unit") +// } +// } -func TestDataUnitValues(t *testing.T) { - for _, s := range DataUnits { - if !ValidDataUnitSymbol(s) { - t.Error("False positive on data unit symbol") - } - } +// func TestDataUnitValues(t *testing.T) { +// for _, s := range DataUnits { +// if !ValidDataUnitSymbol(s) { +// t.Error("False positive on data unit symbol") +// } +// } - if ValidDataUnitSymbol("X") { - t.Error("Invalid data unit symbol") - } -} +// if ValidDataUnitSymbol("X") { +// t.Error("Invalid data unit symbol") +// } +// } -func TestCheckAPIKeyExists(t *testing.T) { - c := &Config{ - APIKey: "asdf1234", - } +// func TestCheckAPIKeyExists(t *testing.T) { +// c := &Config{ +// APIKey: "asdf1234", +// } - if !c.CheckAPIKeyExists() { - t.Error("Key not set") - } -} +// if !c.CheckAPIKeyExists() { +// t.Error("Key not set") +// } +// } -// TestSetOptionsWithEmpty tests setOptions function will do nothing -// when options are empty. -func TestSetOptionsWithEmpty(t *testing.T) { - s := NewSettings() - err := setOptions(s, nil) - if err != nil { - t.Error(err) - } -} +// // TestSetOptionsWithEmpty tests setOptions function will do nothing +// // when options are empty. +// func TestSetOptionsWithEmpty(t *testing.T) { +// s := NewSettings() +// err := setOptions(s, nil) +// if err != nil { +// t.Error(err) +// } +// } diff --git a/pollution.go b/pollution.go index abb5b2d..7eb7f1a 100644 --- a/pollution.go +++ b/pollution.go @@ -1,18 +1,31 @@ +// Copyright 2021 Brian J. Downs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package openweathermap import ( - "encoding/json" "fmt" "strconv" ) -// DateTimeAliases holds the alias the pollution API supports in lieu +// dateTimeAliases holds the alias the pollution API supports in lieu. // of an ISO 8601 timestamp -var DateTimeAliases = []string{"current"} +var dateTimeAliases = []string{"current"} -// ValidAlias checks to make sure the given alias is a valid one +// ValidAlias checks to make sure the given alias is a valid one. func ValidAlias(alias string) bool { - for _, i := range DateTimeAliases { + for _, i := range dateTimeAliases { if i == alias { return true } @@ -20,63 +33,37 @@ func ValidAlias(alias string) bool { return false } -// PollutionData holds the pollution specific data from the call +// PollutionData holds the pollution specific data from the call. type PollutionData struct { Precision float64 `json:"precision"` Pressure float64 `json:"pressure"` Value float64 `json:"value"` } -// PollutionParameters holds the parameters needed to make +// PollutionParameters holds the parameters needed to make. // a call to the pollution API type PollutionParameters struct { Location Coordinates Datetime string // this should be either ISO 8601 or an alias } -// Pollution holds the data returnd from the pollution API +// Pollution holds the data returnd from the pollution API. type Pollution struct { Time string `json:"time"` Location Coordinates `json:"location"` Data []PollutionData `json:"data"` - Key string - *Settings } -// NewPollution creates a new reference to Pollution -func NewPollution(key string, options ...Option) (*Pollution, error) { - k, err := setKey(key) - if err != nil { - return nil, err - } - p := &Pollution{ - Key: k, - Settings: NewSettings(), - } +// PollutionByParams gets the pollution data based on the given parameters. +func (o *OWM) PollutionByParams(params *PollutionParameters) (*Pollution, error) { + lat := strconv.FormatFloat(params.Location.Latitude, 'f', -1, 64) + lon := strconv.FormatFloat(params.Location.Longitude, 'f', -1, 64) + url := fmt.Sprintf("%s%s,%s/%s.json?appid=%s", pollutionURL, lat, lon, params.Datetime, o.apiKey) - if err := setOptions(p.Settings, options); err != nil { + var p Pollution + if err := o.call(url, &p); err != nil { return nil, err } - return p, nil -} - -// PollutionByParams gets the pollution data based on the given parameters -func (p *Pollution) PollutionByParams(params *PollutionParameters) error { - url := fmt.Sprintf("%s%s,%s/%s.json?appid=%s", - pollutionURL, - strconv.FormatFloat(params.Location.Latitude, 'f', -1, 64), - strconv.FormatFloat(params.Location.Longitude, 'f', -1, 64), - params.Datetime, - p.Key) - response, err := p.client.Get(url) - if err != nil { - return err - } - defer response.Body.Close() - - if err = json.NewDecoder(response.Body).Decode(&p); err != nil { - return err - } - return nil + return &p, nil } diff --git a/pollution_test.go b/pollution_test.go index 6a61c1a..6b9d0c0 100644 --- a/pollution_test.go +++ b/pollution_test.go @@ -1,110 +1,124 @@ +// Copyright 2021 Brian J. Downs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package openweathermap -import ( - "net/http" - "os" - "reflect" - "testing" - "time" -) - -// TestNewPollution -func TestNewPollution(t *testing.T) { - - p, err := NewPollution(os.Getenv("OWM_API_KEY")) - if err != nil { - t.Error(err) - } - - if reflect.TypeOf(p).String() != "*openweathermap.Pollution" { - t.Error("incorrect data type returned") - } -} - -// TestNewPollution with custom http client -func TestNewPollutionWithCustomHttpClient(t *testing.T) { - - hc := http.DefaultClient - hc.Timeout = time.Duration(1) * time.Second - p, err := NewPollution(os.Getenv("OWM_API_KEY"), WithHttpClient(hc)) - if err != nil { - t.Error(err) - } - - if reflect.TypeOf(p).String() != "*openweathermap.Pollution" { - t.Error("incorrect data type returned") - } - - expected := time.Duration(1) * time.Second - if p.client.Timeout != expected { - t.Errorf("Expected Duration %v, but got %v", expected, p.client.Timeout) - } -} - -// TestNewPollutionWithInvalidOptions will verify that returns an error with -// invalid option -func TestNewPollutionWithInvalidOptions(t *testing.T) { - - optionsPattern := [][]Option{ - {nil}, - {nil, nil}, - {WithHttpClient(&http.Client{}), nil}, - {nil, WithHttpClient(&http.Client{})}, - } - - for _, options := range optionsPattern { - c, err := NewPollution(os.Getenv("OWM_API_KEY"), options...) - if err == errInvalidOption { - t.Logf("Received expected invalid option error. message: %s", err.Error()) - } else if err != nil { - t.Errorf("Expected %v, but got %v", errInvalidOption, err) - } - if c != nil { - t.Errorf("Expected nil, but got %v", c) - } - } -} - -// TestNewPollutionWithInvalidHttpClient will verify that returns an error with -// invalid http client -func TestNewPollutionWithInvalidHttpClient(t *testing.T) { - - p, err := NewPollution(os.Getenv("OWM_API_KEY"), WithHttpClient(nil)) - if err == errInvalidHttpClient { - t.Logf("Received expected bad client error. message: %s", err.Error()) - } else if err != nil { - t.Errorf("Expected %v, but got %v", errInvalidHttpClient, err) - } - if p != nil { - t.Errorf("Expected nil, but got %v", p) - } -} - -func TestValidAlias(t *testing.T) { - t.Parallel() - testAliases := []string{"now", "then", "current"} - for _, i := range testAliases { - if !ValidAlias(i) { - t.Log("received expected failure") - } - } -} - -// TestPollutionByParams tests the call to the pollution API -func TestPollutionByParams(t *testing.T) { - t.Parallel() - p, err := NewPollution(os.Getenv("OWM_API_KEY")) - if err != nil { - t.Error(err) - } - params := &PollutionParameters{ - Location: Coordinates{ - Latitude: 0.0, - Longitude: 10.0, - }, - Datetime: "current", - } - if err := p.PollutionByParams(params); err != nil { - t.Error(err) - } -} +// import ( +// "net/http" +// "os" +// "reflect" +// "testing" +// "time" +// ) + +// // TestNewPollution +// func TestNewPollution(t *testing.T) { + +// p, err := NewPollution(os.Getenv("OWM_API_KEY")) +// if err != nil { +// t.Error(err) +// } + +// if reflect.TypeOf(p).String() != "*openweathermap.Pollution" { +// t.Error("incorrect data type returned") +// } +// } + +// // TestNewPollution with custom http client +// func TestNewPollutionWithCustomHttpClient(t *testing.T) { + +// hc := http.DefaultClient +// hc.Timeout = time.Duration(1) * time.Second +// p, err := NewPollution(os.Getenv("OWM_API_KEY"), WithHttpClient(hc)) +// if err != nil { +// t.Error(err) +// } + +// if reflect.TypeOf(p).String() != "*openweathermap.Pollution" { +// t.Error("incorrect data type returned") +// } + +// expected := time.Duration(1) * time.Second +// if p.client.Timeout != expected { +// t.Errorf("Expected Duration %v, but got %v", expected, p.client.Timeout) +// } +// } + +// // TestNewPollutionWithInvalidOptions will verify that returns an error with +// // invalid option +// func TestNewPollutionWithInvalidOptions(t *testing.T) { + +// optionsPattern := [][]Option{ +// {nil}, +// {nil, nil}, +// {WithHttpClient(&http.Client{}), nil}, +// {nil, WithHttpClient(&http.Client{})}, +// } + +// for _, options := range optionsPattern { +// c, err := NewPollution(os.Getenv("OWM_API_KEY"), options...) +// if err == errInvalidOption { +// t.Logf("Received expected invalid option error. message: %s", err.Error()) +// } else if err != nil { +// t.Errorf("Expected %v, but got %v", errInvalidOption, err) +// } +// if c != nil { +// t.Errorf("Expected nil, but got %v", c) +// } +// } +// } + +// // TestNewPollutionWithInvalidHttpClient will verify that returns an error with +// // invalid http client +// func TestNewPollutionWithInvalidHttpClient(t *testing.T) { + +// p, err := NewPollution(os.Getenv("OWM_API_KEY"), WithHttpClient(nil)) +// if err == errInvalidHttpClient { +// t.Logf("Received expected bad client error. message: %s", err.Error()) +// } else if err != nil { +// t.Errorf("Expected %v, but got %v", errInvalidHttpClient, err) +// } +// if p != nil { +// t.Errorf("Expected nil, but got %v", p) +// } +// } + +// func TestValidAlias(t *testing.T) { +// t.Parallel() +// testAliases := []string{"now", "then", "current"} +// for _, i := range testAliases { +// if !ValidAlias(i) { +// t.Log("received expected failure") +// } +// } +// } + +// // TestPollutionByParams tests the call to the pollution API +// func TestPollutionByParams(t *testing.T) { +// t.Parallel() +// p, err := NewPollution(os.Getenv("OWM_API_KEY")) +// if err != nil { +// t.Error(err) +// } +// params := &PollutionParameters{ +// Location: Coordinates{ +// Latitude: 0.0, +// Longitude: 10.0, +// }, +// Datetime: "current", +// } +// if err := p.PollutionByParams(params); err != nil { +// t.Error(err) +// } +// } diff --git a/road_risk.go b/road_risk.go new file mode 100644 index 0000000..e0dcb64 --- /dev/null +++ b/road_risk.go @@ -0,0 +1,56 @@ +package openweathermap + +// RoadRisk +type RoadRisk struct { + Lat float64 `json:"lat"` + Lon float64 `json:"lon"` + Dt int64 `json:"dt"` +} + +// RoadRiskRequest +type RoadRiskRequest struct { + RoadRisk []RoadRisk `json:"roadrisk"` +} + +// [ +// { +// "dt": 1602702000, +// "coord": [ +// 7.27, +// 44.04 +// ], +// "weather": { +// "temp": 278.44, +// "wind_speed": 2.27, +// "wind_deg": 7, +// "precipitation_intensity": 0.38, +// "dew_point": 276.13 +// }, +// "alerts": [ +// { +// "sender_name": "METEO-FRANCE", +// "event": "Moderate thunderstorm warning", +// "event_level": 2 +// } +// ] +// }, +// { +// "dt": 1602702400, +// "coord": [ +// 7.37, +// 45.04 +// ], +// "weather": { +// "temp": 282.44, +// "wind_speed": 1.84, +// "wind_deg": 316, +// "dew_point": 275.99 +// }, +// "alerts": [ + +// ] +// } +// ] +type RoadRiskResponse struct { + Alerts []Alert `json:"alerts"` +} diff --git a/station.go b/station.go index 731cf08..e351e36 100644 --- a/station.go +++ b/station.go @@ -1,4 +1,4 @@ -// Copyright 2015 Brian J. Downs +// Copyright 2021 Brian J. Downs // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -20,8 +20,8 @@ import ( "net/url" ) -// Slice of type string of the valid parameters to be sent from a station. -// The API refers to this data as the "Weather station data transmission protocol" +// StationDataParameters is a slice of valid parameters to be sent from a station. +// The API refers to this data as the "Weather station data transmission protocol". var StationDataParameters = []string{ "wind_dir", // Wind direction "wind_speed", // Wind speed diff --git a/station_test.go b/station_test.go index 64e5a5b..dde4e44 100644 --- a/station_test.go +++ b/station_test.go @@ -1,4 +1,4 @@ -// Copyright 2015 Brian J. Downs +// Copyright 2021 Brian J. Downs // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package openweathermap import ( "reflect" + "strconv" "testing" ) @@ -46,11 +47,11 @@ func TestValidateStationDataParameter(t *testing.T) { func TestConvertToURLValues(t *testing.T) { t.Parallel() - var count = 1 - var urlData = make(map[string]string) + count := 1 + urlData := make(map[string]string) for _, s := range StationDataParameters { - urlData[s] = string(count) + urlData[s] = strconv.Itoa(count) count++ } diff --git a/uv.go b/uv.go index 2b10b9d..f5479e9 100644 --- a/uv.go +++ b/uv.go @@ -1,79 +1,61 @@ +// Copyright 2021 Brian J. Downs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package openweathermap import ( - "encoding/json" "errors" "fmt" "time" ) -var errInvalidUVIndex = errors.New("invalid UV index value") - -// UVDataPoints holds the UV specific data +// UVDataPoints holds the UV specific data. type UVDataPoints struct { DT int64 `json:"dt"` Value float64 `json:"value"` } -// UV contains the response from the OWM UV API +// UV contains the response from the OWM UV API. type UV struct { - Coord []float64 `json:"coord"` - Data []UVDataPoints `json:"data,omitempty"` - /*Data []struct { - DT int64 `json:"dt"` - Value float64 `json:"value"` - } `json:"data,omitempty"`*/ - DT int64 `json:"dt,omitempty"` - Value float64 `json:"value,omitempty"` - Key string - *Settings + Coordinates + DateISO string `json:"date_iso"` + Date int64 `json:"date,omitempty"` + Value float64 `json:"value,omitempty"` } -// NewUV creates a new reference to UV -func NewUV(key string, options ...Option) (*UV, error) { - k, err := setKey(key) - if err != nil { - return nil, err - } - u := &UV{ - Key: k, - Settings: NewSettings(), - } +// UVCurrent gets the current UV data for the given coordinates. +func (o *OWM) UVCurrent(coord *Coordinates) (*UV, error) { + url := fmt.Sprintf("%suvi?lat=%f&lon=%f&appid=%s", uvURL, coord.Latitude, coord.Longitude, o.apiKey) - if err := setOptions(u.Settings, options); err != nil { + var uv UV + if err := o.call(url, &uv); err != nil { return nil, err } - return u, nil -} - -// Current gets the current UV data for the given coordinates -func (u *UV) Current(coord *Coordinates) error { - response, err := u.client.Get(fmt.Sprintf("%suvi?lat=%f&lon=%f&appid=%s", uvURL, coord.Latitude, coord.Longitude, u.Key)) - if err != nil { - return err - } - defer response.Body.Close() - - if err = json.NewDecoder(response.Body).Decode(&u); err != nil { - return err - } - return nil + return &uv, nil } -// Historical gets the historical UV data for the coordinates and times -func (u *UV) Historical(coord *Coordinates, start, end time.Time) error { - response, err := u.client.Get(fmt.Sprintf("%shistory?lat=%f&lon=%f&start=%d&end=%d&appid=%s", uvURL, coord.Latitude, coord.Longitude, start.Unix(), end.Unix(), u.Key)) - if err != nil { - return err - } - defer response.Body.Close() +// UVHistorical gets the historical UV data for the coordinates and times. +func (o *OWM) UVHistorical(coord *Coordinates, start, end time.Time) (*UV, error) { + url := fmt.Sprintf("%shistory?lat=%f&lon=%f&start=%d&end=%d&appid=%s", uvURL, coord.Latitude, coord.Longitude, start.Unix(), end.Unix(), o.apiKey) - if err = json.NewDecoder(response.Body).Decode(&u); err != nil { - return err + var uv UV + if err := o.call(url, &uv); err != nil { + return nil, err } - return nil + return &uv, nil } // UVIndexInfo @@ -92,36 +74,36 @@ type UVIndexInfo struct { RecommendedProtection string } -// UVData contains data in regards to UV index ranges, rankings, and steps for protection +// UVData contains data in regards to UV index ranges, rankings, and steps for protection. var UVData = []UVIndexInfo{ { - UVIndex: []float64{0, 2.9}, - MGC: "Green", - Risk: "Low", + UVIndex: []float64{0, 2.9}, + MGC: "Green", + Risk: "Low", RecommendedProtection: "Wear sunglasses on bright days; use sunscreen if there is snow on the ground, which reflects UV radiation, or if you have particularly fair skin.", }, { - UVIndex: []float64{3, 5.9}, - MGC: "Yellow", - Risk: "Moderate", + UVIndex: []float64{3, 5.9}, + MGC: "Yellow", + Risk: "Moderate", RecommendedProtection: "Take precautions, such as covering up, if you will be outside. Stay in shade near midday when the sun is strongest.", }, { - UVIndex: []float64{6, 7.9}, - MGC: "Orange", - Risk: "High", + UVIndex: []float64{6, 7.9}, + MGC: "Orange", + Risk: "High", RecommendedProtection: "Cover the body with sun protective clothing, use SPF 30+ sunscreen, wear a hat, reduce time in the sun within three hours of solar noon, and wear sunglasses.", }, { - UVIndex: []float64{8, 10.9}, - MGC: "Red", - Risk: "Very high", + UVIndex: []float64{8, 10.9}, + MGC: "Red", + Risk: "Very high", RecommendedProtection: "Wear SPF 30+ sunscreen, a shirt, sunglasses, and a wide-brimmed hat. Do not stay in the sun for too long.", }, { - UVIndex: []float64{11}, - MGC: "Violet", - Risk: "Extreme", + UVIndex: []float64{11}, + MGC: "Violet", + Risk: "Extreme", RecommendedProtection: "Take all precautions: Wear SPF 30+ sunscreen, a long-sleeved shirt and trousers, sunglasses, and a very broad hat. Avoid the sun within three hours of solar noon.", }, } @@ -143,26 +125,7 @@ func (u *UV) UVInformation() ([]UVIndexInfo, error) { case u.Value >= 11: return []UVIndexInfo{UVData[4]}, nil default: - return nil, errInvalidUVIndex - } - - case len(u.Data) > 0: - var uvi []UVIndexInfo - for _, i := range u.Data { - switch { - case i.Value < 2.9: - uvi = append(uvi, UVData[0]) - case i.Value > 3 && u.Value < 5.9: - uvi = append(uvi, UVData[1]) - case i.Value > 6 && u.Value < 7.9: - uvi = append(uvi, UVData[2]) - case i.Value > 8 && u.Value < 10.9: - uvi = append(uvi, UVData[3]) - case i.Value >= 11: - uvi = append(uvi, UVData[4]) - default: - return nil, errInvalidUVIndex - } + return nil, errors.New("invalid UV index value") } } diff --git a/uv_test.go b/uv_test.go index 75ef0ff..3ed60c6 100644 --- a/uv_test.go +++ b/uv_test.go @@ -1,140 +1,154 @@ +// Copyright 2021 Brian J. Downs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package openweathermap -import ( - "net/http" - "os" - "reflect" - "testing" - "time" -) - -var coords = &Coordinates{ - Longitude: 53.343497, - Latitude: -6.288379, -} - -// TestNewUV -func TestNewUV(t *testing.T) { - - uv, err := NewUV(os.Getenv("OWM_API_KEY")) - if err != nil { - t.Error(err) - } - - if reflect.TypeOf(uv).String() != "*openweathermap.UV" { - t.Error("incorrect data type returned") - } -} - -// TestNewUV with custom http client -func TestNewUVWithCustomHttpClient(t *testing.T) { - - hc := http.DefaultClient - hc.Timeout = time.Duration(1) * time.Second - uv, err := NewUV(os.Getenv("OWM_API_KEY"), WithHttpClient(hc)) - if err != nil { - t.Error(err) - } - - if reflect.TypeOf(uv).String() != "*openweathermap.UV" { - t.Error("incorrect data type returned") - } - - expected := time.Duration(1) * time.Second - if uv.client.Timeout != expected { - t.Errorf("Expected Duration %v, but got %v", expected, uv.client.Timeout) - } -} - -// TestNewUVWithInvalidOptions will verify that returns an error with -// invalid option -func TestNewUVWithInvalidOptions(t *testing.T) { - - optionsPattern := [][]Option{ - {nil}, - {nil, nil}, - {WithHttpClient(&http.Client{}), nil}, - {nil, WithHttpClient(&http.Client{})}, - } - - for _, options := range optionsPattern { - c, err := NewUV(os.Getenv("OWM_API_KEY"), options...) - if err == errInvalidOption { - t.Logf("Received expected invalid option error. message: %s", err.Error()) - } else if err != nil { - t.Errorf("Expected %v, but got %v", errInvalidOption, err) - } - if c != nil { - t.Errorf("Expected nil, but got %v", c) - } - } -} - -// TestNewUVWithInvalidHttpClient will verify that returns an error with -// invalid http client -func TestNewUVWithInvalidHttpClient(t *testing.T) { - - uv, err := NewUV(os.Getenv("OWM_API_KEY"), WithHttpClient(nil)) - if err == errInvalidHttpClient { - t.Logf("Received expected bad client error. message: %s", err.Error()) - } else if err != nil { - t.Errorf("Expected %v, but got %v", errInvalidHttpClient, err) - } - if uv != nil { - t.Errorf("Expected nil, but got %v", uv) - } -} - -// TestCurrentUV -func TestCurrentUV(t *testing.T) { - t.Parallel() - - uv, err := NewUV(os.Getenv("OWM_API_KEY")) - if err != nil { - t.Error(err) - } - - if err := uv.Current(coords); err != nil { - t.Error(err) - } - - if reflect.TypeOf(uv).String() != "*openweathermap.UV" { - t.Error("incorrect data type returned") - } -} - -// TestHistoricalUV -func TestHistoricalUV(t *testing.T) { - t.Parallel() - - /* uv := NewUV(os.Getenv("OWM_API_KEY")) - - end := time.Now().UTC() - start := time.Now().UTC().Add(-time.Hour * time.Duration(24)) - - if err := uv.Historical(coords, start, end); err != nil { - t.Error(err) - } - - if reflect.TypeOf(uv).String() != "*openweathermap.UV" { - t.Error("incorrect data type returned") - }*/ -} - -func TestUVInformation(t *testing.T) { - t.Parallel() - - uv, err := NewUV(os.Getenv("OWM_API_KEY")) - if err != nil { - t.Error(err) - } - - if err := uv.Current(coords); err != nil { - t.Error(err) - } - - _, err = uv.UVInformation() - if err != nil { - t.Error(err) - } -} +// import ( +// "net/http" +// "os" +// "reflect" +// "testing" +// "time" +// ) + +// var coords = &Coordinates{ +// Longitude: 53.343497, +// Latitude: -6.288379, +// } + +// // TestNewUV +// func TestNewUV(t *testing.T) { + +// uv, err := NewUV(os.Getenv("OWM_API_KEY")) +// if err != nil { +// t.Error(err) +// } + +// if reflect.TypeOf(uv).String() != "*openweathermap.UV" { +// t.Error("incorrect data type returned") +// } +// } + +// // TestNewUV with custom http client +// func TestNewUVWithCustomHttpClient(t *testing.T) { + +// hc := http.DefaultClient +// hc.Timeout = time.Duration(1) * time.Second +// uv, err := NewUV(os.Getenv("OWM_API_KEY"), WithHttpClient(hc)) +// if err != nil { +// t.Error(err) +// } + +// if reflect.TypeOf(uv).String() != "*openweathermap.UV" { +// t.Error("incorrect data type returned") +// } + +// expected := time.Duration(1) * time.Second +// if uv.client.Timeout != expected { +// t.Errorf("Expected Duration %v, but got %v", expected, uv.client.Timeout) +// } +// } + +// // TestNewUVWithInvalidOptions will verify that returns an error with +// // invalid option +// func TestNewUVWithInvalidOptions(t *testing.T) { + +// optionsPattern := [][]Option{ +// {nil}, +// {nil, nil}, +// {WithHttpClient(&http.Client{}), nil}, +// {nil, WithHttpClient(&http.Client{})}, +// } + +// for _, options := range optionsPattern { +// c, err := NewUV(os.Getenv("OWM_API_KEY"), options...) +// if err == errInvalidOption { +// t.Logf("Received expected invalid option error. message: %s", err.Error()) +// } else if err != nil { +// t.Errorf("Expected %v, but got %v", errInvalidOption, err) +// } +// if c != nil { +// t.Errorf("Expected nil, but got %v", c) +// } +// } +// } + +// // TestNewUVWithInvalidHttpClient will verify that returns an error with +// // invalid http client +// func TestNewUVWithInvalidHttpClient(t *testing.T) { + +// uv, err := NewUV(os.Getenv("OWM_API_KEY"), WithHttpClient(nil)) +// if err == errInvalidHttpClient { +// t.Logf("Received expected bad client error. message: %s", err.Error()) +// } else if err != nil { +// t.Errorf("Expected %v, but got %v", errInvalidHttpClient, err) +// } +// if uv != nil { +// t.Errorf("Expected nil, but got %v", uv) +// } +// } + +// // TestCurrentUV +// func TestCurrentUV(t *testing.T) { +// t.Parallel() + +// uv, err := NewUV(os.Getenv("OWM_API_KEY")) +// if err != nil { +// t.Error(err) +// } + +// if err := uv.Current(coords); err != nil { +// t.Error(err) +// } + +// if reflect.TypeOf(uv).String() != "*openweathermap.UV" { +// t.Error("incorrect data type returned") +// } +// } + +// // TestHistoricalUV +// func TestHistoricalUV(t *testing.T) { +// t.Parallel() + +// /* uv := NewUV(os.Getenv("OWM_API_KEY")) + +// end := time.Now().UTC() +// start := time.Now().UTC().Add(-time.Hour * time.Duration(24)) + +// if err := uv.Historical(coords, start, end); err != nil { +// t.Error(err) +// } + +// if reflect.TypeOf(uv).String() != "*openweathermap.UV" { +// t.Error("incorrect data type returned") +// }*/ +// } + +// func TestUVInformation(t *testing.T) { +// t.Parallel() + +// uv, err := NewUV(os.Getenv("OWM_API_KEY")) +// if err != nil { +// t.Error(err) +// } + +// if err := uv.Current(coords); err != nil { +// t.Error(err) +// } + +// _, err = uv.UVInformation() +// if err != nil { +// t.Error(err) +// } +// }