Files
s01e03/internal/infrastructure/weather/api_client.go
2026-03-12 19:17:00 +01:00

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
}