From cf21a73f80ba1961b19a018bcb3379ddea039c9c Mon Sep 17 00:00:00 2001 From: kashif khan Date: Mon, 23 Sep 2024 16:32:33 +0500 Subject: [PATCH] Added weather history api --- README.md | 5 +++-- config/configuration.go | 1 + helper.go | 23 +++++++++++++++++++++ options.go | 4 ++++ weather_api.go | 46 +++++++++++++++++++++++++++++------------ weather_api_config.go | 23 ++++++++++++++++++++- 6 files changed, 86 insertions(+), 16 deletions(-) create mode 100644 helper.go diff --git a/README.md b/README.md index 46464ca..92f57de 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,9 @@ go get "github.com/kashifkhan0771/go-weather" ) func main() { - config := weatherClient.WeatherAPIConfig{ - XApiKey: "", + config, err := weatherClient.NewWeatherAPIConfig() + if err != nil { + panic(err) } weather, err := config.GetCurrentWeather(weatherClient.Options{Query: "Paris"}) diff --git a/config/configuration.go b/config/configuration.go index a74ebe3..6bb8114 100644 --- a/config/configuration.go +++ b/config/configuration.go @@ -6,6 +6,7 @@ const ( // API EndPoints CurrentWeatherJSON = "/current.json" + HistoryWeatherJSON = "/history.json" // APIsTimeout is a timeout for all the API request to api.weatherapi.com APIsTimeout = 30 diff --git a/helper.go b/helper.go new file mode 100644 index 0000000..8512f9a --- /dev/null +++ b/helper.go @@ -0,0 +1,23 @@ +package go_weather + +import ( + "time" +) + +// Define the minimum allowed date (2010-01-01) +var minAllowedDate = time.Date(2010, 1, 1, 0, 0, 0, 0, time.UTC) + +// validateQuery ensure the required query in the option is not empty +func validateQuery(q string) bool { + return q != "" +} + +// Ensure the date is on or after January 1st, 2010 +func validateDate(date time.Time) bool { + if date.IsZero() { + return false + } + + // Check if the input date is on or after 2010-01-01 + return !date.Before(minAllowedDate) +} diff --git a/options.go b/options.go index c292c95..e3989b8 100644 --- a/options.go +++ b/options.go @@ -1,5 +1,7 @@ package go_weather +import "time" + /* Options represent the query parameters of the APIs. @@ -8,4 +10,6 @@ For more details about each query parameters, please visit: https://www.weathera type Options struct { // Query parameter based on which data is sent back (Required) Query string `json:"q"` + // Date is required for history and future API, it restricts date output for Forecast and History API + Date time.Time `json:"dt"` } diff --git a/weather_api.go b/weather_api.go index 3afc1d9..acedcd2 100644 --- a/weather_api.go +++ b/weather_api.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "net/http" - "time" "github.com/kashifkhan0771/go-weather/config" "github.com/kashifkhan0771/go-weather/models" @@ -12,8 +11,8 @@ import ( // GetCurrentWeather return current weather response based on the option query func (c WeatherAPIConfig) GetCurrentWeather(options Options) (*models.WeatherResponse, error) { - if options.Query == "" { - return nil, fmt.Errorf("query is empty") + if !validateQuery(options.Query) { + return nil, fmt.Errorf("invalid query parameter") } url := fmt.Sprintf("%s%s?q=%s", config.BaseURL, config.CurrentWeatherJSON, options.Query) @@ -25,27 +24,44 @@ func (c WeatherAPIConfig) GetCurrentWeather(options Options) (*models.WeatherRes defer resp.Body.Close() - // Check response status code - if resp.StatusCode != http.StatusOK { + // Decode JSON response body + var weatherResp models.WeatherResponse + err = json.NewDecoder(resp.Body).Decode(&weatherResp) + if err != nil { + return nil, err + } + + return &weatherResp, nil +} + +func (c WeatherAPIConfig) WeatherHistory(options Options) (*models.WeatherResponse, error) { + if !validateQuery(options.Query) { + return nil, fmt.Errorf("invalid query parameter") + } else if !validateDate(options.Date) { + return nil, fmt.Errorf("invalid date parameter") + } + + url := fmt.Sprintf("%s%s?q=%s&dt=%s", config.BaseURL, config.HistoryWeatherJSON, options.Query, options.Date.Format("2006-01-02")) + + resp, err := c.makeRequest(http.MethodGet, url) + if err != nil { return nil, err } + defer resp.Body.Close() + // Decode JSON response body - var weatherResp *models.WeatherResponse + var weatherResp models.WeatherResponse err = json.NewDecoder(resp.Body).Decode(&weatherResp) if err != nil { return nil, err } - return weatherResp, nil + return &weatherResp, nil } // makeRequest is a helper function for making API requests. func (c WeatherAPIConfig) makeRequest(method, url string) (*http.Response, error) { - client := &http.Client{ - Timeout: time.Second * config.APIsTimeout, - } - req, err := http.NewRequest(method, url, http.NoBody) if err != nil { return nil, err @@ -54,9 +70,13 @@ func (c WeatherAPIConfig) makeRequest(method, url string) (*http.Response, error req.Header.Set("key", c.XApiKey) // Send request - resp, err := client.Do(req) + resp, err := c.HttpClient.Do(req) if err != nil { - return nil, err + return nil, fmt.Errorf("HTTP request failed: %w", err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) } return resp, nil diff --git a/weather_api_config.go b/weather_api_config.go index 8796610..60139c2 100644 --- a/weather_api_config.go +++ b/weather_api_config.go @@ -1,7 +1,28 @@ package go_weather +import ( + "errors" + "net/http" + "time" + + "github.com/kashifkhan0771/go-weather/config" +) + // WeatherAPIConfig has the configurations required to call the APIs type WeatherAPIConfig struct { // XApiKey can be obtained from: https://www.weatherapi.com/ after you log in - XApiKey string `json:"key"` + XApiKey string `json:"key"` + HttpClient *http.Client `json:"-"` +} + +// NewWeatherAPIConfig creates a WeatherAPIConfig with apiKey passed. +func NewWeatherAPIConfig(apiKey string) (*WeatherAPIConfig, error) { + if apiKey == "" { + return nil, errors.New("API key cannot be empty") + } + + return &WeatherAPIConfig{ + XApiKey: apiKey, + HttpClient: &http.Client{Timeout: time.Second * config.APIsTimeout}, + }, nil }