client.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. package ionet
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "fmt"
  6. "net/http"
  7. "net/url"
  8. "strconv"
  9. "time"
  10. )
  11. const (
  12. DefaultEnterpriseBaseURL = "https://api.io.solutions/enterprise/v1/io-cloud/caas"
  13. DefaultBaseURL = "https://api.io.solutions/v1/io-cloud/caas"
  14. DefaultTimeout = 30 * time.Second
  15. )
  16. // DefaultHTTPClient is the default HTTP client implementation
  17. type DefaultHTTPClient struct {
  18. client *http.Client
  19. }
  20. // NewDefaultHTTPClient creates a new default HTTP client
  21. func NewDefaultHTTPClient(timeout time.Duration) *DefaultHTTPClient {
  22. return &DefaultHTTPClient{
  23. client: &http.Client{
  24. Timeout: timeout,
  25. },
  26. }
  27. }
  28. // Do executes an HTTP request
  29. func (c *DefaultHTTPClient) Do(req *HTTPRequest) (*HTTPResponse, error) {
  30. httpReq, err := http.NewRequest(req.Method, req.URL, bytes.NewReader(req.Body))
  31. if err != nil {
  32. return nil, fmt.Errorf("failed to create HTTP request: %w", err)
  33. }
  34. // Set headers
  35. for key, value := range req.Headers {
  36. httpReq.Header.Set(key, value)
  37. }
  38. resp, err := c.client.Do(httpReq)
  39. if err != nil {
  40. return nil, fmt.Errorf("HTTP request failed: %w", err)
  41. }
  42. defer resp.Body.Close()
  43. // Read response body
  44. var body bytes.Buffer
  45. _, err = body.ReadFrom(resp.Body)
  46. if err != nil {
  47. return nil, fmt.Errorf("failed to read response body: %w", err)
  48. }
  49. // Convert headers
  50. headers := make(map[string]string)
  51. for key, values := range resp.Header {
  52. if len(values) > 0 {
  53. headers[key] = values[0]
  54. }
  55. }
  56. return &HTTPResponse{
  57. StatusCode: resp.StatusCode,
  58. Headers: headers,
  59. Body: body.Bytes(),
  60. }, nil
  61. }
  62. // NewEnterpriseClient creates a new IO.NET API client targeting the enterprise API base URL.
  63. func NewEnterpriseClient(apiKey string) *Client {
  64. return NewClientWithConfig(apiKey, DefaultEnterpriseBaseURL, nil)
  65. }
  66. // NewClient creates a new IO.NET API client targeting the public API base URL.
  67. func NewClient(apiKey string) *Client {
  68. return NewClientWithConfig(apiKey, DefaultBaseURL, nil)
  69. }
  70. // NewClientWithConfig creates a new IO.NET API client with custom configuration
  71. func NewClientWithConfig(apiKey, baseURL string, httpClient HTTPClient) *Client {
  72. if baseURL == "" {
  73. baseURL = DefaultBaseURL
  74. }
  75. if httpClient == nil {
  76. httpClient = NewDefaultHTTPClient(DefaultTimeout)
  77. }
  78. return &Client{
  79. BaseURL: baseURL,
  80. APIKey: apiKey,
  81. HTTPClient: httpClient,
  82. }
  83. }
  84. // makeRequest performs an HTTP request and handles common response processing
  85. func (c *Client) makeRequest(method, endpoint string, body interface{}) (*HTTPResponse, error) {
  86. var reqBody []byte
  87. var err error
  88. if body != nil {
  89. reqBody, err = json.Marshal(body)
  90. if err != nil {
  91. return nil, fmt.Errorf("failed to marshal request body: %w", err)
  92. }
  93. }
  94. headers := map[string]string{
  95. "X-API-KEY": c.APIKey,
  96. "Content-Type": "application/json",
  97. }
  98. req := &HTTPRequest{
  99. Method: method,
  100. URL: c.BaseURL + endpoint,
  101. Headers: headers,
  102. Body: reqBody,
  103. }
  104. resp, err := c.HTTPClient.Do(req)
  105. if err != nil {
  106. return nil, fmt.Errorf("request failed: %w", err)
  107. }
  108. // Handle API errors
  109. if resp.StatusCode >= 400 {
  110. var apiErr APIError
  111. if len(resp.Body) > 0 {
  112. // Try to parse the actual error format: {"detail": "message"}
  113. var errorResp struct {
  114. Detail string `json:"detail"`
  115. }
  116. if err := json.Unmarshal(resp.Body, &errorResp); err == nil && errorResp.Detail != "" {
  117. apiErr = APIError{
  118. Code: resp.StatusCode,
  119. Message: errorResp.Detail,
  120. }
  121. } else {
  122. // Fallback: use raw body as details
  123. apiErr = APIError{
  124. Code: resp.StatusCode,
  125. Message: fmt.Sprintf("API request failed with status %d", resp.StatusCode),
  126. Details: string(resp.Body),
  127. }
  128. }
  129. } else {
  130. apiErr = APIError{
  131. Code: resp.StatusCode,
  132. Message: fmt.Sprintf("API request failed with status %d", resp.StatusCode),
  133. }
  134. }
  135. return nil, &apiErr
  136. }
  137. return resp, nil
  138. }
  139. // buildQueryParams builds query parameters for GET requests
  140. func buildQueryParams(params map[string]interface{}) string {
  141. if len(params) == 0 {
  142. return ""
  143. }
  144. values := url.Values{}
  145. for key, value := range params {
  146. if value == nil {
  147. continue
  148. }
  149. switch v := value.(type) {
  150. case string:
  151. if v != "" {
  152. values.Add(key, v)
  153. }
  154. case int:
  155. if v != 0 {
  156. values.Add(key, strconv.Itoa(v))
  157. }
  158. case int64:
  159. if v != 0 {
  160. values.Add(key, strconv.FormatInt(v, 10))
  161. }
  162. case float64:
  163. if v != 0 {
  164. values.Add(key, strconv.FormatFloat(v, 'f', -1, 64))
  165. }
  166. case bool:
  167. values.Add(key, strconv.FormatBool(v))
  168. case time.Time:
  169. if !v.IsZero() {
  170. values.Add(key, v.Format(time.RFC3339))
  171. }
  172. case *time.Time:
  173. if v != nil && !v.IsZero() {
  174. values.Add(key, v.Format(time.RFC3339))
  175. }
  176. case []int:
  177. if len(v) > 0 {
  178. if encoded, err := json.Marshal(v); err == nil {
  179. values.Add(key, string(encoded))
  180. }
  181. }
  182. case []string:
  183. if len(v) > 0 {
  184. if encoded, err := json.Marshal(v); err == nil {
  185. values.Add(key, string(encoded))
  186. }
  187. }
  188. default:
  189. values.Add(key, fmt.Sprint(v))
  190. }
  191. }
  192. if len(values) > 0 {
  193. return "?" + values.Encode()
  194. }
  195. return ""
  196. }