Go SDK
Go SDK
Section titled “Go SDK”A comprehensive Go client library for the Splinterpic API. Supports Go 1.19+ with full type safety and idiomatic error handling.
Installation
Section titled “Installation”go get github.com/your-org/splinterpic-goSDK Implementation
Section titled “SDK Implementation”package splinterpic
import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "net/url" "time")
// Client represents a Splinterpic API clienttype Client struct { BaseURL string DefaultModel string HTTPClient *http.Client}
// Config holds client configurationtype Config struct { BaseURL string DefaultModel string Timeout time.Duration}
// GenerateRequest represents an image generation requesttype GenerateRequest struct { Prompt string `json:"prompt"` Model string `json:"model,omitempty"` TemplateID *string `json:"template_id,omitempty"` CollectionID *string `json:"collection_id,omitempty"`}
// Image represents a generated imagetype Image struct { ID string `json:"id"` Prompt string `json:"prompt"` Model string `json:"model"` R2Key string `json:"r2_key"` R2URL string `json:"r2_url"` CreatedAt time.Time `json:"created_at"` UserID string `json:"user_id"` Cost float64 `json:"cost"` TemplateID *string `json:"template_id,omitempty"` CollectionID *string `json:"collection_id,omitempty"`}
// ImagesResponse represents a list of imagestype ImagesResponse struct { Images []Image `json:"images"` Total int `json:"total"`}
// Model represents an AI modeltype Model struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description"` CostPerImage float64 `json:"cost_per_image"` IsRecommended bool `json:"is_recommended"`}
// ModelsResponse represents available modelstype ModelsResponse struct { Models []Model `json:"models"`}
// Collection represents an image collectiontype Collection struct { ID string `json:"id"` Name string `json:"name"` Description *string `json:"description,omitempty"` CreatedAt time.Time `json:"created_at"` UserID string `json:"user_id"`}
// CollectionsResponse represents a list of collectionstype CollectionsResponse struct { Collections []Collection `json:"collections"`}
// Error typestype Error struct { StatusCode int Message string}
func (e *Error) Error() string { return fmt.Sprintf("splinterpic: %s (status %d)", e.Message, e.StatusCode)}
// NewClient creates a new Splinterpic clientfunc NewClient(config Config) *Client { if config.Timeout == 0 { config.Timeout = 30 * time.Second }
if config.DefaultModel == "" { config.DefaultModel = "@cf/black-forest-labs/flux-1-schnell" }
return &Client{ BaseURL: config.BaseURL, DefaultModel: config.DefaultModel, HTTPClient: &http.Client{ Timeout: config.Timeout, }, }}
// Generate creates a new AI-generated imagefunc (c *Client) Generate(ctx context.Context, req GenerateRequest) (*Image, error) { if req.Model == "" { req.Model = c.DefaultModel }
var image Image err := c.request(ctx, "POST", "/api/generate", req, &image) if err != nil { return nil, err }
return &image, nil}
// Images retrieves a list of imagesfunc (c *Client) Images(ctx context.Context, filters map[string]string) (*ImagesResponse, error) { var response ImagesResponse err := c.requestWithQuery(ctx, "GET", "/api/images", filters, &response) if err != nil { return nil, err }
return &response, nil}
// Image retrieves a single image by IDfunc (c *Client) Image(ctx context.Context, imageID string) (*Image, error) { var image Image path := fmt.Sprintf("/api/images/%s", imageID) err := c.request(ctx, "GET", path, nil, &image) if err != nil { return nil, err }
return &image, nil}
// DeleteImage deletes an image by IDfunc (c *Client) DeleteImage(ctx context.Context, imageID string) error { path := fmt.Sprintf("/api/images/%s", imageID) return c.request(ctx, "DELETE", path, nil, nil)}
// Models retrieves available AI modelsfunc (c *Client) Models(ctx context.Context) (*ModelsResponse, error) { var response ModelsResponse err := c.request(ctx, "GET", "/api/models", nil, &response) if err != nil { return nil, err }
return &response, nil}
// CreateCollection creates a new image collectionfunc (c *Client) CreateCollection(ctx context.Context, name string, description *string) (*Collection, error) { req := map[string]interface{}{ "name": name, } if description != nil { req["description"] = *description }
var collection Collection err := c.request(ctx, "POST", "/api/collections", req, &collection) if err != nil { return nil, err }
return &collection, nil}
// Collections retrieves all collectionsfunc (c *Client) Collections(ctx context.Context) (*CollectionsResponse, error) { var response CollectionsResponse err := c.request(ctx, "GET", "/api/collections", nil, &response) if err != nil { return nil, err }
return &response, nil}
// request makes an HTTP request to the APIfunc (c *Client) request(ctx context.Context, method, path string, body interface{}, result interface{}) error { return c.requestWithQuery(ctx, method, path, nil, result)}
// requestWithQuery makes an HTTP request with query parametersfunc (c *Client) requestWithQuery(ctx context.Context, method, path string, query map[string]string, result interface{}) error { u, err := url.Parse(c.BaseURL + path) if err != nil { return fmt.Errorf("invalid URL: %w", err) }
if query != nil { q := u.Query() for k, v := range query { q.Set(k, v) } u.RawQuery = q.Encode() }
var reqBody io.Reader if body != nil { jsonData, err := json.Marshal(body) if err != nil { return fmt.Errorf("failed to marshal request: %w", err) } reqBody = bytes.NewBuffer(jsonData) }
req, err := http.NewRequestWithContext(ctx, method, u.String(), reqBody) if err != nil { return fmt.Errorf("failed to create request: %w", err) }
req.Header.Set("Content-Type", "application/json")
resp, err := c.HTTPClient.Do(req) if err != nil { return fmt.Errorf("request failed: %w", err) } defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("failed to read response: %w", err) }
if resp.StatusCode >= 400 { var apiError struct { Error string `json:"error"` } json.Unmarshal(respBody, &apiError)
return &Error{ StatusCode: resp.StatusCode, Message: apiError.Error, } }
if result != nil && resp.StatusCode != http.StatusNoContent { if err := json.Unmarshal(respBody, result); err != nil { return fmt.Errorf("failed to unmarshal response: %w", err) } }
return nil}Quick Start
Section titled “Quick Start”package main
import ( "context" "fmt" "log" "time"
"github.com/your-org/splinterpic-go")
func main() { // Initialize client client := splinterpic.NewClient(splinterpic.Config{ BaseURL: "https://splinterpic-worker.your-name.workers.dev", DefaultModel: "@cf/black-forest-labs/flux-1-schnell", Timeout: 30 * time.Second, })
ctx := context.Background()
// Generate an image image, err := client.Generate(ctx, splinterpic.GenerateRequest{ Prompt: "A serene mountain landscape at sunset", }) if err != nil { log.Fatalf("Failed to generate image: %v", err) }
fmt.Printf("Image generated successfully!\n") fmt.Printf("ID: %s\n", image.ID) fmt.Printf("URL: %s\n", image.R2URL) fmt.Printf("Cost: $%.4f\n", image.Cost)}Web Server Integration
Section titled “Web Server Integration”HTTP Handler Example
Section titled “HTTP Handler Example”package main
import ( "context" "encoding/json" "log" "net/http" "os" "time"
"github.com/your-org/splinterpic-go")
type Server struct { client *splinterpic.Client}
func NewServer() *Server { client := splinterpic.NewClient(splinterpic.Config{ BaseURL: os.Getenv("SPLINTERPIC_BASE_URL"), Timeout: 30 * time.Second, })
return &Server{client: client}}
func (s *Server) handleGenerate(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return }
var req splinterpic.GenerateRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return }
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second) defer cancel()
image, err := s.client.Generate(ctx, req) if err != nil { log.Printf("Generation error: %v", err) http.Error(w, err.Error(), http.StatusInternalServerError) return }
w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "success": true, "image": image, })}
func (s *Server) handleImages(w http.ResponseWriter, r *http.Request) { ctx := r.Context()
filters := make(map[string]string) if limit := r.URL.Query().Get("limit"); limit != "" { filters["limit"] = limit } if offset := r.URL.Query().Get("offset"); offset != "" { filters["offset"] = offset }
images, err := s.client.Images(ctx, filters) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return }
w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(images)}
func main() { server := NewServer()
http.HandleFunc("/api/generate", server.handleGenerate) http.HandleFunc("/api/images", server.handleImages)
log.Println("Server starting on :8080") log.Fatal(http.ListenAndServe(":8080", nil))}Gin Framework Integration
Section titled “Gin Framework Integration”package main
import ( "context" "net/http" "os" "time"
"github.com/gin-gonic/gin" "github.com/your-org/splinterpic-go")
func main() { client := splinterpic.NewClient(splinterpic.Config{ BaseURL: os.Getenv("SPLINTERPIC_BASE_URL"), Timeout: 30 * time.Second, })
r := gin.Default()
// Generate image endpoint r.POST("/api/generate", func(c *gin.Context) { var req splinterpic.GenerateRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return }
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) defer cancel()
image, err := client.Generate(ctx, req) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return }
c.JSON(http.StatusOK, gin.H{ "success": true, "image": image, }) })
// List images endpoint r.GET("/api/images", func(c *gin.Context) { filters := map[string]string{ "limit": c.DefaultQuery("limit", "20"), "offset": c.DefaultQuery("offset", "0"), }
images, err := client.Images(c.Request.Context(), filters) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return }
c.JSON(http.StatusOK, images) })
// Get single image r.GET("/api/images/:id", func(c *gin.Context) { imageID := c.Param("id")
image, err := client.Image(c.Request.Context(), imageID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "Image not found"}) return }
c.JSON(http.StatusOK, image) })
r.Run(":8080")}Advanced Usage
Section titled “Advanced Usage”Concurrent Batch Generation
Section titled “Concurrent Batch Generation”package main
import ( "context" "fmt" "sync" "time"
"github.com/your-org/splinterpic-go")
type BatchResult struct { Image *splinterpic.Image Error error}
func GenerateBatch(client *splinterpic.Client, prompts []string, maxConcurrent int) []BatchResult { ctx := context.Background() results := make([]BatchResult, len(prompts))
// Semaphore to limit concurrent requests sem := make(chan struct{}, maxConcurrent) var wg sync.WaitGroup
for i, prompt := range prompts { wg.Add(1) go func(index int, p string) { defer wg.Done()
sem <- struct{}{} // Acquire defer func() { <-sem }() // Release
image, err := client.Generate(ctx, splinterpic.GenerateRequest{ Prompt: p, })
results[index] = BatchResult{ Image: image, Error: err, } }(i, prompt) }
wg.Wait() return results}
func main() { client := splinterpic.NewClient(splinterpic.Config{ BaseURL: "https://splinterpic-worker.your-name.workers.dev", })
prompts := []string{ "Mountain landscape at sunset", "Ocean waves on beach", "City skyline at night", "Forest path in autumn", }
results := GenerateBatch(client, prompts, 3)
for i, result := range results { if result.Error != nil { fmt.Printf("Prompt %d failed: %v\n", i, result.Error) } else { fmt.Printf("Prompt %d generated: %s\n", i, result.Image.ID) } }}Retry with Exponential Backoff
Section titled “Retry with Exponential Backoff”package main
import ( "context" "fmt" "time"
"github.com/your-org/splinterpic-go")
type RetryConfig struct { MaxRetries int InitialBackoff time.Duration MaxBackoff time.Duration}
func GenerateWithRetry(client *splinterpic.Client, req splinterpic.GenerateRequest, config RetryConfig) (*splinterpic.Image, error) { ctx := context.Background() backoff := config.InitialBackoff
for attempt := 0; attempt <= config.MaxRetries; attempt++ { image, err := client.Generate(ctx, req) if err == nil { return image, nil }
// Check if error is retryable if apiErr, ok := err.(*splinterpic.Error); ok { if apiErr.StatusCode == 429 || apiErr.StatusCode >= 500 { // Rate limit or server error - retry if attempt < config.MaxRetries { fmt.Printf("Attempt %d failed, retrying in %v...\n", attempt+1, backoff) time.Sleep(backoff)
// Exponential backoff backoff *= 2 if backoff > config.MaxBackoff { backoff = config.MaxBackoff } continue } } }
return nil, err }
return nil, fmt.Errorf("max retries exceeded")}
func main() { client := splinterpic.NewClient(splinterpic.Config{ BaseURL: "https://splinterpic-worker.your-name.workers.dev", })
image, err := GenerateWithRetry(client, splinterpic.GenerateRequest{ Prompt: "Test image", }, RetryConfig{ MaxRetries: 3, InitialBackoff: 2 * time.Second, MaxBackoff: 30 * time.Second, })
if err != nil { fmt.Printf("Failed after retries: %v\n", err) return }
fmt.Printf("Generated: %s\n", image.ID)}Worker Pool Pattern
Section titled “Worker Pool Pattern”package main
import ( "context" "fmt" "sync"
"github.com/your-org/splinterpic-go")
type Job struct { Prompt string Result chan<- BatchResult}
type WorkerPool struct { client *splinterpic.Client jobs chan Job numWorkers int wg sync.WaitGroup}
func NewWorkerPool(client *splinterpic.Client, numWorkers int) *WorkerPool { return &WorkerPool{ client: client, jobs: make(chan Job, numWorkers*2), numWorkers: numWorkers, }}
func (p *WorkerPool) Start(ctx context.Context) { for i := 0; i < p.numWorkers; i++ { p.wg.Add(1) go p.worker(ctx, i) }}
func (p *WorkerPool) worker(ctx context.Context, id int) { defer p.wg.Done()
for job := range p.jobs { image, err := p.client.Generate(ctx, splinterpic.GenerateRequest{ Prompt: job.Prompt, })
job.Result <- BatchResult{ Image: image, Error: err, } }}
func (p *WorkerPool) Submit(job Job) { p.jobs <- job}
func (p *WorkerPool) Stop() { close(p.jobs) p.wg.Wait()}
func main() { client := splinterpic.NewClient(splinterpic.Config{ BaseURL: "https://splinterpic-worker.your-name.workers.dev", })
pool := NewWorkerPool(client, 5) ctx := context.Background() pool.Start(ctx)
prompts := []string{ "Mountain at sunset", "Ocean waves", "City at night", }
results := make(chan BatchResult, len(prompts))
for _, prompt := range prompts { pool.Submit(Job{ Prompt: prompt, Result: results, }) }
pool.Stop() close(results)
for result := range results { if result.Error != nil { fmt.Printf("Error: %v\n", result.Error) } else { fmt.Printf("Generated: %s\n", result.Image.ID) } }}Context Timeout Management
Section titled “Context Timeout Management”package main
import ( "context" "fmt" "time"
"github.com/your-org/splinterpic-go")
func GenerateWithTimeout(client *splinterpic.Client, prompt string, timeout time.Duration) (*splinterpic.Image, error) { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel()
image, err := client.Generate(ctx, splinterpic.GenerateRequest{ Prompt: prompt, })
if err != nil { if ctx.Err() == context.DeadlineExceeded { return nil, fmt.Errorf("request timed out after %v", timeout) } return nil, err }
return image, nil}
func main() { client := splinterpic.NewClient(splinterpic.Config{ BaseURL: "https://splinterpic-worker.your-name.workers.dev", Timeout: 60 * time.Second, // Overall client timeout })
// Generate with custom timeout image, err := GenerateWithTimeout(client, "Test image", 15*time.Second) if err != nil { fmt.Printf("Error: %v\n", err) return }
fmt.Printf("Generated: %s\n", image.ID)}Testing
Section titled “Testing”Unit Test Example
Section titled “Unit Test Example”package splinterpic_test
import ( "context" "net/http" "net/http/httptest" "testing"
"github.com/your-org/splinterpic-go")
func TestGenerate(t *testing.T) { // Mock server server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/api/generate" { t.Errorf("Expected path /api/generate, got %s", r.URL.Path) }
if r.Method != http.MethodPost { t.Errorf("Expected POST method, got %s", r.Method) }
w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write([]byte(`{ "id": "img_test123", "prompt": "Test prompt", "model": "@cf/black-forest-labs/flux-1-schnell", "r2_url": "https://example.com/image.png", "cost": 0.01 }`)) })) defer server.Close()
client := splinterpic.NewClient(splinterpic.Config{ BaseURL: server.URL, })
ctx := context.Background() image, err := client.Generate(ctx, splinterpic.GenerateRequest{ Prompt: "Test prompt", })
if err != nil { t.Fatalf("Generate failed: %v", err) }
if image.ID != "img_test123" { t.Errorf("Expected ID img_test123, got %s", image.ID) }
if image.Prompt != "Test prompt" { t.Errorf("Expected prompt 'Test prompt', got %s", image.Prompt) }}
func TestErrorHandling(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error": "Invalid prompt"}`)) })) defer server.Close()
client := splinterpic.NewClient(splinterpic.Config{ BaseURL: server.URL, })
ctx := context.Background() _, err := client.Generate(ctx, splinterpic.GenerateRequest{ Prompt: "", })
if err == nil { t.Fatal("Expected error, got nil") }
apiErr, ok := err.(*splinterpic.Error) if !ok { t.Fatalf("Expected *splinterpic.Error, got %T", err) }
if apiErr.StatusCode != 400 { t.Errorf("Expected status code 400, got %d", apiErr.StatusCode) }}Best Practices
Section titled “Best Practices”1. Always Use Context
Section titled “1. Always Use Context”// Good - allows cancellation and timeoutctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)defer cancel()image, err := client.Generate(ctx, req)
// Bad - no timeout or cancellationimage, err := client.Generate(context.Background(), req)2. Handle Errors Properly
Section titled “2. Handle Errors Properly”image, err := client.Generate(ctx, req)if err != nil { if apiErr, ok := err.(*splinterpic.Error); ok { switch apiErr.StatusCode { case 429: // Rate limited - wait and retry time.Sleep(5 * time.Second) // retry logic... case 402: // Quota exceeded - notify admin log.Printf("Quota exceeded: %v", apiErr) default: log.Printf("API error: %v", apiErr) } } return err}3. Use Environment Variables
Section titled “3. Use Environment Variables”import "os"
client := splinterpic.NewClient(splinterpic.Config{ BaseURL: os.Getenv("SPLINTERPIC_BASE_URL"), Timeout: 30 * time.Second,})4. Implement Graceful Shutdown
Section titled “4. Implement Graceful Shutdown”func main() { client := splinterpic.NewClient(splinterpic.Config{ BaseURL: os.Getenv("SPLINTERPIC_BASE_URL"), })
ctx, cancel := context.WithCancel(context.Background()) defer cancel()
// Listen for interrupt signal sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
go func() { <-sigChan log.Println("Shutting down gracefully...") cancel() }()
// Use ctx for all operations image, err := client.Generate(ctx, splinterpic.GenerateRequest{ Prompt: "Test", })}Support
Section titled “Support”- Documentation: API Reference
- Email: support@splinterpic.com