Skip to main content

Functional Options Pattern

The Gcore Go SDK uses the functional options pattern for flexible configuration. Functions in the option package return a RequestOption that modifies request behavior.
package main

import (
    "github.com/G-Core/gcore-go"
    "github.com/G-Core/gcore-go/option"
)

func main() {
    client := gcore.NewClient(
        option.WithAPIKey("your-api-key"),
        option.WithMaxRetries(5),
    )
}

Common Request Options

The option package provides numerous request options for customizing behavior:

WithHeader

Add or modify HTTP headers:
import (
    "context"
    "github.com/G-Core/gcore-go"
    "github.com/G-Core/gcore-go/cloud"
    "github.com/G-Core/gcore-go/option"
)

// Client-level header (applies to all requests)
client := gcore.NewClient(
    option.WithHeader("X-Custom-Header", "global-value"),
)

// Request-level header (applies to this request only)
project, err := client.Cloud.Projects.New(
    context.TODO(),
    cloud.ProjectNewParams{Name: "my-project"},
    option.WithHeader("X-Custom-Header", "request-value"),
)
// Sets or overwrites a header
option.WithHeader("X-Custom-Header", "value")

WithMaxRetries

Configure retry behavior for failed requests:
import (
    "github.com/G-Core/gcore-go"
    "github.com/G-Core/gcore-go/option"
)

// Client-level: all requests retry up to 5 times
client := gcore.NewClient(
    option.WithMaxRetries(5),
)

// Request-level: this specific request retries up to 10 times
project, err := client.Cloud.Projects.New(
    context.TODO(),
    params,
    option.WithMaxRetries(10),
)

// Disable retries
project, err := client.Cloud.Projects.New(
    context.TODO(),
    params,
    option.WithMaxRetries(0),
)
The SDK automatically retries:
  • Connection errors
  • 408 Request Timeout
  • 409 Conflict
  • 429 Rate Limit
  • 5xx Server Errors
Default retry count is 2 (3 total attempts).

WithRequestTimeout

Set timeout for individual request attempts (not including retries):
import (
    "context"
    "time"
    
    "github.com/G-Core/gcore-go"
    "github.com/G-Core/gcore-go/option"
)

// Set per-retry timeout
client := gcore.NewClient(
    option.WithRequestTimeout(30 * time.Second),
)

// Combined with context timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()

project, err := client.Cloud.Projects.New(
    ctx, // Total timeout including retries: 5 minutes
    params,
    option.WithRequestTimeout(20*time.Second), // Per-attempt timeout: 20 seconds
)
Use context timeout for overall operation timeout (including retries) and WithRequestTimeout() for individual attempt timeout.

Client-Level vs Request-Level Options

Client-Level Options

Applied when creating the client and affect all requests:
client := gcore.NewClient(
    option.WithAPIKey("your-api-key"),
    option.WithMaxRetries(3),
    option.WithHeader("User-Agent", "MyApp/1.0"),
    option.WithRequestTimeout(30*time.Second),
)

// All requests use these options
project1, _ := client.Cloud.Projects.New(ctx, params1)
project2, _ := client.Cloud.Projects.New(ctx, params2)

Request-Level Options

Applied to individual requests and override client-level options:
client := gcore.NewClient(
    option.WithMaxRetries(2), // Default: 2 retries
)

// This request uses 2 retries (client default)
project1, _ := client.Cloud.Projects.New(ctx, params1)

// This request uses 5 retries (overrides client default)
project2, _ := client.Cloud.Projects.New(
    ctx,
    params2,
    option.WithMaxRetries(5),
)

// This request uses 2 retries again (client default)
project3, _ := client.Cloud.Projects.New(ctx, params3)

Example: Override Pattern

package main

import (
    "context"
    "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.WithHeader("X-Environment", "production"),
        option.WithMaxRetries(2),
    )
    
    // Use client defaults
    project1, _ := client.Cloud.Projects.New(
        context.TODO(),
        cloud.ProjectNewParams{Name: "project1"},
    )
    // Header: X-Environment=production, Retries: 2
    
    // Override for specific request
    project2, _ := client.Cloud.Projects.New(
        context.TODO(),
        cloud.ProjectNewParams{Name: "project2"},
        option.WithHeader("X-Environment", "staging"),
        option.WithMaxRetries(5),
    )
    // Header: X-Environment=staging, Retries: 5
}

WithDebugLog for Debugging

The WithDebugLog() option logs complete HTTP requests and responses:
import (
    "log"
    "github.com/G-Core/gcore-go"
    "github.com/G-Core/gcore-go/option"
)

// Use default logger
client := gcore.NewClient(
    option.WithDebugLog(nil),
)

// Use custom logger
customLogger := log.New(os.Stdout, "[GCORE] ", log.LstdFlags)
client := gcore.NewClient(
    option.WithDebugLog(customLogger),
)
WithDebugLog() is for development and debugging only. It logs sensitive information including API keys and request/response bodies. Never use in production.

Debug Log Output Example

package main

import (
    "context"
    "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.WithDebugLog(nil),
    )
    
    _, err := client.Cloud.Projects.New(
        context.TODO(),
        cloud.ProjectNewParams{Name: "debug-test"},
    )
}
Output:
Request Content:
POST /cloud/v1/projects HTTP/1.1
Host: api.gcore.com
Authorization: APIKey your-api-key
Content-Type: application/json

{"name":"debug-test"}

Response Content:
HTTP/1.1 201 Created
Content-Type: application/json

{"id":12345,"name":"debug-test"}

Advanced Options

WithMiddleware

Add custom middleware for request/response processing:
import (
    "fmt"
    "net/http"
    "time"
    
    "github.com/G-Core/gcore-go"
    "github.com/G-Core/gcore-go/option"
)

func LoggingMiddleware(req *http.Request, next option.MiddlewareNext) (*http.Response, error) {
    start := time.Now()
    fmt.Printf("Request: %s %s\n", req.Method, req.URL.Path)
    
    resp, err := next(req)
    
    duration := time.Since(start)
    if err != nil {
        fmt.Printf("Error: %s (took %v)\n", err, duration)
    } else {
        fmt.Printf("Response: %d (took %v)\n", resp.StatusCode, duration)
    }
    
    return resp, err
}

client := gcore.NewClient(
    option.WithMiddleware(LoggingMiddleware),
)

WithJSONSet and WithJSONDel

Modify request JSON body using sjson syntax:
import (
    "context"
    "github.com/G-Core/gcore-go"
    "github.com/G-Core/gcore-go/cloud"
    "github.com/G-Core/gcore-go/option"
)

params := cloud.ProjectNewParams{
    Name: "my-project",
}

project, err := client.Cloud.Projects.New(
    context.TODO(),
    params,
    // Add undocumented field
    option.WithJSONSet("custom_field", "custom_value"),
    // Add nested field
    option.WithJSONSet("metadata.environment", "production"),
)
Use WithJSONSet() to add fields not yet supported by the SDK or for experimental API features.

WithResponseInto

Capture the raw HTTP response:
import (
    "context"
    "fmt"
    "net/http"
    
    "github.com/G-Core/gcore-go"
    "github.com/G-Core/gcore-go/cloud"
    "github.com/G-Core/gcore-go/option"
)

var response *http.Response
project, err := client.Cloud.Projects.New(
    context.TODO(),
    cloud.ProjectNewParams{Name: "my-project"},
    option.WithResponseInto(&response),
)

if err != nil {
    panic(err)
}

fmt.Printf("Status Code: %d\n", response.StatusCode)
fmt.Printf("Headers: %+v\n", response.Header)
fmt.Printf("Content-Type: %s\n", response.Header.Get("Content-Type"))

WithQuery

Add or modify query parameters:
// Sets or overwrites query parameter
option.WithQuery("filter", "active")

WithHTTPClient

Use a custom HTTP client:
import (
    "net/http"
    "time"
    
    "github.com/G-Core/gcore-go"
    "github.com/G-Core/gcore-go/option"
)

// Custom HTTP client with specific settings
httpClient := &http.Client{
    Timeout: 60 * time.Second,
    Transport: &http.Transport{
        MaxIdleConns:        100,
        MaxIdleConnsPerHost: 10,
        IdleConnTimeout:     90 * time.Second,
    },
}

client := gcore.NewClient(
    option.WithHTTPClient(httpClient),
)

Complete Example

package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
    "time"
    
    "github.com/G-Core/gcore-go"
    "github.com/G-Core/gcore-go/cloud"
    "github.com/G-Core/gcore-go/option"
)

func main() {
    // Custom HTTP client
    httpClient := &http.Client{
        Timeout: 60 * time.Second,
    }
    
    // Client with multiple options
    client := gcore.NewClient(
        // Authentication
        option.WithAPIKey("your-api-key"),
        
        // HTTP configuration
        option.WithHTTPClient(httpClient),
        option.WithMaxRetries(3),
        option.WithRequestTimeout(30*time.Second),
        
        // Headers
        option.WithHeader("User-Agent", "MyApp/2.0"),
        option.WithHeader("X-Environment", "production"),
        
        // Cloud settings
        option.WithCloudProjectID(12345),
        option.WithCloudRegionID(67890),
    )
    
    // Standard request (uses client defaults)
    project1, err := client.Cloud.Projects.New(
        context.TODO(),
        cloud.ProjectNewParams{Name: "standard-project"},
    )
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Created project: %d\n", project1.ID)
    
    // Request with overrides
    var response *http.Response
    project2, err := client.Cloud.Projects.New(
        context.TODO(),
        cloud.ProjectNewParams{Name: "custom-project"},
        // Override client-level options
        option.WithMaxRetries(10),
        option.WithHeader("X-Environment", "staging"),
        // Capture response
        option.WithResponseInto(&response),
    )
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Created project: %d (HTTP %d)\n", project2.ID, response.StatusCode)
}

Best Practices

1. Use Client-Level Options for Common Settings

// ✅ Good - Common settings at client level
client := gcore.NewClient(
    option.WithAPIKey("key"),
    option.WithMaxRetries(3),
    option.WithRequestTimeout(30*time.Second),
)

// ❌ Bad - Repeating options on every request
project, _ := client.Cloud.Projects.New(
    ctx, params,
    option.WithMaxRetries(3),
    option.WithRequestTimeout(30*time.Second),
)

2. Use Request-Level Options for Exceptions

// ✅ Good - Override only when needed
project, _ := client.Cloud.Projects.New(
    ctx, params,
    option.WithMaxRetries(10), // This specific operation needs more retries
)

3. Disable Debug Logging in Production

import "os"

func newClient() *gcore.Client {
    opts := []option.RequestOption{
        option.WithAPIKey(os.Getenv("GCORE_API_KEY")),
    }
    
    // Only enable debug logging in development
    if os.Getenv("ENV") == "development" {
        opts = append(opts, option.WithDebugLog(nil))
    }
    
    return gcore.NewClient(opts...)
}

4. Combine Context and Request Timeouts

import "time"

// Set overall timeout with context
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()

// Set per-retry timeout with option
project, err := client.Cloud.Projects.New(
    ctx,
    params,
    option.WithRequestTimeout(20*time.Second),
)
// Maximum time: 2 minutes (context)
// Per attempt: 20 seconds (option)

Build docs developers (and LLMs) love