package client import ( "bytes" "encoding/json" "fmt" "io" "net/http" "net/url" "time" ) type Client struct { baseURL string http *http.Client } func New(baseURL string) *Client { return &Client{ baseURL: baseURL, http: &http.Client{Timeout: 10 * time.Second}, } } type listResponse struct { Servers []string `json:"servers"` } type addRequest struct { Address string `json:"address"` } type errorResponse struct { Error string `json:"error"` } func (c *Client) List() ([]string, error) { req, err := http.NewRequest(http.MethodGet, c.baseURL+"/dns", nil) if err != nil { return nil, err } resp, err := c.http.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, decodeError(resp) } var lr listResponse if err := json.NewDecoder(resp.Body).Decode(&lr); err != nil { return nil, fmt.Errorf("decode response: %w", err) } return lr.Servers, nil } func (c *Client) Add(addr string) error { body, err := json.Marshal(addRequest{Address: addr}) if err != nil { return err } req, err := http.NewRequest(http.MethodPost, c.baseURL+"/dns", bytes.NewReader(body)) if err != nil { return err } req.Header.Set("Content-Type", "application/json") resp, err := c.http.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusCreated { return decodeError(resp) } return nil } func (c *Client) Remove(addr string) error { u := c.baseURL + "/dns/" + url.PathEscape(addr) req, err := http.NewRequest(http.MethodDelete, u, nil) if err != nil { return err } resp, err := c.http.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusNoContent { return decodeError(resp) } return nil } func decodeError(resp *http.Response) error { body, _ := io.ReadAll(resp.Body) var er errorResponse if json.Unmarshal(body, &er) == nil && er.Error != "" { return fmt.Errorf("server returned %s: %s", resp.Status, er.Error) } return fmt.Errorf("server returned %s: %s", resp.Status, string(body)) }