package notify import ( "bytes" "context" "errors" "fmt" "io" "net/http" "strings" "time" ) const webhookTimeOut = 5000 * time.Millisecond // WebhookParams contain settings for webhook notifications type WebhookParams struct { Timeout time.Duration Headers []string // headers in format "header:value" } // Webhook notifications client type Webhook struct { WebhookParams webhookClient webhookClient } // webhookClient defines an interface of client for webhook type webhookClient interface { Do(*http.Request) (*http.Response, error) } // NewWebhook makes Webhook func NewWebhook(params WebhookParams) *Webhook { res := &Webhook{WebhookParams: params} if res.Timeout == 0 { res.Timeout = webhookTimeOut } res.webhookClient = &http.Client{Timeout: res.Timeout} return res } // Send sends Webhook notification. Destination field is expected to have http:// or https:// schema. // // Example: // // - https://example.com/webhook func (wh *Webhook) Send(ctx context.Context, destination, text string) error { payload := bytes.NewBufferString(text) httpReq, err := http.NewRequestWithContext(ctx, "POST", destination, payload) if err != nil { return fmt.Errorf("unable to create webhook request: %w", err) } for _, h := range wh.Headers { elems := strings.Split(h, ":") if len(elems) != 2 { continue } httpReq.Header.Set(strings.TrimSpace(elems[0]), strings.TrimSpace(elems[1])) } resp, err := wh.webhookClient.Do(httpReq) if err != nil { return fmt.Errorf("webhook request failed: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { errMsg := fmt.Sprintf("webhook request failed with non-OK status code: %d", resp.StatusCode) respBody, e := io.ReadAll(resp.Body) if e != nil { return errors.New(errMsg) } return fmt.Errorf("%s, body: %s", errMsg, respBody) } return nil } // Schema returns schema prefix supported by this client func (wh *Webhook) Schema() string { return "http" } // String describes the webhook instance func (wh *Webhook) String() string { str := fmt.Sprintf("webhook notification with timeout %s", wh.Timeout) if wh.Headers != nil { str += fmt.Sprintf(" and headers %v", wh.Headers) } return str }