113 lines
3.1 KiB
Go
113 lines
3.1 KiB
Go
// Package feriados consulta a API feriadosapi.com, endpoint por cidade
|
|
// (GET /feriados/cidade/{ibge}?ano=AAAA), que devolve os feriados nacionais e
|
|
// os municipais do ano.
|
|
package feriados
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"botpontos/internal/domain"
|
|
)
|
|
|
|
// apiDateLayout é o formato das datas devolvidas pela API (dd/mm/aaaa).
|
|
const apiDateLayout = "02/01/2006"
|
|
|
|
type Client struct {
|
|
http *http.Client
|
|
baseURL string
|
|
apiKey string
|
|
location string // código IBGE da cidade
|
|
}
|
|
|
|
func NewClient(baseURL, apiKey, location string) *Client {
|
|
return &Client{
|
|
http: &http.Client{Timeout: 10 * time.Second},
|
|
baseURL: strings.TrimRight(strings.TrimSpace(baseURL), "/"),
|
|
apiKey: strings.TrimSpace(apiKey),
|
|
location: strings.TrimSpace(location),
|
|
}
|
|
}
|
|
|
|
func (c *Client) Configured() bool {
|
|
return c.baseURL != "" && c.apiKey != "" && c.location != ""
|
|
}
|
|
|
|
type response struct {
|
|
Feriados []struct {
|
|
Data string `json:"data"`
|
|
Nome string `json:"nome"`
|
|
} `json:"feriados"`
|
|
}
|
|
|
|
// FetchYear devolve os feriados do ano para a cidade configurada.
|
|
func (c *Client) FetchYear(ctx context.Context, year int) ([]domain.Holiday, error) {
|
|
if !c.Configured() {
|
|
return nil, domain.ErrNotConfigured
|
|
}
|
|
if _, err := strconv.Atoi(c.location); err != nil {
|
|
return nil, fmt.Errorf("BP_HOLIDAY_LOCATION deve ser o código IBGE da cidade (ex.: 3106200), não %q", c.location)
|
|
}
|
|
endpoint, err := url.Parse(c.baseURL + "/feriados/cidade/" + url.PathEscape(c.location))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("URL da API de feriados: %w", err)
|
|
}
|
|
query := endpoint.Query()
|
|
query.Set("ano", strconv.Itoa(year))
|
|
endpoint.RawQuery = query.Encode()
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req.Header.Set("Accept", "application/json")
|
|
req.Header.Set("Authorization", "Bearer "+c.apiKey)
|
|
|
|
resp, err := c.http.Do(req)
|
|
if err != nil {
|
|
var urlErr *url.Error
|
|
if errors.As(err, &urlErr) {
|
|
err = urlErr.Err
|
|
}
|
|
return nil, fmt.Errorf("consultar API de feriados: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
|
return nil, fmt.Errorf("API de feriados retornou HTTP %d", resp.StatusCode)
|
|
}
|
|
|
|
var payload response
|
|
if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
|
|
return nil, fmt.Errorf("resposta da API de feriados ilegível: %w", err)
|
|
}
|
|
|
|
found := map[string]string{}
|
|
for _, item := range payload.Feriados {
|
|
parsed, err := time.Parse(apiDateLayout, strings.TrimSpace(item.Data))
|
|
if err != nil || parsed.Year() != year {
|
|
continue
|
|
}
|
|
name := strings.TrimSpace(item.Nome)
|
|
if name == "" {
|
|
name = "Feriado"
|
|
}
|
|
found[parsed.Format(domain.DateLayout)] = name
|
|
}
|
|
if len(found) == 0 {
|
|
return nil, fmt.Errorf("a API de feriados não retornou datas para %d", year)
|
|
}
|
|
|
|
holidays := make([]domain.Holiday, 0, len(found))
|
|
for day, name := range found {
|
|
holidays = append(holidays, domain.Holiday{Day: day, Name: name, Source: domain.HolidaySourceAPI})
|
|
}
|
|
return holidays, nil
|
|
}
|