package closure import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "os" "path/filepath" "strconv" "time" ) type Client struct { endpoint string http *http.Client } func NewClientFromEnv() *Client { endpoint := os.Getenv("CLOSURE_ENDPOINT") if endpoint == "" { endpoint = "http://closure-svc:8080/compile" } return &Client{ endpoint: endpoint, http: &http.Client{ Timeout: 70 * time.Second, }, } } func (c *Client) Compile(ctx context.Context, reqBody CompileRequest) ([]byte, error) { body, err := json.Marshal(reqBody) if err != nil { return nil, err } req, err := http.NewRequestWithContext( ctx, http.MethodPost, c.endpoint, bytes.NewReader(body), ) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") resp, err := c.http.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode/100 != 2 { body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096)) return nil, fmt.Errorf("closure failed: %s: %s", resp.Status, body) } return io.ReadAll(resp.Body) } func (c *Client) DebugPrintCurl(ctx context.Context, reqBody CompileRequest) { if os.Getenv("CLOSURE_DEBUG_CURL") == "" { return } body, err := json.MarshalIndent(reqBody, "", " ") if err != nil { fmt.Fprintf(os.Stderr, "closure debug: marshal payload failed: %v\n", err) return } tmpDir := os.Getenv("CLOSURE_DEBUG_DIR") if tmpDir == "" { tmpDir = os.TempDir() } path := filepath.Join( tmpDir, fmt.Sprintf("closure-request-%d.json", time.Now().UnixNano()), ) if err := os.WriteFile(path, body, 0o600); err != nil { fmt.Fprintf(os.Stderr, "closure debug: write payload failed: %v\n", err) return } fmt.Fprintf( os.Stderr, "closure debug curl:\n curl -v -X POST %s -H 'Content-Type: application/json' --data-binary @%s\n", shellQuote(c.endpoint), shellQuote(path), ) if deadline, ok := ctx.Deadline(); ok { fmt.Fprintf(os.Stderr, "closure debug deadline: %s\n", deadline.Format(time.RFC3339Nano)) } } func shellQuote(s string) string { return strconv.Quote(s) }