dasrick/go-teams-notify

View on GitHub
send.go

Summary

Maintainability
A
0 mins
Test Coverage
package goteamsnotify

import (
    "bytes"
    "encoding/json"
    "errors"
    "fmt"
    "net/http"
    "net/url"
    "strings"
    "time"
)

// Known webhook URL prefixes for submitting messages to Microsoft Teams
const (
    WebhookURLOfficecomPrefix = "https://outlook.office.com"
    WebhookURLOffice365Prefix = "https://outlook.office365.com"
)

// API - interface of MS Teams notify
type API interface {
    Send(webhookURL string, webhookMessage MessageCard) error
}

type teamsClient struct {
    httpClient *http.Client
}

// NewClient - create a brand new client for MS Teams notify
func NewClient() API {
    client := teamsClient{
        httpClient: &http.Client{
            Timeout: 5 * time.Second,
        },
    }
    return &client
}

// Send - will post a notification to MS Teams webhook URL
func (c teamsClient) Send(webhookURL string, webhookMessage MessageCard) error {
    // Validate input data
    if valid, err := IsValidInput(webhookMessage, webhookURL); !valid {
        return err
    }

    // prepare message
    webhookMessageByte, _ := json.Marshal(webhookMessage)
    webhookMessageBuffer := bytes.NewBuffer(webhookMessageByte)

    // prepare request (error not possible)
    req, _ := http.NewRequest(http.MethodPost, webhookURL, webhookMessageBuffer)
    req.Header.Add("Content-Type", "application/json;charset=utf-8")

    // do the request
    res, err := c.httpClient.Do(req)
    if err != nil {
        return err
    }
    if res.StatusCode >= 299 {
        err = errors.New("error on notification: " + res.Status)
        return err
    }

    return nil
}

// helper --------------------------------------------------------------------------------------------------------------

// IsValidInput is a validation "wrapper" function. This function is intended
// to run current validation checks and offer easy extensibility for future
// validation requirements.
func IsValidInput(webhookMessage MessageCard, webhookURL string) (bool, error) {
    // validate url
    if valid, err := IsValidWebhookURL(webhookURL); !valid {
        return false, err
    }

    // validate message
    if valid, err := IsValidMessageCard(webhookMessage); !valid {
        return false, err
    }

    return true, nil
}

// IsValidWebhookURL performs validation checks on the webhook URL used to
// submit messages to Microsoft Teams.
func IsValidWebhookURL(webhookURL string) (bool, error) {

    switch {
    case strings.HasPrefix(webhookURL, WebhookURLOfficecomPrefix):
    case strings.HasPrefix(webhookURL, WebhookURLOffice365Prefix):
    default:
        u, err := url.Parse(webhookURL)
        if err != nil {
            return false, fmt.Errorf(
                "unable to parse webhook URL %q: %v",
                webhookURL,
                err,
            )
        }
        userProvidedWebhookURLPrefix := u.Scheme + "://" + u.Host

        return false, fmt.Errorf(
            "webhook URL does not contain expected prefix; got %q, expected one of %q or %q",
            userProvidedWebhookURLPrefix,
            WebhookURLOfficecomPrefix,
            WebhookURLOffice365Prefix,
        )
    }

    return true, nil
}

// IsValidMessageCard performs validation/checks for known issues with
// MessardCard values.
func IsValidMessageCard(webhookMessage MessageCard) (bool, error) {
    if (webhookMessage.Text == "") && (webhookMessage.Summary == "") {
        // This scenario results in:
        // 400 Bad Request
        // Summary or Text is required.
        return false, fmt.Errorf("invalid message card: summary or text field is required")
    }

    return true, nil
}