github

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 17, 2026 License: GPL-3.0 Imports: 15 Imported by: 0

README

GitHub Client Package

A generic, reusable GitHub client for interacting with the GitHub API. This package abstracts the underlying google/go-github SDK and exposes only domain types to prevent SDK leakage.

Features

  • No SDK Leakage: All public APIs return domain types, never SDK types
  • Context-Aware: All operations support context cancellation and timeout
  • Structured Logging: Integrated with pkg/logger for consistent logging
  • HTTP Client Integration: Uses pkg/httpclient for retry, backoff, and authentication
  • Webhook Support: Parse and validate GitHub webhooks with signature verification
  • Mock Support: Includes mock client for testing without real API calls
  • Table-Driven Tests: Comprehensive test coverage with clear examples

Installation

go get github.com/google/go-github/v67/github

Usage

Creating a Client
import (
    "github.com/grhili/cd-operator/pkg/github"
    "github.com/grhili/cd-operator/pkg/httpclient"
    "github.com/grhili/cd-operator/pkg/logger"
)

// Create HTTP client with authentication
token := "ghp_xxxxxxxxxxxx"
httpClient := httpclient.NewClient().
    WithAuthToken(httpclient.Token(token))

// Create logger
log, _ := logger.New(logger.Config{
    Level:  "info",
    Format: "json",
})

// Create GitHub client
cfg := github.ClientConfig{
    BaseURL:    "https://api.github.com",
    HTTPClient: httpClient,
    Logger:     log,
}
client := github.NewClient(cfg)
Listing Pull Requests
ctx := context.Background()
repo := github.Repository{
    Owner: "grhili",
    Name:  "cd-operator",
}

prs, err := client.ListPRs(ctx, repo)
if err != nil {
    log.Error("failed to list PRs", zap.Error(err))
    return
}

for _, pr := range prs {
    log.Info("found PR",
        zap.Int("number", pr.Number),
        zap.String("title", pr.Title),
        zap.String("state", string(pr.State)))
}
Getting a Specific Pull Request
pr, err := client.GetPR(ctx, repo, 42)
if err != nil {
    log.Error("failed to get PR", zap.Error(err))
    return
}

log.Info("PR details",
    zap.Int("number", pr.Number),
    zap.String("title", pr.Title),
    zap.String("head_sha", pr.HeadSHA),
    zap.String("base_branch", pr.BaseBranch))
Getting PR Files
files, err := client.GetPRFiles(ctx, repo, 42)
if err != nil {
    log.Error("failed to get PR files", zap.Error(err))
    return
}

for _, file := range files {
    log.Info("file changed", zap.String("path", file.Path))
}
Merging a Pull Request
err := client.MergePR(ctx, repo, 42, "Merge pull request #42")
if err != nil {
    log.Error("failed to merge PR", zap.Error(err))
    return
}

log.Info("PR merged successfully")
Managing Labels
// Add a label
err := client.AddLabel(ctx, repo, 42, "bug")
if err != nil {
    log.Error("failed to add label", zap.Error(err))
    return
}

// Remove a label
err = client.RemoveLabel(ctx, repo, 42, "wontfix")
if err != nil {
    log.Error("failed to remove label", zap.Error(err))
    return
}
Parsing Webhooks
import (
    "net/http"
)

func webhookHandler(w http.ResponseWriter, req *http.Request) {
    // Parse and validate webhook
    event, err := github.ParseWebhook(req, "webhook-secret")
    if err != nil {
        http.Error(w, "Invalid webhook", http.StatusBadRequest)
        return
    }

    // Handle different event types
    switch payload := event.Payload.(type) {
    case *github.PullRequestWebhookPayload:
        log.Info("PR event received",
            zap.String("action", payload.Action),
            zap.Int("pr_number", payload.PullRequest.Number),
            zap.String("title", payload.PullRequest.Title))

        // Process pull request event
        handlePullRequest(payload)

    default:
        log.Info("unhandled event type", zap.String("type", event.Type))
    }

    w.WriteHeader(http.StatusOK)
}

Testing

Using the Mock Client
import (
    "testing"
    "github.com/grhili/cd-operator/pkg/github"
    "github.com/grhili/cd-operator/pkg/logger"
)

func TestMyFunction(t *testing.T) {
    log := logger.NewFakeLogger()
    mock := github.NewMockClient(log)

    // Override specific methods
    mock.GetPRFunc = func(ctx context.Context, repo github.Repository, number int) (*github.PullRequest, error) {
        return &github.PullRequest{
            Number: number,
            Title:  "Test PR",
            State:  github.PullRequestStateOpen,
        }, nil
    }

    // Use mock in tests
    ctx := context.Background()
    pr, err := mock.GetPR(ctx, github.Repository{Owner: "test", Name: "repo"}, 42)
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }

    if pr.Number != 42 {
        t.Errorf("expected PR #42, got #%d", pr.Number)
    }
}
Running Tests
# Run all tests
go test ./pkg/github/...

# Run tests with coverage
go test ./pkg/github/... -cover

# Run specific test
go test ./pkg/github/... -run TestToPR

Architecture

Package Structure
pkg/github/
├── types.go         # Domain types (PR, Repository, File, Label)
├── adapter.go       # SDK → domain conversion
├── client.go        # Client implementation
├── webhook.go       # Webhook parsing and validation
├── mock.go          # Mock client for testing
├── adapter_test.go  # Adapter tests
├── webhook_test.go  # Webhook tests
├── mock_test.go     # Mock client tests
└── example_test.go  # Usage examples
Domain Types
  • PullRequest: Represents a GitHub pull request with all essential fields
  • Repository: Repository identifier (owner + name)
  • File: File in a repository with path and content
  • Label: GitHub label with name and color
  • PullRequestState: Enum for PR states (open, closed, merged)
Key Design Decisions
  1. No SDK Leakage: All public methods return domain types, never *github.PullRequest or other SDK types
  2. Value vs Pointer Types:
    • Repository and Label are value types (small, immutable)
    • PullRequest and File are pointer types (can be large, often mutated)
  3. Context First: All methods take context.Context as first parameter
  4. Structured Logging: Uses injected Logger interface for consistent logging
  5. HTTP Client Abstraction: Uses pkg/httpclient for retry and authentication

Configuration

Custom Base URL (GitHub Enterprise)
cfg := github.ClientConfig{
    BaseURL:    "https://github.company.com/api/v3",
    HTTPClient: httpClient,
    Logger:     log,
}
client := github.NewClient(cfg)
Custom HTTP Client Options
httpClient := httpclient.NewClient().
    WithAuthToken(httpclient.Token(token)).
    WithRetry(httpclient.RetryConfig{
        MaxRetries: httpclient.RetryCount(5),
        WaitMin:    2 * time.Second,
        WaitMax:    30 * time.Second,
    }).
    WithTimeout(httpclient.TimeoutConfig{
        HTTPTimeout: 60 * time.Second,
    })

Error Handling

All methods return descriptive errors with context:

pr, err := client.GetPR(ctx, repo, 42)
if err != nil {
    // Error includes context about what failed
    // Example: "failed to get pull request #42: 404 Not Found"
    log.Error("operation failed", zap.Error(err))
    return err
}

Best Practices

  1. Always pass context: Use context.Background() or request context
  2. Configure timeouts: Set reasonable HTTP timeouts via httpclient
  3. Handle rate limits: GitHub API has rate limits; implement backoff
  4. Use structured logging: Pass meaningful fields to logger
  5. Test with mocks: Use MockClient for unit tests
  6. Validate webhooks: Always verify webhook signatures in production

License

This package is part of the cd-operator project.

Documentation

Overview

Package github provides a generic, reusable GitHub client for interacting with the GitHub API. It abstracts the underlying SDK and exposes only domain types to prevent SDK leakage.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client wraps the GitHub SDK and exposes domain-specific operations. This is a pointer type (holds state, manages HTTP client lifecycle).

func NewClient

func NewClient(cfg ClientConfig) *Client

NewClient creates a new GitHub client with the provided configuration. The HTTPClient should be configured with authentication (e.g., via AuthTransport).

Example:

httpClient := httpclient.NewClient().WithToken("ghp_...")
cfg := github.ClientConfig{
    BaseURL:    "https://api.github.com",
    HTTPClient: httpClient,
    Logger:     log,
}
client := github.NewClient(cfg)
Example

ExampleNewClient demonstrates how to create a GitHub client with authentication.

package main

import (
	"github.com/grhili/cd-operator/pkg/github"
	"github.com/grhili/cd-operator/pkg/httpclient"
	"github.com/grhili/cd-operator/pkg/logger"
)

func main() {
	// Create HTTP client with GitHub token
	token := "ghp_xxxxxxxxxxxx"
	httpClient := httpclient.NewClient().
		WithAuthToken(httpclient.Token(token))

	// Create logger
	log, _ := logger.New(logger.Config{
		Level:  "info",
		Format: "json",
	})

	// Create GitHub client
	cfg := github.ClientConfig{
		BaseURL:    "https://api.github.com",
		HTTPClient: httpClient,
		Logger:     log,
	}
	client := github.NewClient(cfg)

	_ = client // Use client for API operations
}

func (*Client) AddLabel

func (c *Client) AddLabel(ctx context.Context, repo Repository, number int, label string) error

AddLabel adds a label to a pull request. Creates the label if it doesn't exist in the repository.

Example

ExampleClient_AddLabel demonstrates how to add a label to a pull request.

package main

import (
	"context"
	"fmt"

	"github.com/grhili/cd-operator/pkg/github"
)

const (
	testRepoOwner = "grhili"
	testRepoName  = "cd-operator"
)

func main() {
	// Setup (omitted for brevity)
	var client *github.Client
	ctx := context.Background()

	repo := github.Repository{
		Owner: testRepoOwner,
		Name:  testRepoName,
	}

	err := client.AddLabel(ctx, repo, 42, "bug")
	if err != nil {
		fmt.Printf("failed to add label: %v\n", err)
		return
	}

	fmt.Println("Label added successfully")
}

func (*Client) AddLabelToPRsBatched

func (c *Client) AddLabelToPRsBatched(ctx context.Context, repo Repository, prNumbers []int, label string) error

AddLabelToPRsBatched adds a label to multiple PRs in a single batch operation. This is much more efficient than calling AddLabel for each PR individually. For N PRs, this makes 1 API call instead of N calls.

func (*Client) GetFile

func (c *Client) GetFile(ctx context.Context, repo Repository, path string, ref string) (*File, error)

GetFile retrieves a file from a repository at a specific commit/branch/tag reference. Returns the file content as a domain File object.

func (*Client) GetPR

func (c *Client) GetPR(ctx context.Context, repo Repository, number int) (*PullRequest, error)

GetPR retrieves a specific pull request by number. Returns nil if the PR does not exist.

Example

ExampleClient_GetPR demonstrates how to get a specific pull request.

package main

import (
	"context"
	"fmt"

	"github.com/grhili/cd-operator/pkg/github"
)

const (
	testRepoOwner = "grhili"
	testRepoName  = "cd-operator"
	fmtPRLog      = "PR #%d: %s\n"
)

func main() {
	// Setup (omitted for brevity)
	var client *github.Client
	ctx := context.Background()

	repo := github.Repository{
		Owner: testRepoOwner,
		Name:  testRepoName,
	}

	pr, err := client.GetPR(ctx, repo, 42)
	if err != nil {
		fmt.Printf("failed to get PR: %v\n", err)
		return
	}

	fmt.Printf(fmtPRLog, pr.Number, pr.Title)
	fmt.Printf("State: %s\n", pr.State)
	fmt.Printf("Head SHA: %s\n", pr.HeadSHA)
	fmt.Printf("Base Branch: %s\n", pr.BaseBranch)
}

func (*Client) GetPRFiles

func (c *Client) GetPRFiles(ctx context.Context, repo Repository, number int) ([]File, error)

GetPRFiles retrieves all files changed in a pull request. Returns a slice of File domain objects with their content.

func (*Client) ListFiles

func (c *Client) ListFiles(ctx context.Context, repo Repository, path string, ref string) ([]string, error)

ListFiles lists all files in a directory at a specific commit/branch/tag reference. Returns a slice of file paths (not content).

func (*Client) ListPRFiles

func (c *Client) ListPRFiles(ctx context.Context, repo Repository, number int) ([]PRFile, error)

ListPRFiles retrieves all files modified in a pull request. Returns a slice of PRFile domain objects.

func (*Client) ListPRs

func (c *Client) ListPRs(ctx context.Context, repo Repository) ([]PullRequest, error)

ListPRs retrieves all open pull requests for the specified repository. Returns a slice of PullRequest domain objects, never GitHub SDK types.

Example

ExampleClient_ListPRs demonstrates how to list pull requests.

package main

import (
	"context"
	"fmt"

	"github.com/grhili/cd-operator/pkg/github"
)

const (
	testRepoOwner = "grhili"
	testRepoName  = "cd-operator"
	fmtPRLog      = "PR #%d: %s\n"
)

func main() {
	// Setup (omitted for brevity)
	var client *github.Client
	ctx := context.Background()

	// List all open pull requests
	repo := github.Repository{
		Owner: testRepoOwner,
		Name:  testRepoName,
	}

	prs, err := client.ListPRs(ctx, repo)
	if err != nil {
		fmt.Printf("failed to list PRs: %v\n", err)
		return
	}

	for _, pr := range prs {
		fmt.Printf(fmtPRLog, pr.Number, pr.Title)
	}
}

func (*Client) ListReviews

func (c *Client) ListReviews(ctx context.Context, repo Repository, number int) ([]Review, error)

ListReviews retrieves all reviews for a pull request. Returns a slice of Review domain objects.

func (*Client) ListStatuses

func (c *Client) ListStatuses(ctx context.Context, repo Repository, ref string) ([]Status, error)

ListStatuses retrieves all status checks for a given ref (commit SHA or branch name). Returns a slice of Status domain objects.

func (*Client) MergePR

func (c *Client) MergePR(ctx context.Context, repo Repository, number int, commitMsg string) error

MergePR merges a pull request using the specified commit message. Returns an error if the merge fails (e.g., conflicts, not mergeable).

Example

ExampleClient_MergePR demonstrates how to merge a pull request.

package main

import (
	"context"
	"fmt"

	"github.com/grhili/cd-operator/pkg/github"
)

const (
	testRepoOwner = "grhili"
	testRepoName  = "cd-operator"
)

func main() {
	// Setup (omitted for brevity)
	var client *github.Client
	ctx := context.Background()

	repo := github.Repository{
		Owner: testRepoOwner,
		Name:  testRepoName,
	}

	err := client.MergePR(ctx, repo, 42, "Merge pull request #42")
	if err != nil {
		fmt.Printf("failed to merge PR: %v\n", err)
		return
	}

	fmt.Println("PR merged successfully")
}

func (*Client) RemoveLabel

func (c *Client) RemoveLabel(ctx context.Context, repo Repository, number int, label string) error

RemoveLabel removes a label from a pull request. Returns an error if the label doesn't exist on the PR.

func (*Client) RemoveLabelFromPRsBatched

func (c *Client) RemoveLabelFromPRsBatched(ctx context.Context, repo Repository, prNumbers []int, label string) error

RemoveLabelFromPRsBatched removes a label from multiple PRs in a single batch operation. This is much more efficient than calling RemoveLabel for each PR individually. For N PRs, this makes 1 API call instead of N calls.

func (*Client) RemoveLabelsWithPrefixBatched

func (c *Client) RemoveLabelsWithPrefixBatched(ctx context.Context, repo Repository, prNumbers []int, prefix string, excludeLabels []string) error

RemoveLabelsWithPrefixBatched removes all labels with a given prefix from multiple PRs. For example, prefix "deployment:" removes "deployment:success", "deployment:failure", etc. excludeLabels specifies labels that should not be removed.

type ClientConfig

type ClientConfig struct {
	// BaseURL is the GitHub API base URL (default: https://api.github.com).
	BaseURL string

	// HTTPClient is the HTTP client to use for requests.
	HTTPClient *httpclient.Client

	// Logger is the logger instance for structured logging.
	Logger Logger
}

ClientConfig holds configuration for creating a GitHub client. This is a value type (immutable configuration).

type File

type File struct {
	// Path is the file path within the repository.
	Path string

	// Content is the file content (base64 decoded).
	Content string
}

File represents a file in a GitHub repository. This is a pointer type (Content can be large).

type InstrumentedClient

type InstrumentedClient struct {
	// contains filtered or unexported fields
}

InstrumentedClient wraps a GitHub Client with Prometheus metrics instrumentation. All operations are recorded with timing, success/failure counts, and rate limit tracking.

This follows the decorator pattern to add observability without modifying the core client.

func NewInstrumentedClient

func NewInstrumentedClient(client *Client, metrics *observability.ComponentMetrics) *InstrumentedClient

NewInstrumentedClient creates a new GitHub client with metrics instrumentation.

Example:

client := github.NewClient(cfg)
instrumentedClient := github.NewInstrumentedClient(client, metrics)

func (*InstrumentedClient) AddLabel

func (ic *InstrumentedClient) AddLabel(ctx context.Context, repo Repository, number int, label string) error

AddLabel adds a label to a pull request with metrics recording.

func (*InstrumentedClient) AddLabelToPRsBatched

func (ic *InstrumentedClient) AddLabelToPRsBatched(ctx context.Context, repo Repository, prNumbers []int, label string) error

AddLabelToPRsBatched adds a label to multiple PRs with metrics recording.

func (*InstrumentedClient) GetFile

func (ic *InstrumentedClient) GetFile(ctx context.Context, repo Repository, path string, ref string) (*File, error)

GetFile retrieves a file from a repository with metrics recording.

func (*InstrumentedClient) GetPR

func (ic *InstrumentedClient) GetPR(ctx context.Context, repo Repository, number int) (*PullRequest, error)

GetPR retrieves a specific pull request with metrics recording.

func (*InstrumentedClient) GetPRFiles

func (ic *InstrumentedClient) GetPRFiles(ctx context.Context, repo Repository, number int) ([]File, error)

GetPRFiles retrieves all files changed in a pull request with metrics recording.

func (*InstrumentedClient) ListFiles

func (ic *InstrumentedClient) ListFiles(ctx context.Context, repo Repository, path string, ref string) ([]string, error)

ListFiles lists all files in a directory with metrics recording.

func (*InstrumentedClient) ListPRFiles

func (ic *InstrumentedClient) ListPRFiles(ctx context.Context, repo Repository, number int) ([]PRFile, error)

ListPRFiles retrieves all files modified in a pull request with metrics recording.

func (*InstrumentedClient) ListPRs

func (ic *InstrumentedClient) ListPRs(ctx context.Context, repo Repository) ([]PullRequest, error)

ListPRs retrieves all open pull requests with metrics recording.

func (*InstrumentedClient) ListReviews

func (ic *InstrumentedClient) ListReviews(ctx context.Context, repo Repository, number int) ([]Review, error)

ListReviews retrieves all reviews for a pull request with metrics recording.

func (*InstrumentedClient) ListStatuses

func (ic *InstrumentedClient) ListStatuses(ctx context.Context, repo Repository, ref string) ([]Status, error)

ListStatuses retrieves all status checks for a given ref with metrics recording.

func (*InstrumentedClient) MergePR

func (ic *InstrumentedClient) MergePR(ctx context.Context, repo Repository, number int, commitMsg string) error

MergePR merges a pull request with metrics recording.

func (*InstrumentedClient) RemoveLabel

func (ic *InstrumentedClient) RemoveLabel(ctx context.Context, repo Repository, number int, label string) error

RemoveLabel removes a label from a pull request with metrics recording.

func (*InstrumentedClient) RemoveLabelFromPRsBatched

func (ic *InstrumentedClient) RemoveLabelFromPRsBatched(ctx context.Context, repo Repository, prNumbers []int, label string) error

RemoveLabelFromPRsBatched removes a label from multiple PRs with metrics recording.

func (*InstrumentedClient) RemoveLabelsWithPrefixBatched

func (ic *InstrumentedClient) RemoveLabelsWithPrefixBatched(ctx context.Context, repo Repository, prNumbers []int, prefix string, excludeLabels []string) error

RemoveLabelsWithPrefixBatched removes all labels with a given prefix from multiple PRs with metrics recording.

type Label

type Label struct {
	// Name is the label name.
	Name string

	// Color is the label color (hex code without #).
	Color string
}

Label represents a GitHub label. This is a value type (small, immutable).

type Logger

type Logger interface {
	Debug(msg string, fields ...zap.Field)
	Info(msg string, fields ...zap.Field)
	Warn(msg string, fields ...zap.Field)
	Error(msg string, fields ...zap.Field)
}

Logger defines the logging interface used by the GitHub client.

type MockClient

type MockClient struct {
	// ListPRsFunc is the mock function for ListPRs.
	ListPRsFunc func(ctx context.Context, repo Repository) ([]PullRequest, error)

	// GetPRFunc is the mock function for GetPR.
	GetPRFunc func(ctx context.Context, repo Repository, number int) (*PullRequest, error)

	// GetPRFilesFunc is the mock function for GetPRFiles.
	GetPRFilesFunc func(ctx context.Context, repo Repository, number int) ([]File, error)

	// MergePRFunc is the mock function for MergePR.
	MergePRFunc func(ctx context.Context, repo Repository, number int, commitMsg string) error

	// AddLabelFunc is the mock function for AddLabel.
	AddLabelFunc func(ctx context.Context, repo Repository, number int, label string) error

	// RemoveLabelFunc is the mock function for RemoveLabel.
	RemoveLabelFunc func(ctx context.Context, repo Repository, number int, label string) error
	// contains filtered or unexported fields
}

MockClient is a mock implementation of the GitHub client for testing. It allows tests to simulate GitHub API responses without making real HTTP calls.

func NewMockClient

func NewMockClient(log Logger) *MockClient

NewMockClient creates a new mock GitHub client with no-op implementations. Tests should override specific functions as needed.

Example

ExampleMockClient demonstrates how to use the mock client in tests.

package main

import (
	"context"
	"fmt"

	"github.com/grhili/cd-operator/pkg/github"
	"github.com/grhili/cd-operator/pkg/logger"
)

const fmtPRLog = "PR #%d: %s\n"

func main() {
	log := logger.NewFakeLogger() // Use fake logger to avoid output
	mock := github.NewMockClient(log)

	// Override specific methods for testing
	mock.GetPRFunc = func(ctx context.Context, repo github.Repository, number int) (*github.PullRequest, error) {
		return &github.PullRequest{
			Number: number,
			Title:  "Test PR",
			State:  github.PullRequestStateOpen,
		}, nil
	}

	// Use mock in tests
	ctx := context.Background()
	pr, _ := mock.GetPR(ctx, github.Repository{Owner: "test", Name: "repo"}, 42)
	fmt.Printf(fmtPRLog, pr.Number, pr.Title)

}
Output:
PR #42: Test PR

func (*MockClient) AddLabel

func (m *MockClient) AddLabel(ctx context.Context, repo Repository, number int, label string) error

AddLabel calls the mock AddLabelFunc.

func (*MockClient) GetPR

func (m *MockClient) GetPR(ctx context.Context, repo Repository, number int) (*PullRequest, error)

GetPR calls the mock GetPRFunc.

func (*MockClient) GetPRFiles

func (m *MockClient) GetPRFiles(ctx context.Context, repo Repository, number int) ([]File, error)

GetPRFiles calls the mock GetPRFilesFunc.

func (*MockClient) ListPRs

func (m *MockClient) ListPRs(ctx context.Context, repo Repository) ([]PullRequest, error)

ListPRs calls the mock ListPRsFunc.

func (*MockClient) MergePR

func (m *MockClient) MergePR(ctx context.Context, repo Repository, number int, commitMsg string) error

MergePR calls the mock MergePRFunc.

func (*MockClient) RemoveLabel

func (m *MockClient) RemoveLabel(ctx context.Context, repo Repository, number int, label string) error

RemoveLabel calls the mock RemoveLabelFunc.

type PRFile

type PRFile struct {
	// Filename is the file path.
	Filename string

	// Status is the file change status (added, modified, removed, renamed).
	Status string

	// Additions is the number of lines added.
	Additions int

	// Deletions is the number of lines deleted.
	Deletions int

	// Changes is the total number of changes (additions + deletions).
	Changes int

	// Patch is the unified diff patch.
	Patch string

	// Size is the file size in bytes.
	Size int64
}

PRFile represents a file modified in a pull request. This is a value type (immutable).

type PullRequest

type PullRequest struct {
	// ID is the unique pull request ID.
	ID int64

	// Number is the PR number within the repository.
	Number int

	// Owner is the repository owner (user or organization).
	Owner string

	// Repo is the repository name.
	Repo string

	// Title is the PR title.
	Title string

	// HeadSHA is the commit SHA at the head of the PR branch.
	HeadSHA string

	// BaseBranch is the target branch name.
	BaseBranch string

	// State is the current state of the PR (open, closed, merged).
	State PullRequestState

	// Labels are the labels attached to the PR.
	Labels []Label

	// CreatedAt is the timestamp when the PR was created.
	CreatedAt time.Time

	// UpdatedAt is the timestamp when the PR was last updated.
	UpdatedAt time.Time

	// Mergeable indicates whether the PR can be merged (nil if unknown).
	Mergeable *bool

	// MergeableState describes the mergeable state (e.g., "clean", "unstable", "dirty").
	MergeableState string
}

PullRequest represents a GitHub pull request with essential fields. This is a pointer type (expensive to copy, often mutated).

type PullRequestState

type PullRequestState string

PullRequestState represents the state of a pull request.

const (
	// PullRequestStateOpen indicates the PR is open and can be modified.
	PullRequestStateOpen PullRequestState = "open"

	// PullRequestStateClosed indicates the PR is closed but not merged.
	PullRequestStateClosed PullRequestState = "closed"

	// PullRequestStateMerged indicates the PR is closed and merged.
	PullRequestStateMerged PullRequestState = "merged"
)

type PullRequestWebhookPayload

type PullRequestWebhookPayload struct {
	// Action is the action performed (e.g., "opened", "closed", "synchronize").
	Action string

	// PullRequest is the pull request that triggered the event.
	PullRequest PullRequest

	// Repository is the repository where the event occurred.
	Repository Repository
}

PullRequestWebhookPayload represents a pull_request webhook event.

type Repository

type Repository struct {
	// Owner is the repository owner (user or organization).
	Owner string

	// Name is the repository name.
	Name string
}

Repository represents a GitHub repository identifier. This is a value type (immutable, cheap to copy).

type Review

type Review struct {
	// ID is the unique review ID.
	ID int64

	// User is the GitHub username of the reviewer.
	User string

	// State is the review state (APPROVED, CHANGES_REQUESTED, COMMENTED).
	State string

	// Body is the review comment body.
	Body string

	// SubmittedAt is when the review was submitted.
	SubmittedAt time.Time
}

Review represents a GitHub pull request review. This is a value type (immutable).

type Status

type Status struct {
	// State is the status state (success, failure, error, pending).
	State string

	// Context is the status context (identifies the check).
	Context string

	// Description is a human-readable description of the status.
	Description string

	// TargetURL is the URL to the detailed status information.
	TargetURL string

	// CreatedAt is when the status was created.
	CreatedAt time.Time

	// UpdatedAt is when the status was last updated.
	UpdatedAt time.Time
}

Status represents a GitHub commit status check. This is a value type (immutable).

type WebhookEvent

type WebhookEvent struct {
	// Type is the event type (e.g., "pull_request", "push", "issues").
	Type string

	// DeliveryID is the unique delivery ID for this webhook.
	DeliveryID string

	// Payload is the parsed event payload as a domain-specific type.
	Payload interface{}
}

WebhookEvent represents a parsed GitHub webhook event with metadata.

func ParseWebhook

func ParseWebhook(req *http.Request, secret string) (*WebhookEvent, error)

ParseWebhook parses and validates a GitHub webhook request. It verifies the signature, extracts the event type, and parses the payload. Returns a WebhookEvent with the parsed payload or an error if validation fails.

Example:

event, err := github.ParseWebhook(req, "my-webhook-secret")
if err != nil {
    return err
}
switch payload := event.Payload.(type) {
case *PullRequestWebhookPayload:
    // Handle pull request event
}
Example

ExampleParseWebhook demonstrates how to parse a GitHub webhook.

package main

import ()

func main() {
	// In a real HTTP handler:
	// event, err := github.ParseWebhook(req, "webhook-secret")
	// if err != nil {
	//     http.Error(w, "Invalid webhook", http.StatusBadRequest)
	//     return
	// }

	// Handle different event types
	// switch payload := event.Payload.(type) {
	// case *github.PullRequestWebhookPayload:
	//     fmt.Printf("PR #%d %s\n", payload.PullRequest.Number, payload.Action)
	// default:
	//     fmt.Printf("Unhandled event type: %s\n", event.Type)
	// }
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL