diff --git a/faas-client/README.md b/faas-client/README.md new file mode 100644 index 0000000..16881ef --- /dev/null +++ b/faas-client/README.md @@ -0,0 +1,297 @@ +# Skybridge FaaS Client + +A lightweight Go client library for interacting with the Skybridge Function-as-a-Service (FaaS) platform. + +## Installation + +```bash +go get github.com/RyanCopley/skybridge/faas-client +``` + +## Usage + +### Basic Client Setup + +```go +package main + +import ( + "context" + "log" + + faasclient "github.com/RyanCopley/skybridge/faas-client" +) + +func main() { + // Create a new client + client := faasclient.NewClient( + "http://localhost:8080", // FaaS API base URL + faasclient.WithUserEmail("admin@example.com"), // Authentication + ) + + // Use the client... +} +``` + +### Authentication Options + +```go +// Using user email header (for development) +client := faasclient.NewClient(baseURL, + faasclient.WithUserEmail("user@example.com")) + +// Using custom auth headers +client := faasclient.NewClient(baseURL, + faasclient.WithAuth(map[string]string{ + "Authorization": "Bearer " + token, + "X-User-Email": "user@example.com", + })) + +// Using custom HTTP client +httpClient := &http.Client{Timeout: 30 * time.Second} +client := faasclient.NewClient(baseURL, + faasclient.WithHTTPClient(httpClient)) +``` + +### Function Management + +#### Creating a Function + +```go +req := &faasclient.CreateFunctionRequest{ + Name: "my-function", + AppID: "my-app", + Runtime: faasclient.RuntimeNodeJS18, + Image: "node:18-alpine", // Optional, auto-selected if not provided + Handler: "index.handler", + Code: "exports.handler = async (event) => { return 'Hello World'; }", + Environment: map[string]string{ + "NODE_ENV": "production", + }, + Timeout: faasclient.Duration(30 * time.Second), + Memory: 512, + Owner: faasclient.Owner{ + Type: faasclient.OwnerTypeIndividual, + Name: "John Doe", + Owner: "john@example.com", + }, +} + +function, err := client.CreateFunction(context.Background(), req) +if err != nil { + log.Fatal(err) +} + +log.Printf("Created function: %s (ID: %s)", function.Name, function.ID) +``` + +#### Getting a Function + +```go +function, err := client.GetFunction(context.Background(), functionID) +if err != nil { + log.Fatal(err) +} + +log.Printf("Function: %s, Runtime: %s", function.Name, function.Runtime) +``` + +#### Updating a Function + +```go +newTimeout := faasclient.Duration(60 * time.Second) +req := &faasclient.UpdateFunctionRequest{ + Timeout: &newTimeout, + Environment: map[string]string{ + "NODE_ENV": "development", + }, +} + +function, err := client.UpdateFunction(context.Background(), functionID, req) +if err != nil { + log.Fatal(err) +} +``` + +#### Listing Functions + +```go +response, err := client.ListFunctions(context.Background(), "my-app", 50, 0) +if err != nil { + log.Fatal(err) +} + +for _, fn := range response.Functions { + log.Printf("Function: %s (ID: %s)", fn.Name, fn.ID) +} +``` + +#### Deleting a Function + +```go +err := client.DeleteFunction(context.Background(), functionID) +if err != nil { + log.Fatal(err) +} +``` + +### Function Deployment + +```go +// Deploy with default options +resp, err := client.DeployFunction(context.Background(), functionID, nil) +if err != nil { + log.Fatal(err) +} + +// Force deployment +req := &faasclient.DeployFunctionRequest{ + Force: true, +} +resp, err = client.DeployFunction(context.Background(), functionID, req) +if err != nil { + log.Fatal(err) +} + +log.Printf("Deployment status: %s", resp.Status) +``` + +### Function Execution + +#### Synchronous Execution + +```go +input := json.RawMessage(`{"name": "World"}`) +req := &faasclient.ExecuteFunctionRequest{ + FunctionID: functionID, + Input: input, + Async: false, +} + +response, err := client.ExecuteFunction(context.Background(), req) +if err != nil { + log.Fatal(err) +} + +log.Printf("Result: %s", string(response.Output)) +log.Printf("Duration: %v", response.Duration) +``` + +#### Asynchronous Execution + +```go +input := json.RawMessage(`{"name": "World"}`) +response, err := client.InvokeFunction(context.Background(), functionID, input) +if err != nil { + log.Fatal(err) +} + +log.Printf("Execution ID: %s", response.ExecutionID) +log.Printf("Status: %s", response.Status) +``` + +### Execution Management + +#### Getting Execution Details + +```go +execution, err := client.GetExecution(context.Background(), executionID) +if err != nil { + log.Fatal(err) +} + +log.Printf("Status: %s", execution.Status) +log.Printf("Duration: %v", execution.Duration) +if execution.Error != "" { + log.Printf("Error: %s", execution.Error) +} +``` + +#### Listing Executions + +```go +// List all executions +response, err := client.ListExecutions(context.Background(), nil, 50, 0) +if err != nil { + log.Fatal(err) +} + +// List executions for a specific function +response, err = client.ListExecutions(context.Background(), &functionID, 50, 0) +if err != nil { + log.Fatal(err) +} + +for _, exec := range response.Executions { + log.Printf("Execution: %s, Status: %s", exec.ID, exec.Status) +} +``` + +#### Canceling an Execution + +```go +err := client.CancelExecution(context.Background(), executionID) +if err != nil { + log.Fatal(err) +} +``` + +#### Getting Execution Logs + +```go +logs, err := client.GetExecutionLogs(context.Background(), executionID) +if err != nil { + log.Fatal(err) +} + +for _, logLine := range logs.Logs { + log.Printf("Log: %s", logLine) +} +``` + +#### Getting Running Executions + +```go +response, err := client.GetRunningExecutions(context.Background()) +if err != nil { + log.Fatal(err) +} + +log.Printf("Running executions: %d", response.Count) +for _, exec := range response.Executions { + log.Printf("Running: %s (Function: %s)", exec.ID, exec.FunctionID) +} +``` + +## Types + +The client provides comprehensive type definitions that match the FaaS API: + +- `FunctionDefinition` - Complete function metadata +- `FunctionExecution` - Execution details and results +- `RuntimeType` - Supported runtimes (NodeJS18, Python39, Go120, Custom) +- `ExecutionStatus` - Execution states (Pending, Running, Completed, Failed, etc.) +- `Owner` - Ownership information +- Request/Response types for all operations + +## Error Handling + +The client provides detailed error messages that include HTTP status codes and response bodies: + +```go +function, err := client.GetFunction(ctx, nonExistentID) +if err != nil { + // Error will include status code and details + log.Printf("Error: %v", err) // "get function failed with status 404: Function not found" +} +``` + +## Architecture Benefits + +This client package is designed as a lightweight, standalone library that: + +- **No heavy dependencies**: Only requires `google/uuid` +- **Zero coupling**: Doesn't import the entire FaaS service +- **Modular**: Can be used by any service in your monolith +- **Type-safe**: Comprehensive Go types for all API operations +- **Flexible auth**: Supports multiple authentication methods \ No newline at end of file diff --git a/faas-client/client.go b/faas-client/client.go new file mode 100644 index 0000000..db9b677 --- /dev/null +++ b/faas-client/client.go @@ -0,0 +1,396 @@ +package faasclient + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + + "github.com/google/uuid" +) + +// Client represents the FaaS API client +type Client struct { + baseURL string + httpClient *http.Client + authHeader map[string]string +} + +// NewClient creates a new FaaS client +func NewClient(baseURL string, options ...ClientOption) *Client { + client := &Client{ + baseURL: baseURL, + httpClient: http.DefaultClient, + authHeader: make(map[string]string), + } + + for _, option := range options { + option(client) + } + + return client +} + +// ClientOption represents a configuration option for the client +type ClientOption func(*Client) + +// WithHTTPClient sets a custom HTTP client +func WithHTTPClient(httpClient *http.Client) ClientOption { + return func(c *Client) { + c.httpClient = httpClient + } +} + +// WithAuth sets authentication headers +func WithAuth(headers map[string]string) ClientOption { + return func(c *Client) { + for k, v := range headers { + c.authHeader[k] = v + } + } +} + +// WithUserEmail sets the X-User-Email header for authentication +func WithUserEmail(email string) ClientOption { + return func(c *Client) { + c.authHeader["X-User-Email"] = email + } +} + +// doRequest performs an HTTP request +func (c *Client) doRequest(ctx context.Context, method, path string, body interface{}) (*http.Response, error) { + var reqBody io.Reader + if body != nil { + jsonData, err := json.Marshal(body) + if err != nil { + return nil, fmt.Errorf("failed to marshal request body: %w", err) + } + reqBody = bytes.NewBuffer(jsonData) + } + + req, err := http.NewRequestWithContext(ctx, method, c.baseURL+path, reqBody) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + if body != nil { + req.Header.Set("Content-Type", "application/json") + } + + // Add authentication headers + for k, v := range c.authHeader { + req.Header.Set(k, v) + } + + return c.httpClient.Do(req) +} + +// CreateFunction creates a new function +func (c *Client) CreateFunction(ctx context.Context, req *CreateFunctionRequest) (*FunctionDefinition, error) { + resp, err := c.doRequest(ctx, "POST", "/api/v1/functions", req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusCreated { + body, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("create function failed with status %d: %s", resp.StatusCode, string(body)) + } + + var function FunctionDefinition + if err := json.NewDecoder(resp.Body).Decode(&function); err != nil { + return nil, fmt.Errorf("failed to decode response: %w", err) + } + + return &function, nil +} + +// GetFunction retrieves a function by ID +func (c *Client) GetFunction(ctx context.Context, id uuid.UUID) (*FunctionDefinition, error) { + resp, err := c.doRequest(ctx, "GET", "/api/v1/functions/"+id.String(), nil) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("get function failed with status %d: %s", resp.StatusCode, string(body)) + } + + var function FunctionDefinition + if err := json.NewDecoder(resp.Body).Decode(&function); err != nil { + return nil, fmt.Errorf("failed to decode response: %w", err) + } + + return &function, nil +} + +// UpdateFunction updates an existing function +func (c *Client) UpdateFunction(ctx context.Context, id uuid.UUID, req *UpdateFunctionRequest) (*FunctionDefinition, error) { + resp, err := c.doRequest(ctx, "PUT", "/api/v1/functions/"+id.String(), req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("update function failed with status %d: %s", resp.StatusCode, string(body)) + } + + var function FunctionDefinition + if err := json.NewDecoder(resp.Body).Decode(&function); err != nil { + return nil, fmt.Errorf("failed to decode response: %w", err) + } + + return &function, nil +} + +// DeleteFunction deletes a function +func (c *Client) DeleteFunction(ctx context.Context, id uuid.UUID) error { + resp, err := c.doRequest(ctx, "DELETE", "/api/v1/functions/"+id.String(), nil) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return fmt.Errorf("delete function failed with status %d: %s", resp.StatusCode, string(body)) + } + + return nil +} + +// ListFunctions lists functions with optional filtering +func (c *Client) ListFunctions(ctx context.Context, appID string, limit, offset int) (*ListFunctionsResponse, error) { + params := url.Values{} + if appID != "" { + params.Set("app_id", appID) + } + if limit > 0 { + params.Set("limit", strconv.Itoa(limit)) + } + if offset > 0 { + params.Set("offset", strconv.Itoa(offset)) + } + + path := "/api/v1/functions" + if len(params) > 0 { + path += "?" + params.Encode() + } + + resp, err := c.doRequest(ctx, "GET", path, nil) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("list functions failed with status %d: %s", resp.StatusCode, string(body)) + } + + var response ListFunctionsResponse + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + return nil, fmt.Errorf("failed to decode response: %w", err) + } + + return &response, nil +} + +// DeployFunction deploys a function +func (c *Client) DeployFunction(ctx context.Context, id uuid.UUID, req *DeployFunctionRequest) (*DeployFunctionResponse, error) { + if req == nil { + req = &DeployFunctionRequest{FunctionID: id} + } + req.FunctionID = id + + resp, err := c.doRequest(ctx, "POST", "/api/v1/functions/"+id.String()+"/deploy", req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("deploy function failed with status %d: %s", resp.StatusCode, string(body)) + } + + var response DeployFunctionResponse + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + return nil, fmt.Errorf("failed to decode response: %w", err) + } + + return &response, nil +} + +// ExecuteFunction executes a function synchronously or asynchronously +func (c *Client) ExecuteFunction(ctx context.Context, req *ExecuteFunctionRequest) (*ExecuteFunctionResponse, error) { + resp, err := c.doRequest(ctx, "POST", "/api/v1/functions/"+req.FunctionID.String()+"/execute", req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("execute function failed with status %d: %s", resp.StatusCode, string(body)) + } + + var response ExecuteFunctionResponse + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + return nil, fmt.Errorf("failed to decode response: %w", err) + } + + return &response, nil +} + +// InvokeFunction invokes a function asynchronously +func (c *Client) InvokeFunction(ctx context.Context, functionID uuid.UUID, input json.RawMessage) (*ExecuteFunctionResponse, error) { + req := &ExecuteFunctionRequest{ + FunctionID: functionID, + Input: input, + Async: true, + } + + resp, err := c.doRequest(ctx, "POST", "/api/v1/functions/"+functionID.String()+"/invoke", req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusAccepted { + body, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("invoke function failed with status %d: %s", resp.StatusCode, string(body)) + } + + var response ExecuteFunctionResponse + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + return nil, fmt.Errorf("failed to decode response: %w", err) + } + + return &response, nil +} + +// GetExecution retrieves an execution by ID +func (c *Client) GetExecution(ctx context.Context, id uuid.UUID) (*FunctionExecution, error) { + resp, err := c.doRequest(ctx, "GET", "/api/v1/executions/"+id.String(), nil) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("get execution failed with status %d: %s", resp.StatusCode, string(body)) + } + + var execution FunctionExecution + if err := json.NewDecoder(resp.Body).Decode(&execution); err != nil { + return nil, fmt.Errorf("failed to decode response: %w", err) + } + + return &execution, nil +} + +// ListExecutions lists executions with optional filtering +func (c *Client) ListExecutions(ctx context.Context, functionID *uuid.UUID, limit, offset int) (*ListExecutionsResponse, error) { + params := url.Values{} + if functionID != nil { + params.Set("function_id", functionID.String()) + } + if limit > 0 { + params.Set("limit", strconv.Itoa(limit)) + } + if offset > 0 { + params.Set("offset", strconv.Itoa(offset)) + } + + path := "/api/v1/executions" + if len(params) > 0 { + path += "?" + params.Encode() + } + + resp, err := c.doRequest(ctx, "GET", path, nil) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("list executions failed with status %d: %s", resp.StatusCode, string(body)) + } + + var response ListExecutionsResponse + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + return nil, fmt.Errorf("failed to decode response: %w", err) + } + + return &response, nil +} + +// CancelExecution cancels a running execution +func (c *Client) CancelExecution(ctx context.Context, id uuid.UUID) error { + resp, err := c.doRequest(ctx, "POST", "/api/v1/executions/"+id.String()+"/cancel", nil) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return fmt.Errorf("cancel execution failed with status %d: %s", resp.StatusCode, string(body)) + } + + return nil +} + +// GetExecutionLogs retrieves logs for an execution +func (c *Client) GetExecutionLogs(ctx context.Context, id uuid.UUID) (*GetLogsResponse, error) { + resp, err := c.doRequest(ctx, "GET", "/api/v1/executions/"+id.String()+"/logs", nil) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("get execution logs failed with status %d: %s", resp.StatusCode, string(body)) + } + + var response GetLogsResponse + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + return nil, fmt.Errorf("failed to decode response: %w", err) + } + + return &response, nil +} + +// GetRunningExecutions retrieves all currently running executions +func (c *Client) GetRunningExecutions(ctx context.Context) (*GetRunningExecutionsResponse, error) { + resp, err := c.doRequest(ctx, "GET", "/api/v1/executions/running", nil) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("get running executions failed with status %d: %s", resp.StatusCode, string(body)) + } + + var response GetRunningExecutionsResponse + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + return nil, fmt.Errorf("failed to decode response: %w", err) + } + + return &response, nil +} \ No newline at end of file diff --git a/faas-client/example/main.go b/faas-client/example/main.go new file mode 100644 index 0000000..4031ebb --- /dev/null +++ b/faas-client/example/main.go @@ -0,0 +1,148 @@ +package main + +import ( + "context" + "encoding/json" + "log" + "time" + + "github.com/google/uuid" + faasclient "github.com/RyanCopley/skybridge/faas-client" +) + +func main() { + // Create FaaS client + client := faasclient.NewClient( + "http://localhost:8080", + faasclient.WithUserEmail("admin@example.com"), + ) + + ctx := context.Background() + + // Create a simple Node.js function + log.Println("Creating function...") + createReq := &faasclient.CreateFunctionRequest{ + Name: "hello-world-example", + AppID: "example-app", + Runtime: faasclient.RuntimeNodeJS18, + Handler: "index.handler", + Code: "exports.handler = async (event) => { return { message: 'Hello, ' + (event.name || 'World') + '!' }; }", + Environment: map[string]string{ + "NODE_ENV": "production", + }, + Timeout: faasclient.Duration(30 * time.Second), + Memory: 256, + Owner: faasclient.Owner{ + Type: faasclient.OwnerTypeIndividual, + Name: "Example User", + Owner: "admin@example.com", + }, + } + + function, err := client.CreateFunction(ctx, createReq) + if err != nil { + log.Fatalf("Failed to create function: %v", err) + } + log.Printf("Created function: %s (ID: %s)", function.Name, function.ID) + + // Deploy the function + log.Println("Deploying function...") + deployResp, err := client.DeployFunction(ctx, function.ID, nil) + if err != nil { + log.Fatalf("Failed to deploy function: %v", err) + } + log.Printf("Deployment status: %s - %s", deployResp.Status, deployResp.Message) + + // Execute function synchronously + log.Println("Executing function synchronously...") + input := json.RawMessage(`{"name": "Skybridge"}`) + executeReq := &faasclient.ExecuteFunctionRequest{ + FunctionID: function.ID, + Input: input, + Async: false, + } + + execResp, err := client.ExecuteFunction(ctx, executeReq) + if err != nil { + log.Fatalf("Failed to execute function: %v", err) + } + log.Printf("Sync execution result: %s", string(execResp.Output)) + log.Printf("Duration: %v, Memory used: %d MB", execResp.Duration, execResp.MemoryUsed) + + // Execute function asynchronously + log.Println("Invoking function asynchronously...") + asyncResp, err := client.InvokeFunction(ctx, function.ID, input) + if err != nil { + log.Fatalf("Failed to invoke function: %v", err) + } + log.Printf("Async execution ID: %s, Status: %s", asyncResp.ExecutionID, asyncResp.Status) + + // Wait a moment for async execution to complete + time.Sleep(2 * time.Second) + + // Get execution details + log.Println("Getting execution details...") + execution, err := client.GetExecution(ctx, asyncResp.ExecutionID) + if err != nil { + log.Fatalf("Failed to get execution: %v", err) + } + log.Printf("Execution status: %s", execution.Status) + if execution.Status == faasclient.StatusCompleted { + log.Printf("Async execution result: %s", string(execution.Output)) + log.Printf("Duration: %v, Memory used: %d MB", execution.Duration, execution.MemoryUsed) + } + + // Get execution logs + log.Println("Getting execution logs...") + logs, err := client.GetExecutionLogs(ctx, asyncResp.ExecutionID) + if err != nil { + log.Printf("Failed to get logs: %v", err) + } else { + log.Printf("Logs (%d entries):", len(logs.Logs)) + for _, logLine := range logs.Logs { + log.Printf(" %s", logLine) + } + } + + // List functions + log.Println("Listing functions...") + listResp, err := client.ListFunctions(ctx, "example-app", 10, 0) + if err != nil { + log.Fatalf("Failed to list functions: %v", err) + } + log.Printf("Found %d functions:", len(listResp.Functions)) + for _, fn := range listResp.Functions { + log.Printf(" - %s (%s) - Runtime: %s", fn.Name, fn.ID, fn.Runtime) + } + + // List executions for this function + log.Println("Listing executions...") + execListResp, err := client.ListExecutions(ctx, &function.ID, 10, 0) + if err != nil { + log.Fatalf("Failed to list executions: %v", err) + } + log.Printf("Found %d executions:", len(execListResp.Executions)) + for _, exec := range execListResp.Executions { + status := string(exec.Status) + log.Printf(" - %s: %s (Duration: %v)", exec.ID, status, exec.Duration) + } + + // Clean up - delete the function + log.Println("Cleaning up...") + err = client.DeleteFunction(ctx, function.ID) + if err != nil { + log.Fatalf("Failed to delete function: %v", err) + } + log.Printf("Deleted function: %s", function.ID) + + log.Println("Example completed successfully!") +} + +// Helper function to create a UUID from string (for testing) +func mustParseUUID(s string) uuid.UUID { + id, err := uuid.Parse(s) + if err != nil { + panic(err) + } + return id +} \ No newline at end of file diff --git a/faas-client/go.mod b/faas-client/go.mod new file mode 100644 index 0000000..d542cec --- /dev/null +++ b/faas-client/go.mod @@ -0,0 +1,7 @@ +module github.com/RyanCopley/skybridge/faas-client + +go 1.23 + +require ( + github.com/google/uuid v1.6.0 +) \ No newline at end of file diff --git a/faas-client/types.go b/faas-client/types.go new file mode 100644 index 0000000..f775b1f --- /dev/null +++ b/faas-client/types.go @@ -0,0 +1,191 @@ +package faasclient + +import ( + "encoding/json" + "time" + + "github.com/google/uuid" +) + +// RuntimeType represents supported function runtimes +type RuntimeType string + +const ( + RuntimeNodeJS18 RuntimeType = "nodejs18" + RuntimePython39 RuntimeType = "python3.9" + RuntimeGo120 RuntimeType = "go1.20" + RuntimeCustom RuntimeType = "custom" +) + +// ExecutionStatus represents the status of function execution +type ExecutionStatus string + +const ( + StatusPending ExecutionStatus = "pending" + StatusRunning ExecutionStatus = "running" + StatusCompleted ExecutionStatus = "completed" + StatusFailed ExecutionStatus = "failed" + StatusTimeout ExecutionStatus = "timeout" + StatusCanceled ExecutionStatus = "canceled" +) + +// OwnerType represents the type of owner +type OwnerType string + +const ( + OwnerTypeIndividual OwnerType = "individual" + OwnerTypeTeam OwnerType = "team" +) + +// Owner represents ownership information +type Owner struct { + Type OwnerType `json:"type"` + Name string `json:"name"` + Owner string `json:"owner"` +} + +// Duration wraps time.Duration for JSON marshaling +type Duration time.Duration + +func (d Duration) MarshalJSON() ([]byte, error) { + return json.Marshal(time.Duration(d).String()) +} + +func (d *Duration) UnmarshalJSON(b []byte) error { + var v interface{} + if err := json.Unmarshal(b, &v); err != nil { + return err + } + switch value := v.(type) { + case float64: + *d = Duration(time.Duration(value)) + return nil + case string: + tmp, err := time.ParseDuration(value) + if err != nil { + return err + } + *d = Duration(tmp) + return nil + default: + return nil + } +} + +// FunctionDefinition represents a serverless function +type FunctionDefinition struct { + ID uuid.UUID `json:"id"` + Name string `json:"name"` + AppID string `json:"app_id"` + Runtime RuntimeType `json:"runtime"` + Image string `json:"image"` + Handler string `json:"handler"` + Code string `json:"code,omitempty"` + Environment map[string]string `json:"environment,omitempty"` + Timeout Duration `json:"timeout"` + Memory int `json:"memory"` + Owner Owner `json:"owner"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// FunctionExecution represents a function execution +type FunctionExecution struct { + ID uuid.UUID `json:"id"` + FunctionID uuid.UUID `json:"function_id"` + Status ExecutionStatus `json:"status"` + Input json.RawMessage `json:"input,omitempty"` + Output json.RawMessage `json:"output,omitempty"` + Error string `json:"error,omitempty"` + Duration time.Duration `json:"duration"` + MemoryUsed int `json:"memory_used"` + Logs []string `json:"logs,omitempty"` + ContainerID string `json:"container_id,omitempty"` + ExecutorID string `json:"executor_id"` + CreatedAt time.Time `json:"created_at"` + StartedAt *time.Time `json:"started_at,omitempty"` + CompletedAt *time.Time `json:"completed_at,omitempty"` +} + +// CreateFunctionRequest represents a request to create a new function +type CreateFunctionRequest struct { + Name string `json:"name"` + AppID string `json:"app_id"` + Runtime RuntimeType `json:"runtime"` + Image string `json:"image"` + Handler string `json:"handler"` + Code string `json:"code,omitempty"` + Environment map[string]string `json:"environment,omitempty"` + Timeout Duration `json:"timeout"` + Memory int `json:"memory"` + Owner Owner `json:"owner"` +} + +// UpdateFunctionRequest represents a request to update an existing function +type UpdateFunctionRequest struct { + Name *string `json:"name,omitempty"` + Runtime *RuntimeType `json:"runtime,omitempty"` + Image *string `json:"image,omitempty"` + Handler *string `json:"handler,omitempty"` + Code *string `json:"code,omitempty"` + Environment map[string]string `json:"environment,omitempty"` + Timeout *Duration `json:"timeout,omitempty"` + Memory *int `json:"memory,omitempty"` + Owner *Owner `json:"owner,omitempty"` +} + +// ExecuteFunctionRequest represents a request to execute a function +type ExecuteFunctionRequest struct { + FunctionID uuid.UUID `json:"function_id"` + Input json.RawMessage `json:"input,omitempty"` + Async bool `json:"async,omitempty"` +} + +// ExecuteFunctionResponse represents a response for function execution +type ExecuteFunctionResponse struct { + ExecutionID uuid.UUID `json:"execution_id"` + Status ExecutionStatus `json:"status"` + Output json.RawMessage `json:"output,omitempty"` + Error string `json:"error,omitempty"` + Duration time.Duration `json:"duration,omitempty"` + MemoryUsed int `json:"memory_used,omitempty"` +} + +// DeployFunctionRequest represents a request to deploy a function +type DeployFunctionRequest struct { + FunctionID uuid.UUID `json:"function_id"` + Force bool `json:"force,omitempty"` +} + +// DeployFunctionResponse represents a response for function deployment +type DeployFunctionResponse struct { + Status string `json:"status"` + Message string `json:"message,omitempty"` + Image string `json:"image,omitempty"` + ImageID string `json:"image_id,omitempty"` +} + +// ListFunctionsResponse represents the response for listing functions +type ListFunctionsResponse struct { + Functions []FunctionDefinition `json:"functions"` + Limit int `json:"limit"` + Offset int `json:"offset"` +} + +// ListExecutionsResponse represents the response for listing executions +type ListExecutionsResponse struct { + Executions []FunctionExecution `json:"executions"` + Limit int `json:"limit"` + Offset int `json:"offset"` +} + +// GetLogsResponse represents the response for getting execution logs +type GetLogsResponse struct { + Logs []string `json:"logs"` +} + +// GetRunningExecutionsResponse represents the response for getting running executions +type GetRunningExecutionsResponse struct { + Executions []FunctionExecution `json:"executions"` + Count int `json:"count"` +} \ No newline at end of file