freelo

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: May 1, 2026 License: MIT Imports: 13 Imported by: 0

README

Freelo SDK for Go

Official Go SDK for Freelo.io — typed HTTP client generated from the public OpenAPI spec, with pluggable auth, automatic rate limiting, and retry-with-backoff.

Go Reference

Features

  • Auto-generated from OpenAPI via oapi-codegen — always in sync with the Freelo API.
  • Stdlib-only HTTPnet/http under the hood, no external transport dependencies.
  • Drop-in *WithBody escape hatch for endpoints whose live behavior diverges from the spec.
  • Timezone-correct timestamps — Freelo emits no-zone wall-clock times in Europe/Prague; the SDK parses them back to UTC automatically.
  • Production-grade transport — 25 req/min rate limiting, exponential-backoff retry on 429/5xx, Retry-After honored.
  • Pluggable authenticationBasicAuth ships today; Provider interface ready for future OAuth.
  • Per-request credential lookup for multi-tenant servers (CredentialsFunc).

Installation

go get github.com/freeloio/freelo-go

Requires Go 1.26 or newer.

Quick Start

package main

import (
    "context"
    "fmt"
    "log"
    "os"

    "github.com/freeloio/freelo-go"
    "github.com/freeloio/freelo-go/auth"
)

func main() {
    client, err := freelo.New(
        freelo.WithAuth(auth.BasicAuth{
            Email:  os.Getenv("FREELO_EMAIL"),
            APIKey: os.Getenv("FREELO_API_KEY"),
        }),
        freelo.WithUserAgent("MyApp/1.0 (contact@example.com)"),
    )
    if err != nil {
        log.Fatal(err)
    }

    resp, err := client.API.GetProjectsWithResponse(context.Background(), nil)
    if err != nil {
        log.Fatal(err)
    }
    if resp.JSON200 != nil {
        for _, p := range *resp.JSON200 {
            if p.Id != nil && p.Name != nil {
                fmt.Println(*p.Id, *p.Name)
            }
        }
    }
}

Get your API key from Freelo Settings → Profile → API key.

Authentication

Basic Auth (in-memory credentials)

Best for daemons, CI jobs, and any scenario where credentials live in env vars or a secret store you control.

freelo.WithAuth(auth.BasicAuth{
    Email:  os.Getenv("FREELO_EMAIL"),
    APIKey: os.Getenv("FREELO_API_KEY"),
})
CredentialsFunc (per-request lookup)

Resolves credentials on every outgoing request. Useful when:

  • env vars override an OS keyring (CLI pattern),
  • a multi-tenant server picks credentials per tenant,
  • tokens come from a secret store that needs per-call lookup.
provider := auth.CredentialsFunc(func(ctx context.Context) (string, string, error) {
    if e, k := os.Getenv("FREELO_EMAIL"), os.Getenv("FREELO_API_KEY"); e != "" && k != "" {
        return e, k, nil
    }
    return myKeyring.Lookup(ctx)
})

client, _ := freelo.New(
    freelo.WithAuth(provider),
    freelo.WithUserAgent("MyApp/1.0"),
)
OAuth 2.1

Not yet shipped. The auth.Provider interface (and its companion auth.Refresher) are designed to accept an OAuth provider once Freelo provisions a dedicated client_id for third-party SDKs. Track progress in this repo's milestones.

Configuration

All knobs are functional options on freelo.New:

Option Default Notes
WithAuth(p) Required. auth.Provider impl.
WithUserAgent(ua) Required. Freelo rejects requests without one.
WithBaseURL(url) https://api.freelo.io/v1 Must be HTTPS. Trailing slash trimmed.
WithHTTPClient(hc) &http.Client{Timeout: 30s} Inject custom transport, proxy, mTLS, etc.
WithRateLimit(d) 2.4s 25 req/min server limit. 0 disables (manage externally).
WithRetry(attempts, base, max) 3, 500ms, 8s Full-jitter exponential backoff.
WithRequestEditor(fn) Append custom headers / logging. Runs after auth + UA.

Typed responses & raw access

Two ways to call the API:

Typed (default). client.API is the full *freeloapi.ClientWithResponses. Every endpoint has a typed *WithResponse method that decodes 2xx/4xx/5xx bodies into struct fields:

resp, err := client.API.GetUsersMeWithResponse(ctx)
// resp.JSON200, resp.JSON401, resp.Status() …

Raw. client.Raw sends an arbitrary request through the same auth + UA + rate-limit + retry pipeline and hands you the bare *http.Response:

resp, err := client.Raw(ctx, http.MethodGet, "/users/me", nil)
defer resp.Body.Close()

Use Raw when an endpoint isn't covered by the spec yet, or when you'd rather decode the body yourself.

Timestamps

Freelo returns timestamps without timezone ("2026-04-24T11:12:38"). The SDK ships a freelotime.Time type that:

  • accepts both RFC3339-with-zone and the no-zone wall-clock form,
  • interprets the no-zone form as Europe/Prague,
  • normalizes everything to UTC on the way in and out.

The generated client uses freelotime.Time for every format: date-time field, so typed *WithResponse methods Just Work.

Examples

See examples/ for runnable scenarios:

01_quickstart Smallest possible main.go
02_basic_auth Explicit error handling on /users/me
03_list_projects Pagination
04_create_task POST with typed body
05_comment_with_files File upload + comment with attachment UUID
06_credentials_func env-then-keyring lookup pattern
07_custom_http_client Inject a custom *http.Client
08_raw_passthrough client.Raw escape hatch

Run any of them after exporting FREELO_EMAIL / FREELO_API_KEY:

go run ./examples/01_quickstart

Rate limiting

Freelo's published server limit is 25 req/min. The SDK paces outgoing requests with a 2.4s minimum interval per *Client. A *Client is a singleton per process — creating multiple clients in the same process means multiple rate limiters racing each other against the same server limit.

If you need shared limiting across many clients, set WithRateLimit(0) on each and gate them externally (golang.org/x/sync/semaphore, etc.).

Versioning & releases

Independent semver. Tagged via release-please. Conventional-commit subjects (feat:, fix:, chore:) drive the changelog.

Distribution is the standard Go module proxy — once a tag is pushed, go get github.com/freeloio/freelo-go@vX.Y.Z works within minutes (no separate registry, no goreleaser).

Development

make help          # list targets
make test          # unit tests with -race
make lint          # gofmt + go vet
make examples      # build every example (catches API drift)
make gen           # download spec, regen client, patch time.Time
make gen-check     # CI guard — fails if generated code drifts from spec

Spec lives at spec/freelo-api.yaml (vendored). The vendored copy is byte-identical to upstream apart from one Client → BusinessClient rename (collision with oapi-codegen's HTTP Client type) and the post-generation time.Time → freelotime.Time patch (see scripts/patchgen). A weekly cron in .github/workflows/update-api-spec.yml PRs any drift.

Contributing

Bug reports and PRs welcome. For new features, please open an issue first to discuss the approach — this SDK aims to stay small and predictable, and additions need to fit that goal.

License

MIT — see LICENSE.

Sibling SDKs

Documentation

Overview

Package freelo is the Go SDK for the Freelo.io project-management API.

It provides a typed HTTP client (generated from the public OpenAPI spec), pluggable authentication, automatic rate limiting, and retry with exponential backoff on transient failures.

Quick start

client, err := freelo.New(
    freelo.WithAuth(auth.BasicAuth{Email: email, APIKey: apiKey}),
    freelo.WithUserAgent("MyApp/1.0 (contact@example.com)"),
)
if err != nil {
    log.Fatal(err)
}

resp, err := client.API.GetProjectsWithResponse(ctx, nil)
for _, p := range *resp.JSON200 {
    fmt.Println(*p.Id, *p.Name)
}

See the examples/ directory for runnable scenarios.

Index

Constants

View Source
const (
	// Freelo publishes a 25 req/min rate limit. Spacing requests by ~2.4s
	// keeps us safely under it; tunable via WithRateLimit (0 disables).
	DefaultRateInterval = 60 * time.Second / 25

	// DefaultMaxAttempts caps total tries per request (initial + retries).
	DefaultMaxAttempts = 3

	// DefaultBackoffBase / DefaultBackoffMax bound exponential backoff.
	DefaultBackoffBase = 500 * time.Millisecond
	DefaultBackoffMax  = 8 * time.Second

	// DefaultHTTPTimeout applies when WithHTTPClient is not used.
	DefaultHTTPTimeout = 30 * time.Second
)

Default values applied when corresponding Option is not supplied.

View Source
const DefaultBaseURL = "https://api.freelo.io/v1"

DefaultBaseURL is the production Freelo API root.

View Source
const SDKVersion = "v0.1.0-dev"

SDKVersion is the semantic version of this SDK release. Bumped by release-please based on conventional commits (see .github/workflows/ release-please.yml). Consumers should not rely on this string for feature detection — use API capability checks instead.

Variables

This section is empty.

Functions

This section is empty.

Types

type APIError

type APIError struct {
	StatusCode int
	Status     string
	Body       []byte
	Method     string
	URL        string
}

APIError represents a non-2xx response from the Freelo API after all retries have been exhausted. Surface via errors.As to inspect status code and parsed error fields.

The transport itself does not produce APIError today (the typed client returns *http.Response and the caller decides how to interpret status). This type is exported so consumer code that wants a uniform error shape can construct one from a 4xx/5xx response. Future v0.x versions may promote it into the transport once the patterns settle.

func NewAPIError

func NewAPIError(resp *http.Response, body []byte) *APIError

NewAPIError builds an APIError from a non-2xx *http.Response. The response body is captured up to a safety cap; pass it before the body is closed elsewhere.

func (*APIError) Error

func (e *APIError) Error() string

Error returns a one-line description suitable for logging.

type Client

type Client struct {
	// API is the typed, fully-generated Freelo client. All endpoints from
	// spec/freelo-api.yaml are reachable through it (e.g.
	// API.GetProjectsWithResponse, API.CreateCommentWithBody).
	API *freeloapi.ClientWithResponses
	// contains filtered or unexported fields
}

Client is the entry point to the Freelo SDK. Build one with New, then reach for typed responses via Client.API or send arbitrary requests via Client.Raw. A Client is safe for concurrent use.

Internally a Client wraps a *freeloapi.ClientWithResponses with the rate-limit + retry transport, the user-supplied auth provider, and any extra request editors. Tear-down is the http.Client's responsibility — the SDK does not own goroutines.

func New

func New(opts ...Option) (*Client, error)

New builds a Client from Options. WithAuth and WithUserAgent are required — New returns an error if either is omitted. Other options fall back to documented defaults (see DefaultBaseURL, DefaultRateInterval, DefaultMaxAttempts, DefaultHTTPTimeout).

func (*Client) BaseURL

func (c *Client) BaseURL() string

BaseURL returns the resolved API base URL (with HTTPS enforcement and trailing-slash trim already applied). Useful for callers that build requests through Do and need to know the configured root.

func (*Client) Do

func (c *Client) Do(req *http.Request) (*http.Response, error)

Do sends a pre-built *http.Request through the SDK's transport pipeline. Use this when you need control over headers (e.g. Content-Type for a JSON body, custom Accept negotiation) — set them on req before calling Do.

The auth + User-Agent editors run after the caller's headers are already set, so they can read them but Authorization / User-Agent that the caller pre-sets will be overwritten by SDK editors. That's intentional: nobody should be hand-rolling Authorization on a Freelo SDK call.

func (*Client) Raw

func (c *Client) Raw(ctx context.Context, method, path string, body io.Reader) (*http.Response, error)

Raw sends an ad-hoc HTTP request through the SDK's transport pipeline (auth + User-Agent + rate limit + retry) for endpoints not covered by the generated typed client, or when callers need the bare *http.Response.

Path may be relative (joined to the configured base URL) or absolute (passed through unchanged). Body may be nil for GET/DELETE.

Raw does not set Content-Type — if you're sending a JSON body, build the request via Do instead so you can set the header yourself.

The returned *http.Response is the caller's responsibility to close.

type Option

type Option func(*config) error

Option configures a Client. Apply via New(opts...).

func WithAuth

func WithAuth(p auth.Provider) Option

WithAuth sets the credential provider applied to every request. Required; New returns an error if WithAuth is omitted.

func WithBaseURL

func WithBaseURL(raw string) Option

WithBaseURL overrides the default API root. HTTPS is required — http:// URLs are rejected to prevent credential leakage. Trailing slashes are trimmed.

func WithHTTPClient

func WithHTTPClient(hc *http.Client) Option

WithHTTPClient overrides the default *http.Client (Timeout: 30s). Useful for injecting custom transports, proxies, or mocks in tests.

func WithRateLimit

func WithRateLimit(interval time.Duration) Option

WithRateLimit sets the minimum spacing between requests issued by this Client. Pass 0 to disable client-side rate limiting (useful when the caller manages it themselves, e.g. via a shared semaphore across multiple Clients in the same process).

Default is 2.4s, calibrated to Freelo's published 25 req/min server limit.

func WithRequestEditor

func WithRequestEditor(fn RequestEditorFn) Option

WithRequestEditor appends a request editor that runs after the SDK's built-in auth + User-Agent editors. Use for adding custom headers (correlation IDs, feature flags, ...) or logging.

Editors run in registration order. Returning an error short-circuits the request before it leaves the process.

func WithRetry

func WithRetry(attempts int, base, max time.Duration) Option

WithRetry configures retry behavior on transient failures (network errors, 429, 5xx). attempts counts the initial try (so attempts=1 means no retry). base and max bound the exponential backoff window.

Defaults: attempts=3, base=500ms, max=8s.

func WithUserAgent

func WithUserAgent(ua string) Option

WithUserAgent sets the User-Agent header. Required; Freelo rejects requests without one. The recommended form is `<App>/<Version> (<contact>)`, e.g. "MyApp/1.0 (ops@example.com)".

type RequestEditorFn

type RequestEditorFn = freeloapi.RequestEditorFn

RequestEditorFn is the same shape as freeloapi.RequestEditorFn — re-exported so consumers can write editors without importing the generated package.

Directories

Path Synopsis
Package auth defines pluggable authentication for the Freelo SDK.
Package auth defines pluggable authentication for the Freelo SDK.
examples
01_quickstart command
Quickstart: build a Freelo client and call one endpoint.
Quickstart: build a Freelo client and call one endpoint.
02_basic_auth command
Basic Auth with explicit error handling.
Basic Auth with explicit error handling.
03_list_projects command
List all projects accessible to the authenticated user.
List all projects accessible to the authenticated user.
04_create_task command
Create a task in a tasklist.
Create a task in a tasklist.
05_comment_with_files command
Post a comment with file attachments.
Post a comment with file attachments.
06_credentials_func command
CredentialsFunc lets the SDK look up credentials per request.
CredentialsFunc lets the SDK look up credentials per request.
07_custom_http_client command
Inject a custom *http.Client — corporate proxies, mTLS, custom transports, longer/shorter timeouts.
Inject a custom *http.Client — corporate proxies, mTLS, custom transports, longer/shorter timeouts.
08_raw_passthrough command
Use Client.Raw to call endpoints not covered by the typed client (or when you'd rather decode the response yourself).
Use Client.Raw to call endpoints not covered by the typed client (or when you'd rather decode the response yourself).
Package freeloapi provides primitives to interact with the Freelo HTTP API.
Package freeloapi provides primitives to interact with the Freelo HTTP API.
Package freelotime defines a time.Time wrapper that handles Freelo's two on-the-wire timestamp formats:
Package freelotime defines a time.Time wrapper that handles Freelo's two on-the-wire timestamp formats:
internal
testutil
Package testutil contains shared httptest helpers used by the SDK's own unit tests.
Package testutil contains shared httptest helpers used by the SDK's own unit tests.
scripts
patchgen command
Command patchgen rewrites the oapi-codegen output so that every `time.Time` field becomes a `freelotime.Time` field.
Command patchgen rewrites the oapi-codegen output so that every `time.Time` field becomes a `freelotime.Time` field.

Jump to

Keyboard shortcuts

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