Skip to main content

The gcore.Error Type

When the Gcore API returns a non-success status code, the SDK returns an error of type *gcore.Error. This error type contains detailed information about the failed request:
type Error struct {
    StatusCode int
    Request    *http.Request
    Response   *http.Response
    JSON       struct {
        ExtraFields map[string]respjson.Field
        raw         string
    }
}

Error Structure

  • StatusCode: HTTP status code of the response
  • Request: The original HTTP request
  • Response: The HTTP response from the API
  • JSON: Raw JSON response body and any extra fields

Using errors.As Pattern

The recommended way to handle API errors is using the errors.As pattern:
package main

import (
    "context"
    "errors"
    "fmt"
    
    "github.com/G-Core/gcore-go"
    "github.com/G-Core/gcore-go/cloud"
    "github.com/G-Core/gcore-go/option"
)

func main() {
    client := gcore.NewClient(
        option.WithAPIKey("My API Key"),
    )
    
    _, err := client.Cloud.Projects.New(context.TODO(), cloud.ProjectNewParams{
        Name: "my-project",
    })
    
    if err != nil {
        var apierr *gcore.Error
        if errors.As(err, &apierr) {
            // This is an API error
            fmt.Printf("API Error: Status %d\n", apierr.StatusCode)
            fmt.Printf("Error details: %s\n", apierr.JSON.raw)
        } else {
            // This is a different type of error (network, etc.)
            fmt.Printf("Other error: %s\n", err.Error())
        }
    }
}
The errors.As pattern allows you to distinguish between API errors (with detailed response information) and other types of errors like network failures.

DumpRequest() and DumpResponse() Methods

The gcore.Error type provides methods to inspect the full HTTP request and response:

DumpRequest()

Serializes the HTTP request for debugging:
package main

import (
    "context"
    "errors"
    "fmt"
    
    "github.com/G-Core/gcore-go"
    "github.com/G-Core/gcore-go/cloud"
)

func main() {
    client := gcore.NewClient()
    
    _, err := client.Cloud.Projects.New(context.TODO(), cloud.ProjectNewParams{
        Name: "my-project",
    })
    
    if err != nil {
        var apierr *gcore.Error
        if errors.As(err, &apierr) {
            // Print the full HTTP request
            fmt.Println(string(apierr.DumpRequest(true)))
        }
    }
}

DumpResponse()

Serializes the HTTP response for debugging:
package main

import (
    "context"
    "errors"
    "fmt"
    
    "github.com/G-Core/gcore-go"
    "github.com/G-Core/gcore-go/cloud"
)

func main() {
    client := gcore.NewClient()
    
    _, err := client.Cloud.Projects.New(context.TODO(), cloud.ProjectNewParams{
        Name: "my-project",
    })
    
    if err != nil {
        var apierr *gcore.Error
        if errors.As(err, &apierr) {
            // Print the full HTTP response
            fmt.Println(string(apierr.DumpResponse(true)))
        }
    }
}

Complete Example with Both Methods

package main

import (
    "context"
    "errors"
    "fmt"
    
    "github.com/G-Core/gcore-go"
    "github.com/G-Core/gcore-go/cloud"
)

func main() {
    client := gcore.NewClient()
    
    _, err := client.Cloud.Projects.New(context.TODO(), cloud.ProjectNewParams{
        Name: "my-project",
    })
    
    if err != nil {
        var apierr *gcore.Error
        if errors.As(err, &apierr) {
            fmt.Println("=== REQUEST ===")
            fmt.Println(string(apierr.DumpRequest(true)))
            
            fmt.Println("\n=== RESPONSE ===")
            fmt.Println(string(apierr.DumpResponse(true)))
        }
        panic(err.Error())
    }
}
When dumping requests/responses with true, sensitive data like API keys may be included. Use false in production or ensure logs are secured.

Common Error Scenarios

400 Bad Request

Invalid request parameters:
func handleBadRequest(err error) {
    var apierr *gcore.Error
    if errors.As(err, &apierr) && apierr.StatusCode == 400 {
        fmt.Println("Invalid request parameters:")
        fmt.Println(apierr.JSON.raw)
        // Parse and show specific validation errors
    }
}

401 Unauthorized

Authentication failure:
func handleUnauthorized(err error) {
    var apierr *gcore.Error
    if errors.As(err, &apierr) && apierr.StatusCode == 401 {
        fmt.Println("Authentication failed. Check your API key.")
        // Potentially refresh or re-authenticate
    }
}

403 Forbidden

Insufficient permissions:
func handleForbidden(err error) {
    var apierr *gcore.Error
    if errors.As(err, &apierr) && apierr.StatusCode == 403 {
        fmt.Println("Insufficient permissions for this operation.")
        // Log for security audit
    }
}

404 Not Found

Resource doesn’t exist:
func handleNotFound(err error) {
    var apierr *gcore.Error
    if errors.As(err, &apierr) && apierr.StatusCode == 404 {
        fmt.Println("Resource not found.")
        // Handle missing resource gracefully
        return nil // or create the resource
    }
}

429 Rate Limit

Too many requests:
import "time"

func handleRateLimit(err error) {
    var apierr *gcore.Error
    if errors.As(err, &apierr) && apierr.StatusCode == 429 {
        fmt.Println("Rate limit exceeded. Waiting before retry...")
        time.Sleep(60 * time.Second)
        // Retry the request
    }
}
The SDK automatically retries 429 errors with exponential backoff (default: 2 retries). You can configure this with option.WithMaxRetries().

500+ Server Errors

Internal server errors:
func handleServerError(err error) {
    var apierr *gcore.Error
    if errors.As(err, &apierr) && apierr.StatusCode >= 500 {
        fmt.Printf("Server error: %d\n", apierr.StatusCode)
        // Log for monitoring
        // Retry with backoff
    }
}

Non-API Errors

Some errors are not API errors but other types of failures:
import (
    "errors"
    "net/url"
    "net"
)

func handleErrors(err error) {
    // API error
    var apierr *gcore.Error
    if errors.As(err, &apierr) {
        fmt.Printf("API error: %d\n", apierr.StatusCode)
        return
    }
    
    // URL error (DNS, connection issues)
    var urlerr *url.Error
    if errors.As(err, &urlerr) {
        fmt.Printf("URL error: %s\n", urlerr.Error())
        return
    }
    
    // Network error
    var neterr *net.OpError
    if errors.As(err, &neterr) {
        fmt.Printf("Network error: %s\n", neterr.Error())
        return
    }
    
    // Other errors
    fmt.Printf("Unknown error: %s\n", err.Error())
}

Error Handling Patterns

Retry with Exponential Backoff

import "time"

func retryWithBackoff(operation func() error, maxRetries int) error {
    var err error
    for i := 0; i < maxRetries; i++ {
        err = operation()
        if err == nil {
            return nil
        }
        
        var apierr *gcore.Error
        if errors.As(err, &apierr) {
            // Don't retry client errors (4xx)
            if apierr.StatusCode >= 400 && apierr.StatusCode < 500 {
                return err
            }
        }
        
        // Exponential backoff
        waitTime := time.Duration(1<<uint(i)) * time.Second
        time.Sleep(waitTime)
    }
    return err
}
The SDK has built-in retry logic for connection errors, 408, 409, 429, and 5xx errors with a default of 2 retries. Use option.WithMaxRetries() to configure this.

Graceful Degradation

func getProjectWithFallback(client *gcore.Client, projectID int64) (*cloud.Project, error) {
    project, err := client.Cloud.Projects.Get(context.TODO(), projectID)
    
    if err != nil {
        var apierr *gcore.Error
        if errors.As(err, &apierr) && apierr.StatusCode == 404 {
            // Return a default project or handle gracefully
            return getDefaultProject(), nil
        }
        return nil, err
    }
    
    return project, nil
}

Structured Error Logging

import "log/slog"

func logError(err error, operation string) {
    var apierr *gcore.Error
    if errors.As(err, &apierr) {
        slog.Error("API error",
            "operation", operation,
            "status", apierr.StatusCode,
            "method", apierr.Request.Method,
            "url", apierr.Request.URL.String(),
            "response", apierr.JSON.raw,
        )
    } else {
        slog.Error("Non-API error",
            "operation", operation,
            "error", err.Error(),
        )
    }
}

Complete Error Handling Example

package main

import (
    "context"
    "errors"
    "fmt"
    "log"
    
    "github.com/G-Core/gcore-go"
    "github.com/G-Core/gcore-go/cloud"
)

func main() {
    client := gcore.NewClient()
    
    if err := createProject(client, "my-project"); err != nil {
        log.Fatal(err)
    }
}

func createProject(client *gcore.Client, name string) error {
    project, err := client.Cloud.Projects.New(context.TODO(), cloud.ProjectNewParams{
        Name: name,
    })
    
    if err != nil {
        return handleProjectError(err, "create project")
    }
    
    fmt.Printf("Created project: %d\n", project.ID)
    return nil
}

func handleProjectError(err error, operation string) error {
    var apierr *gcore.Error
    if errors.As(err, &apierr) {
        switch apierr.StatusCode {
        case 400:
            return fmt.Errorf("%s: invalid parameters - %s", operation, apierr.JSON.raw)
        case 401:
            return fmt.Errorf("%s: authentication failed", operation)
        case 403:
            return fmt.Errorf("%s: insufficient permissions", operation)
        case 404:
            return fmt.Errorf("%s: resource not found", operation)
        case 429:
            return fmt.Errorf("%s: rate limit exceeded", operation)
        default:
            // Log detailed error for debugging
            log.Printf("Request: %s\n", apierr.DumpRequest(true))
            log.Printf("Response: %s\n", apierr.DumpResponse(true))
            return fmt.Errorf("%s: API error %d", operation, apierr.StatusCode)
        }
    }
    
    // Non-API error
    return fmt.Errorf("%s: %w", operation, err)
}

Build docs developers (and LLMs) love