api

package
v0.1.5 Latest Latest
Warning

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

Go to latest
Published: Sep 24, 2025 License: Apache-2.0 Imports: 16 Imported by: 0

README

API Client Layer (internal/api)

This package provides a thin, well-tested HTTP client for the Control Plane API described in docs/api-spec.yaml.

We use generated OpenAPI types from internal/api-gen (package apigen) directly for all request/response models to avoid drift and keep behavior aligned with the spec.

Goals

  • Decouple transport concerns with a focused client and small helpers.
  • Stable, testable interfaces which remain compatible as the API evolves.
  • Resilient HTTP behavior with timeouts and typed errors.

Key Decisions

  • Health endpoint: used internally for a one-time readiness probe, not part of the exported interface.
  • Identity handling: carried via context.Context. Helpers: WithIdentity(ctx, Identity) and IdentityFromContext(ctx); a default identity can be set on the client and overridden per call via context.
  • Single-rating endpoints: 200 returns apigen.RatingResponse (we surface the first rating); 202 returns apigen.ScanInProgress with polling helpers.
  • Batch ratings: 200 returns apigen.BatchRatingResponse (links); 202 returns apigen.ScanStatus (pending). Client returns (BatchRatingResponse, *ScanStatus, error) to distinguish immediate vs accepted.
  • Auth: All endpoints require a specific publishable key via Authorization: Bearer <publishable_key>.

Public Interfaces (internal)

// Identity is attached to requests if not Anonymous.
type Identity struct {
    OrgUUID   string
    HostUUID  string
    Anonymous bool
}

// Context helpers.
func WithIdentity(parent context.Context, id Identity) context.Context
func IdentityFromContext(ctx context.Context) (Identity, bool)

// RatingsClient is the main transport interface used by the scanner.
type RatingsClient interface {
    GetRating(ctx context.Context, target RatingTarget) (RatingResult, error)
    SubmitBatchRatings(ctx context.Context, req apigen.BatchRatingRequest) (apigen.BatchRatingResponse, *apigen.ScanStatus, error)
    GetScanStatus(ctx context.Context, scanID uuid.UUID) (apigen.ScanStatus, error)

    // Optional helper: polls when a prior call returned 202.
    WaitForScanCompletion(ctx context.Context, ref string, pollEvery time.Duration) ([]apigen.SecurityRating, error)
}

// Client construction.
type ClientOption func(*Client)
func NewClient(opts ...ClientOption) (*Client, error)

Types

  • We use the generated types from internal/api-gen/types.gen.go (package apigen) directly: SecurityRating, RatingResponse, BatchRatingRequest, BatchRatingResponse, ScanInProgress, ScanStatus, TargetIdentifier, IdentifierKind, etc.
  • Internal convenience types:
// RatingResult represents either a final rating (200) or an async in-progress response (202).
type RatingResult struct {
    Rating     *apigen.SecurityRating // 200 response
    InProgress *apigen.ScanInProgress // 202 response
}

// RatingTarget captures supported targets using generated IdentifierKind.
type RatingTarget interface { kind() apigen.IdentifierKind }

HTTP Behavior

  • Standard net/http client with a sane default timeout.
  • Conditional headers X-Org-Uuid and X-Host-Uuid when Anonymous == false.
  • Limited retries for idempotent GETs on transient errors and HTTP 429/5xx, honoring Retry-After.
  • Strict JSON decoding with clear error surfacing (apigen.Error mapped to typed errors).

Endpoints Covered

  • GET /ratings/purl/{purl}RatingResponse or ScanInProgress.
  • GET /ratings/repo/{org}/{repo}RatingResponse or ScanInProgress.
  • GET /ratings/oci/{ref}RatingResponse or ScanInProgress.
  • GET /ratings/url/{url}RatingResponse or ScanInProgress.
  • POST /ratings/batchBatchRatingResponse (200) or ScanStatus (202).
  • GET /scan-status/{scanId}ScanStatus.
  • GET /health is available internally for diagnostics.

Error Handling

  • Typed errors: ErrUnauthorized, ErrNotFound, ErrValidation, ErrOffline.
  • Structured errors: RateLimitedError with Retry-After, and RemoteError with status code and optional request ID.

Testing Strategy

  • Unit tests: httptest.Server covering paths, headers, and all response codes.
  • Contract tests: optional against an OpenAPI mock derived from docs/api-spec.yaml.
  • Schema drift guard: task api-spec:check.

Taskfile hooks

  • task test → run standard tests.
  • task test-all → run all tests.
  • task api-spec:gen → regenerate types from docs/api-spec.yaml.
  • task api-spec:check → verify generated types are up-to-date and committed.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrUnauthorized = errors.New("unauthorized")
	ErrNotFound     = errors.New("not found")
	ErrValidation   = errors.New("validation error")
	ErrOffline      = errors.New("offline")
)

Sentinel and typed errors for transport-level reporting.

View Source
var (
	BuildVersion = "dev"
	BuildCommit  = "none"
	BuildDate    = "unknown"
)

Build-time variables injected via -ldflags -X. Defaults are for local dev and tests.

Functions

func WithIdentity

func WithIdentity(parent context.Context, id Identity) context.Context

WithIdentity returns a new context with the provided identity.

Types

type Client

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

Client is a concrete implementation of RatingsClient.

func NewClient

func NewClient(opts ...ClientOption) (*Client, error)

NewClient constructs a new Client with defaults.

func (*Client) GetRating

func (c *Client) GetRating(ctx context.Context, target RatingTarget) (RatingResult, error)

GetRating dispatches rating retrieval based on the target type.

func (*Client) GetScanStatus

func (c *Client) GetScanStatus(ctx context.Context, scanID uuid.UUID) (apigen.ScanStatus, error)

GetScanStatus implements GET /scan-status/{scanId}.

func (*Client) SubmitBatchRatings

SubmitBatchRatings implements POST /ratings/batch. Per spec: 200 => BatchRatingResponse (links), 202 => ScanStatus. It performs a single POST and returns either the immediate response or an accepted ScanStatus.

func (*Client) WaitForScanCompletion

func (c *Client) WaitForScanCompletion(ctx context.Context, ref string, pollEvery time.Duration) ([]apigen.SecurityRating, error)

WaitForScanCompletion polls a scan until completion and returns ratings for all completed targets. The ref must be either a scan ID or a relative path "/scan-status/{id}".

type ClientOption

type ClientOption func(*Client)

ClientOption mutates Client configuration.

func WithBaseURL

func WithBaseURL(base string) ClientOption

WithBaseURL configures the API base URL for production or tests.

func WithPublishableKey

func WithPublishableKey(key string) ClientOption

WithPublishableKey configures the Authorization bearer publishable key header. The value should match the expected format: ^ens_pk_live_[a-f0-9]{40}$.

type Identity

type Identity struct {
	OrgUUID   string
	HostUUID  string
	Anonymous bool
}

Identity carries optional request identity. When Anonymous is true, OrgUUID and HostUUID are ignored.

func IdentityFromContext

func IdentityFromContext(ctx context.Context) (Identity, bool)

IdentityFromContext extracts an Identity from context if present.

type OCITarget

type OCITarget struct{ Ref string }

OCITarget identifies an OCI ref.

func NewOCITarget

func NewOCITarget(ref string) (OCITarget, error)

NewOCITarget validates non-empty ref.

type PURLTarget

type PURLTarget struct{ PURL string }

PURLTarget identifies a package via purl.

func NewPURLTarget

func NewPURLTarget(purl string) (PURLTarget, error)

NewPURLTarget validates basic non-empty purl input.

type RateLimitedError

type RateLimitedError struct {
	RetryAfterSeconds int
	Remote            apigen.Error
}

RateLimitedError includes optional retry-after seconds.

func (RateLimitedError) Error

func (e RateLimitedError) Error() string

type RatingResult

type RatingResult struct {
	Rating     *apigen.SecurityRating // i.e. 200 response
	InProgress *apigen.ScanInProgress // i.e. 202 response
}

RatingResult abstracts 200 vs 202 for single-rating endpoints using apigen types.

type RatingTarget

type RatingTarget interface {
	// contains filtered or unexported methods
}

RatingTarget is a closed set of supported target types. Implementations are small value types with validation via constructors. The discriminator uses the generated IdentifierKind to avoid duplication.

type RatingsClient

type RatingsClient interface {
	GetRating(ctx context.Context, target RatingTarget) (RatingResult, error)
	SubmitBatchRatings(ctx context.Context, req apigen.BatchRatingRequest) (apigen.BatchRatingResponse, *apigen.ScanStatus, error)
	GetScanStatus(ctx context.Context, scanID uuid.UUID) (apigen.ScanStatus, error)
	// WaitForScanCompletion polls a scan by ID or PollUrl and returns all completed ratings.
	// If ref looks like a URL, it polls that URL; otherwise it treats ref as a scan ID.
	WaitForScanCompletion(ctx context.Context, ref string, pollEvery time.Duration) ([]apigen.SecurityRating, error)
}

RatingsClient is the main transport interface used by the scanner.

type RemoteError

type RemoteError struct {
	StatusCode int
	Remote     apigen.Error
}

RemoteError wraps non-specific remote errors with status code and optional request ID.

func (RemoteError) Error

func (e RemoteError) Error() string

type RepoTarget

type RepoTarget struct{ Org, Repo string }

RepoTarget identifies a repo by organization and name.

func NewRepoTarget

func NewRepoTarget(org, repo string) (RepoTarget, error)

NewRepoTarget validates non-empty org and repo.

type URLTarget

type URLTarget struct{ URL string }

URLTarget identifies a raw URL string.

func NewURLTarget

func NewURLTarget(u string) (URLTarget, error)

NewURLTarget validates non-empty URL and enforces http(s) scheme only.

Jump to

Keyboard shortcuts

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