126 lines
3.0 KiB
Go
126 lines
3.0 KiB
Go
package weather
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
|
|
"github.com/paramah/ai_devs4/s01e03/internal/domain"
|
|
)
|
|
|
|
// APIClient implements domain.WeatherClient using wttr.in
|
|
type APIClient struct {
|
|
baseURL string
|
|
client *http.Client
|
|
}
|
|
|
|
// NewAPIClient creates a new weather API client
|
|
// apiKey parameter is ignored as wttr.in doesn't require authentication
|
|
func NewAPIClient(baseURL, apiKey string) *APIClient {
|
|
if baseURL == "" {
|
|
baseURL = "https://wttr.in"
|
|
}
|
|
return &APIClient{
|
|
baseURL: baseURL,
|
|
client: &http.Client{},
|
|
}
|
|
}
|
|
|
|
type wttrResponse struct {
|
|
CurrentCondition []struct {
|
|
TempC string `json:"temp_C"`
|
|
Humidity string `json:"humidity"`
|
|
WeatherDesc []struct {
|
|
Value string `json:"value"`
|
|
} `json:"weatherDesc"`
|
|
} `json:"current_condition"`
|
|
NearestArea []struct {
|
|
AreaName []struct {
|
|
Value string `json:"value"`
|
|
} `json:"areaName"`
|
|
} `json:"nearest_area"`
|
|
}
|
|
|
|
// GetWeather gets weather information for a location using wttr.in
|
|
func (c *APIClient) GetWeather(ctx context.Context, location string) (*domain.WeatherInfo, error) {
|
|
// Build URL - wttr.in format: https://wttr.in/Location?format=j1
|
|
u, err := url.Parse(c.baseURL + "/" + url.PathEscape(location))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing URL: %w", err)
|
|
}
|
|
|
|
q := u.Query()
|
|
q.Set("format", "j1")
|
|
u.RawQuery = q.Encode()
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("creating request: %w", err)
|
|
}
|
|
|
|
// wttr.in prefers this header to return JSON
|
|
req.Header.Set("User-Agent", "curl/7.0")
|
|
|
|
resp, err := c.client.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("sending request: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
body, _ := io.ReadAll(resp.Body)
|
|
return nil, fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("reading response: %w", err)
|
|
}
|
|
|
|
var apiResp wttrResponse
|
|
if err := json.Unmarshal(body, &apiResp); err != nil {
|
|
return nil, fmt.Errorf("unmarshaling response: %w", err)
|
|
}
|
|
|
|
if len(apiResp.CurrentCondition) == 0 {
|
|
return nil, fmt.Errorf("no weather data available for location: %s", location)
|
|
}
|
|
|
|
current := apiResp.CurrentCondition[0]
|
|
|
|
// Parse temperature
|
|
temp, err := strconv.ParseFloat(current.TempC, 64)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing temperature: %w", err)
|
|
}
|
|
|
|
// Parse humidity
|
|
humidity, err := strconv.Atoi(current.Humidity)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing humidity: %w", err)
|
|
}
|
|
|
|
// Get description
|
|
description := "unknown"
|
|
if len(current.WeatherDesc) > 0 {
|
|
description = current.WeatherDesc[0].Value
|
|
}
|
|
|
|
// Get location name
|
|
locationName := location
|
|
if len(apiResp.NearestArea) > 0 && len(apiResp.NearestArea[0].AreaName) > 0 {
|
|
locationName = apiResp.NearestArea[0].AreaName[0].Value
|
|
}
|
|
|
|
return &domain.WeatherInfo{
|
|
Location: locationName,
|
|
Temperature: temp,
|
|
Description: description,
|
|
Humidity: humidity,
|
|
}, nil
|
|
}
|