package api import ( "context" "encoding/json" "fmt" "io" "net/http" "net/url" "time" ) type TrainResponse struct { Rank string `json:"rank"` Number string `json:"number"` Date string `json:"date"` Operator string `json:"operator"` Groups []TrainGroup `json:"groups"` } type TrainGroup struct { Route struct { From string `json:"from"` To string `json:"to"` } `json:"route"` Status *struct { Delay int `json:"delay"` Station string `json:"station"` State string `json:"state"` } `json:"status"` Stations []TrainStation `json:"stations"` } type TrainStation struct { Name string `json:"name"` LinkName string `json:"linkName"` Km int `json:"km"` StoppingTime *int `json:"stoppingTime"` Platform *string `json:"platform"` Arrival *TrainArrDep `json:"arrival"` Departure *TrainArrDep `json:"departure"` Notes []any `json:"notes"` } type TrainArrDep struct { ScheduleTime time.Time `json:"scheduleTime"` Status *struct { Delay int `json:"delay"` Real bool `json:"real"` Cancelled bool `json:"cancelled"` } `json:"status"` } const ( trainApiEndpoint = "https://scraper.infotren.dcdev.ro/v3" ) var ( TrainNotFound = fmt.Errorf("train not found") ServerError = fmt.Errorf("server error") ) func GetTrain(ctx context.Context, trainNumber string, date time.Time) (*TrainResponse, error) { u, _ := url.Parse(trainApiEndpoint) u.Path, _ = url.JoinPath(u.Path, "trains", trainNumber) query := u.Query() query.Add("date", date.Format(time.RFC3339)) u.RawQuery = query.Encode() req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) if err != nil { return nil, fmt.Errorf("error getting train %s: %w", trainNumber, err) } res, err := http.DefaultClient.Do(req) if err != nil { return nil, fmt.Errorf("error getting train %s: %w", trainNumber, err) } defer func() { _ = res.Body.Close() }() switch { case res.StatusCode == http.StatusNotFound: return nil, fmt.Errorf("error getting train %s: %w", trainNumber, TrainNotFound) case res.StatusCode/100 != 2: return nil, fmt.Errorf("error getting train %s: status code %d: %w", trainNumber, res.StatusCode, ServerError) } var body []byte if res.ContentLength > 0 { body = make([]byte, res.ContentLength) n, err := io.ReadFull(res.Body, body) if err != nil && err != io.EOF { return nil, fmt.Errorf("error getting train %s: %w", trainNumber, err) } else if n != int(res.ContentLength) { body = body[0:n] } } else { body, err = io.ReadAll(res.Body) if err != nil { return nil, fmt.Errorf("error getting train %s: %w", trainNumber, err) } } var trainData TrainResponse if err := json.Unmarshal(body, &trainData); err != nil { return nil, fmt.Errorf("error getting train %s: %w", trainNumber, err) } return &trainData, nil }