package mailinabox import ( "encoding/json" "io" "net/http" "net/url" "time" "github.com/nrdcg/mailinabox/errutils" ) type service struct { client *Client } // Client the Mail-in-a-Box client. type Client struct { httpClient *http.Client baseURL *url.URL email string password string common service // Reuse a single struct instead of allocating one for each service on the heap. DNS *DNSService User *UserService Mail *MailService System *SystemService } // New creates a new Client. func New(apiURL, email, password string, opts ...Option) (*Client, error) { baseURL, err := url.Parse(apiURL) if err != nil { return nil, err } client := &Client{ httpClient: &http.Client{Timeout: 10 * time.Second}, baseURL: baseURL, email: email, password: password, } for _, opt := range opts { err := opt(client) if err != nil { return nil, err } } client.common.client = client client.DNS = (*DNSService)(&client.common) client.User = (*UserService)(&client.common) client.Mail = (*MailService)(&client.common) client.System = (*SystemService)(&client.common) return client, nil } func (c *Client) doJSON(req *http.Request, result any) error { req.SetBasicAuth(c.email, c.password) req.Header.Set("Accept", "application/json") resp, err := c.httpClient.Do(req) if err != nil { return errutils.NewHTTPDoError(req, err) } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return errutils.NewUnexpectedResponseStatusCodeError(req, resp) } if result == nil { return nil } raw, err := io.ReadAll(resp.Body) if err != nil { return errutils.NewReadResponseError(req, resp.StatusCode, err) } err = json.Unmarshal(raw, result) if err != nil { return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err) } return nil } func (c *Client) doPlain(req *http.Request) ([]byte, error) { req.SetBasicAuth(c.email, c.password) resp, err := c.httpClient.Do(req) if err != nil { return nil, errutils.NewHTTPDoError(req, err) } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return nil, errutils.NewUnexpectedResponseStatusCodeError(req, resp) } raw, err := io.ReadAll(resp.Body) if err != nil { return nil, errutils.NewReadResponseError(req, resp.StatusCode, err) } return raw, nil } func boolToIntStr(v bool) string { if v { return "1" } return "0" } type Option func(*Client) error func WithHTTPClient(client *http.Client) Option { return func(c *Client) error { if client != nil { c.httpClient = client } return nil } }