// Access [Meteorologisk institutt's API](https://api.met.no) package met import ( "encoding/json" "fmt" "io" "net/http" "time" ) type Met struct { siteName string } // Returns valid Met client or error if empty siteName is provided func New(siteName string) (*Met, error) { if siteName == "" { return nil, fmt.Errorf("`siteName` must be defined.") } 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, fmt.Errorf("Unable to get 'Expires' header") } expires, err := time.Parse(time.RFC1123, expiresRaw[0]) if err != nil { return nil, fmt.Errorf("Unable to parse `expiresRaw`: %w", err) } lastModifiedRaw, ok := resp.Header["Last-Modified"] if !ok { return nil, fmt.Errorf("Unable to get 'Last-Modified' header") } lastModified, err := time.Parse(time.RFC1123, lastModifiedRaw[0]) if err != nil { return nil, fmt.Errorf("Unable to parse `Last-Modified`: %w", err) } forecast := LocationForecast{} err = json.Unmarshal(body, &forecast) if err != nil { return nil, err } return &LocationForecastResult{ Expires: expires, LastModified: lastModified, LocationForecast: forecast, }, nil }