namesilo.go raw

   1  // Package namesilo A Go client library for accessing the Namesilo API.
   2  package namesilo
   3  
   4  import (
   5  	"context"
   6  	"encoding/xml"
   7  	"errors"
   8  	"fmt"
   9  	"io"
  10  	"net/http"
  11  	"net/url"
  12  	"time"
  13  
  14  	querystring "github.com/google/go-querystring/query"
  15  )
  16  
  17  const (
  18  	// DefaultAPIEndpoint The default API endpoint.
  19  	DefaultAPIEndpoint = "https://www.namesilo.com/api"
  20  
  21  	// SandboxAPIEndpoint The sandbox API endpoint.
  22  	SandboxAPIEndpoint = "https://sandbox.namesilo.com/api"
  23  
  24  	// OTEAPIEndpoint The OTE sandbox API endpoint.
  25  	OTEAPIEndpoint = "https://ote.namesilo.com/api"
  26  )
  27  
  28  // Response Codes.
  29  const (
  30  	SuccessfulAPIOperation = "300"
  31  	SuccessfulRegistration = "301"
  32  	SuccessfulOrder        = "302"
  33  )
  34  
  35  // Client the Namesilo client.
  36  type Client struct {
  37  	apiKey string
  38  
  39  	Endpoint   *url.URL
  40  	HTTPClient *http.Client
  41  }
  42  
  43  // NewClient Creates a Namesilo client.
  44  func NewClient(apiKey string) *Client {
  45  	endpoint, _ := url.Parse(DefaultAPIEndpoint)
  46  
  47  	return &Client{
  48  		apiKey:     apiKey,
  49  		Endpoint:   endpoint,
  50  		HTTPClient: &http.Client{Timeout: 10 * time.Second},
  51  	}
  52  }
  53  
  54  func (c *Client) do(ctx context.Context, name string, params, op any) error {
  55  	endpoint := c.Endpoint.JoinPath(name)
  56  
  57  	query, err := querystring.Values(params)
  58  	if err != nil {
  59  		return err
  60  	}
  61  
  62  	query.Set("version", "1")
  63  	query.Set("type", "xml")
  64  	query.Set("key", c.apiKey)
  65  
  66  	endpoint.RawQuery = query.Encode()
  67  
  68  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), http.NoBody)
  69  	if err != nil {
  70  		return err
  71  	}
  72  
  73  	resp, err := c.HTTPClient.Do(req)
  74  	if err != nil {
  75  		return err
  76  	}
  77  
  78  	defer func() { _ = resp.Body.Close() }()
  79  
  80  	if resp.StatusCode != http.StatusOK {
  81  		data, _ := io.ReadAll(resp.Body)
  82  
  83  		return fmt.Errorf("error: %d: %s", resp.StatusCode, string(data))
  84  	}
  85  
  86  	data, err := io.ReadAll(resp.Body)
  87  	if err != nil {
  88  		return err
  89  	}
  90  
  91  	err = xml.Unmarshal(data, op)
  92  	if err != nil {
  93  		return fmt.Errorf("failed to decode: %w: %s", err, data)
  94  	}
  95  
  96  	return nil
  97  }
  98  
  99  func GetEndpoint(prod, ote bool) (*url.URL, error) {
 100  	if prod && ote {
 101  		return nil, errors.New("prod and ote are mutually exclusive")
 102  	}
 103  
 104  	if prod {
 105  		return url.Parse(DefaultAPIEndpoint)
 106  	}
 107  
 108  	if ote {
 109  		return url.Parse(OTEAPIEndpoint)
 110  	}
 111  
 112  	return url.Parse(SandboxAPIEndpoint)
 113  }
 114  
 115  type replyGetter interface {
 116  	getCode() string
 117  	getDetail() string
 118  }
 119  
 120  func checkReply(reply replyGetter) error {
 121  	switch reply.getCode() {
 122  	case SuccessfulAPIOperation:
 123  		// Successful API operation
 124  		return nil
 125  	case SuccessfulRegistration:
 126  		// Successful registration, but not all provided hosts were valid resulting in our nameservers being used
 127  		return nil
 128  	case SuccessfulOrder:
 129  		// Successful order, but there was an error with the contact information provided so your account default contact profile was used
 130  		return nil
 131  	default:
 132  		// error
 133  		return fmt.Errorf("code: %s, details: %s", reply.getCode(), reply.getDetail())
 134  	}
 135  }
 136