// Access [Meteorologisk institutt's API](https://api.met.no) package met import ( "encoding/json" "errors" "fmt" "io" "net/http" "time" ) type Met struct { siteName string } var ( ErrorMissingSiteName = errors.New("siteName must be defined") ErrorMissingHeaderExpires = errors.New("missing header 'Expires'") ErrorMissingHeaderLastModified = errors.New("missing header 'Last-Modified'") ErrorParseExpires = errors.New("unable to parse 'Expires'") ErrorParseLastModified = errors.New("unable to parse 'Last-Modified'") ) // Returns valid Met client or error if empty siteName is provided func New(siteName string) (*Met, error) { if siteName == "" { return nil, ErrorMissingSiteName } return &Met{ siteName: siteName, }, nil } // Get LocationForecast from Met, returns error if Met doesn't answer or response is missing required headers // API endpoint: https://api.met.no/weatherapi/locationforecast/2.0/documentation func (m *Met) Forecast(lat, lon float64, alt *int) (*LocationForecastResult, error) { url := "https://api.met.no/weatherapi/locationforecast/2.0/complete" req, err := http.NewRequest("GET", url, nil) if err != nil { return nil, err } q := req.URL.Query() q.Add("lat", fmt.Sprintf("%.4f", lat)) q.Add("lon", fmt.Sprintf("%.4f", lon)) if alt != nil { q.Add("altitude", fmt.Sprintf("%d", alt)) } req.URL.RawQuery = q.Encode() req.Header.Set("User-Agent", m.siteName) resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("%s", resp.Status) } body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } expiresRaw, ok := resp.Header["Expires"] if !ok { return nil, ErrorMissingHeaderExpires } expires, err := time.Parse(time.RFC1123, expiresRaw[0]) if err != nil { return nil, errors.Join(ErrorParseExpires, err) } lastModifiedRaw, ok := resp.Header["Last-Modified"] if !ok { return nil, ErrorMissingHeaderLastModified } lastModified, err := time.Parse(time.RFC1123, lastModifiedRaw[0]) if err != nil { return nil, errors.Join(ErrorParseLastModified, err) } forecast := LocationForecast{} err = json.Unmarshal(body, &forecast) if err != nil { return nil, err } return &LocationForecastResult{ Expires: expires, LastModified: lastModified, LocationForecast: forecast, }, nil }