twapi

package module
v1.29.1 Latest Latest
Warning

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

Go to latest
Published: Sep 4, 2026 License: MIT Imports: 13 Imported by: 0

README

🚀 Teamwork.com API - Go SDK

Go Version Go Reference License Go Report Card

The official Go SDK for the Teamwork.com API

Build powerful integrations with Teamwork's project management platform

📖 API Documentation🎯 Examples🐛 Report Issues


✨ Features

  • Multiple Authentication Methods - Bearer token, Basic auth, and OAuth2
  • 🏗️ Type-Safe API - Fully typed requests and responses
  • 🌐 Context Support - Built-in context.Context support for cancellation and timeouts
  • 📦 Zero Dependencies - Minimal external dependencies
  • 🧪 Thoroughly Tested - Comprehensive test coverage
  • 📱 Cross-Platform - Works on Windows, macOS, and Linux

📦 Installation

Add this library as a dependency to your Go module:

go get github.com/teamwork/twapi-go-sdk

Requirements:

  • Go 1.27 or later
  • A Teamwork.com account with API access

🔐 Authentication

The SDK supports multiple authentication methods to suit different use cases:

Perfect for server-to-server integrations and scripts:

import "github.com/teamwork/twapi-go-sdk/session"

session := session.NewBearerToken("your_api_token", "https://yourdomain.teamwork.com")
🔑 Basic Authentication

Use with API tokens or user credentials:

// With API token
session := session.NewBasicAuth("your_api_token", "", "https://yourdomain.teamwork.com")

// With username/password
session := session.NewBasicAuth("username", "password", "https://yourdomain.teamwork.com")
🌐 OAuth2

Ideal for user-facing applications (opens browser for authorization):

session := session.NewOAuth2("client_id", "client_secret",
  session.WithOAuth2Server("https://teamwork.com"),
  session.WithOAuth2CallbackServerAddr("127.0.0.1:6275"),
)

[!CAUTION] ⚠️ Note: OAuth2 opens a browser window and is not suitable for headless environments.

🏁 Quick Start

Here's a simple example to get you started:

package main

import (
  "context"
  "fmt"
  "log"

  twapi "github.com/teamwork/twapi-go-sdk"
  "github.com/teamwork/twapi-go-sdk/projects"
  "github.com/teamwork/twapi-go-sdk/session"
)

func main() {
  ctx := context.Background()
  
  // Initialize the SDK with bearer token authentication
  engine := twapi.NewEngine(session.NewBearerToken("your_token", "https://yourdomain.teamwork.com"))

  // Create a new project
  project, err := projects.ProjectCreate(ctx, engine, projects.NewProjectCreateRequest("My Awesome Project"))
  if err != nil {
    log.Fatalf("Failed to create project: %v", err)
  }
  
  fmt.Printf("✅ Created project '%s' with ID: %d\n", project.Name, project.ID)
}

📚 Examples

Working with Projects
package main

import (
  "context"
  "fmt"
  "time"

  twapi "github.com/teamwork/twapi-go-sdk"
  "github.com/teamwork/twapi-go-sdk/projects"
  "github.com/teamwork/twapi-go-sdk/session"
)

func main() {
  ctx := context.Background()
  engine := twapi.NewEngine(session.NewBearerToken("your_token", "https://yourdomain.teamwork.com"))

  project, err := projects.ProjectCreate(ctx, engine, projects.ProjectCreateRequest{
    Name:        "Q1 Marketing Campaign",
    Description: twapi.Ptr("Marketing campaign for Q1 product launch"),
    StartAt:     twapi.Ptr(time.Now()),
    EndAt:       twapi.Ptr(time.Now().AddDate(0, 3, 0)), // 3 months from now
  })
  if err != nil {
    fmt.Fprintf(os.Stderr, "❌ Failed to create project: %v\n", err)
    os.Exit(1)
  }

  // Retrieve the project
  retrievedProject, err := projects.ProjectGet(ctx, engine, projects.NewProjectRetrieveRequest(int64(project.ID)))
  if err != nil {
    fmt.Fprintf(os.Stderr, "❌ Failed to retrieve project: %v\n", err)
    os.Exit(1)
  }

  fmt.Printf("✅ Project: %s (ID: %d)\n", retrievedProject.Name, retrievedProject.ID)
  
  // List all projects
  projectsList, err := projects.ProjectList(ctx, engine, projects.NewProjectRetrieveManyRequest())
  if err != nil {
    fmt.Fprintf(os.Stderr, "❌ Failed to list projects: %v\n", err)
    os.Exit(1)
  }
  
  fmt.Printf("✅ Found %d projects\n", len(projectsList.Projects))
  
  // Update the project
  updatedProject, err := projects.ProjectUpdate(ctx, engine, projects.ProjectUpdateRequest{
    Path:  projects.ProjectUpdateRequestPath{
      ID: int64(project.ID),
    },
    Name: "Q1 Marketing Campaign - Updated",
  })
  if err != nil {
    fmt.Fprintf(os.Stderr, "❌ Failed to update project: %v\n", err)
    os.Exit(1)
  }
  
  fmt.Printf("✅ Updated project name to: %s\n", updatedProject.Name)

  // Delete the project
  err = projects.ProjectDelete(ctx, engine, projects.NewProjectDeleteRequest(int64(project.ID)))
  if err != nil {
    fmt.Fprintf(os.Stderr, "❌ Failed to delete project: %v\n", err)
    os.Exit(1)
  }

  fmt.Println("✅ Project deleted successfully")
}
OAuth2 Authentication Example
package main

import (
  "context"
  "flag"
  "fmt"
  "os"

  twapi "github.com/teamwork/twapi-go-sdk"
  "github.com/teamwork/twapi-go-sdk/projects"
  "github.com/teamwork/twapi-go-sdk/session"
)

func main() {
  clientID := flag.String("client-id", "", "OAuth2 Client ID")
  clientSecret := flag.String("client-secret", "", "OAuth2 Client Secret")
  flag.Parse()

  if *clientID == "" || *clientSecret == "" {
    fmt.Fprintln(os.Stderr, "❌ client-id and client-secret are required")
    os.Exit(1)
  }

  // Create OAuth2 session (will open browser for authorization)
  session := session.NewOAuth2(*clientID, *clientSecret,
    session.WithOAuth2CallbackServerAddr("127.0.0.1:6275"),
  )
  
  engine := twapi.NewEngine(session)

  // Test the connection by creating a project
  project, err := projects.ProjectCreate(context.Background(), engine, projects.NewProjectCreateRequest("OAuth2 Test Project"))
  if err != nil {
    fmt.Fprintf(os.Stderr, "❌ Failed to create project: %v\n", err)
    os.Exit(1)
  }

  fmt.Printf("✅ OAuth2 authentication successful! Created project: %s (ID: %d)\n", project.Name, project.ID)
}
Error Handling Best Practices
package main

import (
  "context"
  "errors"
  "fmt"
  "net/http"

  twapi "github.com/teamwork/twapi-go-sdk"
  "github.com/teamwork/twapi-go-sdk/projects"
  "github.com/teamwork/twapi-go-sdk/session"
)

func main() {
  ctx := context.Background()
  engine := twapi.NewEngine(session.NewBearerToken("your_token", "https://yourdomain.teamwork.com"))

  project, err := projects.ProjectCreate(ctx, engine, projects.NewProjectCreateRequest("Test Project"))
  if err != nil {
    // Handle different types of errors
    var httpErr *twapi.HTTPError
    if errors.As(err, &httpErr) {
      switch httpErr.StatusCode {
      case http.StatusUnauthorized:
        fmt.Println("❌ Authentication failed - check your API token")
      case http.StatusForbidden:
        fmt.Println("❌ Access denied - insufficient permissions")
      case http.StatusTooManyRequests:
        fmt.Println("❌ Rate limit exceeded - please retry later")
      default:
        fmt.Printf("❌ HTTP error %d: %s\n", httpErr.StatusCode, httpErr.Message)
      }
    } else {
      fmt.Printf("❌ Unexpected error: %v\n", err)
    }
    return
  }

  fmt.Printf("✅ Success! Created project: %s\n", project.Name)
}

🔧 Configuration

Context and Timeouts

The SDK supports Go's context.Context for request cancellation and timeouts:

import "time"

// Create a context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

// Use the context in API calls
project, err := projects.ProjectCreate(ctx, engine, request)
Custom HTTP Client

You can customize the underlying HTTP client:

import (
  "net/http"
  "time"
)

// Create engine with custom HTTP client
httpClient := &http.Client{
  Timeout: 60 * time.Second,
  Transport: &http.Transport{
    MaxIdleConns:        10,
    IdleConnTimeout:     30 * time.Second,
    DisableCompression:  true,
  },
}

engine := twapi.NewEngine(session,
  twapi.WithHTTPClient(httpClient),
)
Middleware

You can add custom middleware to intercept and modify HTTP requests/responses. Middlewares are executed in the order they are added:

import (
  "fmt"
  "net/http"
  "time"

  twapi "github.com/teamwork/twapi-go-sdk"
  "github.com/teamwork/twapi-go-sdk/session"
)

// Logging middleware
func loggingMiddleware(next twapi.HTTPClient) twapi.HTTPClient {
  return twapi.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
    start := time.Now()
    fmt.Printf("➡️  %s %s", req.Method, req.URL)

    resp, err := next.Do(req)
    duration := time.Since(start)

    switch {
    case err != nil:
      fmt.Printf(" ❌ %s (took %v)\n", err.Error(), duration)
    case resp.StatusCode >= 400:
      fmt.Printf(" ❌ %s (took %v)\n", resp.Status, duration)
    default:
      fmt.Printf(" ✅ %s (took %v)\n", resp.Status, duration)
    }
    return resp, err
  })
}

// Rate limiting middleware
func rateLimitingMiddleware(next twapi.HTTPClient) twapi.HTTPClient {
  return twapi.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
    // Add rate limiting logic here
    time.Sleep(100 * time.Millisecond) // Simple delay example
    return next.Do(req)
  })
}

// Authentication header middleware
func authHeaderMiddleware(apiKey string) func(twapi.HTTPClient) twapi.HTTPClient {
  return func(next twapi.HTTPClient) twapi.HTTPClient {
    return twapi.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
      req.Header.Set("X-Custom-Auth", apiKey)
      return next.Do(req)
    })
  }
}

func main() {
  session := session.NewBearerToken("your_token", "https://yourdomain.teamwork.com")

  // Chain multiple middlewares
  engine := twapi.NewEngine(session,
    twapi.WithMiddleware(loggingMiddleware),
    twapi.WithMiddleware(rateLimitingMiddleware),
    twapi.WithMiddleware(authHeaderMiddleware("custom-key")),
  )

  // Now all requests will go through the middleware chain
  // ...use engine for API calls...
}
Iterator for Paginated Results

The SDK provides an iterator function to easily handle paginated API responses:

import (
  "context"
  "fmt"

  twapi "github.com/teamwork/twapi-go-sdk"
  "github.com/teamwork/twapi-go-sdk/projects"
  "github.com/teamwork/twapi-go-sdk/session"
)

func main() {
  ctx := context.Background()
  engine := twapi.NewEngine(session.NewBearerToken("your_token", "https://yourdomain.teamwork.com"))

  // Create an iterator for paginated project results
  next, err := twapi.Iterate[projects.ProjectListRequest, *projects.ProjectListResponse](
    ctx,
    engine,
    projects.NewProjectListRequest(),
  )
  if err != nil {
    fmt.Printf("Failed to create iterator: %v\n", err)
    return
  }

  // Iterate through all pages
  var iteration int
  for {
    iteration++
    fmt.Printf("📄 Page %d\n", iteration)

    response, hasNext, err := next()
    if err != nil {
      fmt.Printf("Error fetching page: %v\n", err)
      break
    }
    if response == nil {
      break
    }

    // Process projects from current page
    for _, project := range response.Projects {
      fmt.Printf("  ➢ %s (ID: %d)\n", project.Name, project.ID)
    }

    // Check if there are more pages
    if !hasNext {
      break
    }
  }
}

🐛 Error Handling

The SDK provides structured error handling:

import "errors"

project, err := projects.ProjectCreate(ctx, engine, request)
if err != nil {
  var httpErr *twapi.HTTPError
  if errors.As(err, &httpErr) {
    fmt.Printf("HTTP %d: %s\n", httpErr.StatusCode, httpErr.Message)
    // Handle specific status codes
  }
}

🧪 Testing

Run the test suite:

go test ./...

Run integration tests:

TWAPI_SERVER=https://yourdomain.teamwork.com/ TWAPI_TOKEN=your_api_token go test ./...

Run tests with coverage:

go test -race -coverprofile=coverage.out ./...
go tool cover -html=coverage.out

📋 Requirements

  • Go Version: 1.27 or later
  • Dependencies: Minimal external dependencies (see go.mod)
  • Teamwork Account: Valid Teamwork.com account with API access

🤝 Contributing

We welcome contributions! Please see our Contributing Guidelines for details.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🆘 Support


Made with ❤️ by the Teamwork.com team

⭐ Star us on GitHub if this project helped you!

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ApplySparseFields added in v1.17.0

func ApplySparseFields[F ~string](query url.Values, entityKey string, fields []F)

ApplySparseFields adds a `fields[<entityKey>]=...` parameter to query when fields is non-empty. entityKey is the JSON collection name used by the v3 response (e.g. "tasks" for the main task list, "customfields" for a sideload of custom fields). F is constrained to typed string aliases so callers can only pass values that correspond to real JSON attributes on the entity — concrete types are generated alongside each list response.

https://apidocs.teamwork.com/guides/teamwork/sparse-fieldsets

func Execute

func Execute[R HTTPRequester, T HTTPResponser](ctx context.Context, engine *Engine, requester R) (T, error)

Execute sends an HTTP request using the provided requester and handles the response using the provided responser.

func ExecuteRaw added in v1.17.0

func ExecuteRaw[R HTTPRequester](ctx context.Context, engine *Engine, requester R) (*http.Response, error)

ExecuteRaw sends an HTTP request using the provided requester and returns the raw HTTP response. The caller will be responsible for closing the response body. This is useful when using sparse fields, where only a subset of the fields are returned in the response, and the caller needs to handle the response manually.

func Iterate added in v0.0.7

func Iterate[T HTTPRequester, R interface {
	HTTPResponser
	Iterate() *T
}](ctx context.Context, e *Engine, req T) (next func() (R, bool, error), err error)

Iterate allows scanning through paginated results from the Teamwork API.

Types

type Date

type Date time.Time

Date is a type alias for time.Time, used to represent date values in the API.

func (Date) IsZero added in v1.8.0

func (d Date) IsZero() bool

IsZero reports whether the Date is zero.

func (Date) MarshalJSON

func (d Date) MarshalJSON() ([]byte, error)

MarshalJSON encodes the Date as a string in the format "2006-01-02".

func (Date) MarshalText

func (d Date) MarshalText() ([]byte, error)

MarshalText encodes the Date as a string in the format "2006-01-02".

func (Date) String

func (d Date) String() string

String returns the string representation of the Date in the format "2006-01-02".

func (*Date) UnmarshalJSON

func (d *Date) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes a JSON string into a Date type.

func (*Date) UnmarshalText

func (d *Date) UnmarshalText(text []byte) error

UnmarshalText decodes a text string into a Date type. This is required when using Date type as a map key.

The parsing lives here rather than in UnmarshalJSON because the text form is unquoted: feeding it to UnmarshalJSON would ask the JSON decoder to parse 2006-01-02 as a bare token, which fails on the first hyphen.

type Engine

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

Engine is the main structure that handles communication with the Teamwork API.

func NewEngine

func NewEngine(session Session, opts ...EngineOption) *Engine

NewEngine creates a new Engine instance with the provided HTTP client and session.

func (*Engine) Do added in v1.22.0

func (e *Engine) Do(req *http.Request) (*http.Response, error)

Do sends an already built request through the Engine's client and middlewares, returning the response with its body open for the caller to close.

It neither authenticates the request nor derives its URL from the session, so it is only for services outside the Teamwork API, such as a pre-signed storage URL. Use Execute for the API itself.

type EngineOption

type EngineOption func(*Engine)

EngineOption is a function that modifies the Engine configuration.

func WithHTTPClient

func WithHTTPClient(client HTTPClient) EngineOption

WithHTTPClient sets the HTTP client for the Engine. By default, it uses http.DefaultClient. When setting the HTTP client, any middlewares that were added using WithMiddleware before this call will be ignored.

func WithLogger

func WithLogger(logger *slog.Logger) EngineOption

WithLogger sets the logger for the Engine. By default, it uses slog.Default().

func WithMiddleware added in v0.0.7

func WithMiddleware(middleware func(HTTPClient) HTTPClient) EngineOption

WithMiddleware adds a middleware to the Engine. Middlewares are applied in the order they are added.

type HTTPClient added in v0.0.7

type HTTPClient interface {
	Do(req *http.Request) (*http.Response, error)
}

HTTPClient is an interface that defines the methods required for an HTTP client. This allows for adding middlewares and easier testing.

type HTTPClientFunc added in v0.0.8

type HTTPClientFunc func(req *http.Request) (*http.Response, error)

HTTPClientFunc is a function type that implements the HTTPClient interface.

func (HTTPClientFunc) Do added in v0.0.8

func (f HTTPClientFunc) Do(req *http.Request) (*http.Response, error)

Do executes the HTTP request and returns the response.

type HTTPError added in v0.0.3

type HTTPError struct {
	StatusCode int
	Headers    http.Header
	Message    string
	Details    string
}

HTTPError represents an error response from the API.

func NewHTTPError added in v0.0.3

func NewHTTPError(resp *http.Response, message string) *HTTPError

NewHTTPError creates a new HTTPError from an http.Response.

func (*HTTPError) Error added in v0.0.3

func (e *HTTPError) Error() string

Error implements the error interface.

type HTTPRequester

type HTTPRequester interface {
	HTTPRequest(ctx context.Context, server string) (*http.Request, error)
}

HTTPRequester knows how to create an HTTP request for a specific entity.

type HTTPResponser

type HTTPResponser interface {
	HandleHTTPResponse(*http.Response) error
}

HTTPResponser knows how to handle an HTTP response for a specific entity.

type HexColor added in v1.16.0

type HexColor string

HexColor defines a hexadecimal color.

The leading "#" is optional on the way in and always present on the way out: endpoints are not consistent about sending it, and a colour that fails to decode takes the whole response down with it. The six digits are what the type stores, lower-cased.

func NewHexColor added in v1.16.0

func NewHexColor(color string) *HexColor

NewHexColor creates a pointer to new hexadecimal color. The value is normalised, so "#8BC34A" and "8bc34a" produce the same colour. A value that is not a hexadecimal colour is stored as given: the signature has nowhere to report the error, so the API rejects it instead.

func (HexColor) MarshalJSON added in v1.16.0

func (h HexColor) MarshalJSON() ([]byte, error)

MarshalJSON encodes the type to JSON format, always with the leading "#".

func (HexColor) String added in v1.16.0

func (h HexColor) String() string

String returns the hexadecimal color as a string, with its leading "#".

func (*HexColor) UnmarshalJSON added in v1.16.0

func (h *HexColor) UnmarshalJSON(v []byte) error

UnmarshalJSON validate and parse v into a hexadecimal type. A value with no leading "#" is accepted and normalised; the empty string is not — use OptionalHexColor for a field an endpoint can leave blank.

type ListCountMode added in v1.21.5

type ListCountMode string

ListCountMode selects whether a v3 list endpoint computes the exact number of items matching the request filters, exposed as ListMeta.Page.Count in the response.

The total is derived from a dedicated count query. The API caches it per filter combination, independently of the requested page, so paginating over a result set only pays for it once. The cache is invalidated by any change to the counted entities though, and the narrower the filters the longer it survives: a count scoped to one project outlives one scoped to the whole installation, which any change to any of those entities invalidates. An unscoped exact count is therefore the expensive one to ask for.

https://apidocs.teamwork.com/guides/teamwork/pagination

const (
	// ListCountModeDefault leaves the decision to the API, which currently
	// computes the count. ListMeta.Page.Count is populated, and the API is free
	// to use the total to optimise how it loads large result sets.
	ListCountModeDefault ListCountMode = ""

	// ListCountModeExact asks the API for the exact total, populating
	// ListMeta.Page.Count.
	ListCountModeExact ListCountMode = "exact"

	// ListCountModeSkip asks the API to skip the count query.
	// ListMeta.Page.Count is nil and pagination relies on ListMeta.Page.HasMore
	// alone.
	ListCountModeSkip ListCountMode = "skip"
)

List of possible count modes for ListCountMode.

func (ListCountMode) Apply added in v1.21.5

func (l ListCountMode) Apply(query url.Values)

Apply adds the `skipCounts` parameter to query, unless the mode leaves the decision to the API.

type ListMeta added in v1.21.5

type ListMeta struct {
	// Page contains the pagination details of the response.
	Page ListMetaPage `json:"page"`
}

ListMeta contains the metadata a v3 list endpoint reports alongside the items matching the request filters. List responses embed it as their Meta field.

func (*ListMeta) ResolveCount added in v1.21.5

func (l *ListMeta) ResolveCount(mode ListCountMode)

ResolveCount reconciles the count reported by the API with what the request asked for, clearing Page.Count when the API answered with a lower bound instead of a total. List responses call it from SetRequest, which is the first point at which the request that produced the response is known.

type ListMetaPage added in v1.21.5

type ListMetaPage struct {
	// Count is the exact number of items matching the request filters, across
	// every page. It is nil when the request asked the API to skip the count
	// query with ListCountModeSkip, because the API answers those requests with
	// a lower bound derived from the page rather than a total. Use HasMore to
	// paginate in that case.
	Count *int64 `json:"count"`

	// HasMore indicates whether more items match the request filters beyond the
	// current page.
	HasMore bool `json:"hasMore"`
}

ListMetaPage contains the pagination details a v3 list endpoint reports.

type Money

type Money int64

Money represents a monetary value in the API.

func NewMoney added in v1.5.0

func NewMoney(value float64) Money

NewMoney creates a Money from a float64 value (in major units).

func (*Money) Set

func (m *Money) Set(value float64)

Set sets the value of Money from a float64.

func (Money) Value

func (m Money) Value() float64

Value returns the value of Money as a float64.

type NullableInt64 added in v1.18.0

type NullableInt64 struct {
	// Value is the integer value, ignored when Null is true.
	Value int64
	// Null indicates the field is explicitly set to null.
	Null bool
	// Set indicates the field is present in the payload. When false, the
	// field is omitted entirely.
	Set bool
}

NullableInt64 is a tri-state integer used on writes where the API distinguishes between an unset field, a field set to null, and a field set to a concrete value. Use the helper constructors NewNullableInt64 and NullInt64 instead of building it by hand.

func NewNullableInt64 added in v1.18.0

func NewNullableInt64(value int64) NullableInt64

NewNullableInt64 returns a NullableInt64 set to the given value.

func NullInt64 added in v1.18.0

func NullInt64() NullableInt64

NullInt64 returns a NullableInt64 set to null.

func (NullableInt64) MarshalJSON added in v1.18.0

func (n NullableInt64) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler. Unset values are encoded as a JSON null only when the surrounding tag does not include omitempty, so callers embed NullableInt64 alongside the omitempty tag on the parent field for "omit when not set" semantics.

type OptionalDateTime

type OptionalDateTime time.Time

OptionalDateTime is a type alias for time.Time, used to represent date and time values in the API. The difference is that it will accept empty strings as valid values.

func (OptionalDateTime) MarshalJSON

func (d OptionalDateTime) MarshalJSON() ([]byte, error)

MarshalJSON encodes the OptionalDateTime as a string in the format "2006-01-02T15:04:05Z07:00", or as null when unset.

The zero value must round-trip back to null rather than to the year-1 timestamp time.Time would produce. The API spells "unset" as an empty string on some fields, and encoding/json allocates the pointer before calling UnmarshalJSON, so an unset value reaches this method as a non-nil pointer to the zero time. Emitting the year-1 timestamp there would report a date the API never sent — and would break consumers that derive a JSON Schema from these models, since the value no longer matches the field's declared shape.

func (*OptionalDateTime) UnmarshalJSON

func (d *OptionalDateTime) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes a JSON string into an OptionalDateTime type.

type OptionalHexColor added in v1.29.0

type OptionalHexColor string

OptionalHexColor is HexColor for the fields an endpoint can leave blank. HexColor rejects the empty string, so a model whose colour is only set sometimes cannot use it — the project update endpoints report no colour for a project nobody has rated, and every such row would fail to decode.

Unset round-trips back to the empty string rather than to null, which is how these endpoints spell it. That also keeps the declared string type accurate for consumers deriving a JSON Schema from these models, which is the trap OptionalDateTime cannot avoid, being defined over time.Time.

func (OptionalHexColor) MarshalJSON added in v1.29.0

func (h OptionalHexColor) MarshalJSON() ([]byte, error)

MarshalJSON encodes the type to JSON format, as "#rrggbb" or as the empty string when unset.

func (OptionalHexColor) String added in v1.29.0

func (h OptionalHexColor) String() string

String returns the hexadecimal color with its leading "#", or the empty string when unset.

func (*OptionalHexColor) UnmarshalJSON added in v1.29.0

func (h *OptionalHexColor) UnmarshalJSON(v []byte) error

UnmarshalJSON validates and parses v into an optional hexadecimal type, reading null, the empty string and a bare "#" as unset, and everything else as a HexColor.

The bare "#" is what a HexColor with no value encodes to, on either side of the wire: an endpoint modelling an optional colour with its own non-optional type answers a record that has none with the sign alone.

type OrderMode added in v1.5.0

type OrderMode string

OrderMode specifies the order direction of a list request. Ascending and descending are the only directions the API recognises, so the two constants below are the full set of usable values; the type exists to keep any other string from reaching a request by mistake.

const (
	OrderModeAscending  OrderMode = "asc"
	OrderModeDescending OrderMode = "desc"
)

Supported order modes.

type Relationship added in v0.0.3

type Relationship struct {
	ID   int64          `json:"id"`
	Type string         `json:"type"`
	Meta map[string]any `json:"meta,omitempty"`
}

Relationship describes the relation between the main entity and a sideload type.

type Session

type Session interface {
	Authenticate(context.Context, *http.Request) error
	Server() string
}

Session is an interface that defines the methods required for a session to authenticate requests to the Teamwork Engine.

type Time

type Time time.Time

Time is a type alias for time.Time, used to represent time values in the API.

func (Time) MarshalJSON

func (t Time) MarshalJSON() ([]byte, error)

MarshalJSON encodes the Time as a string in the format "15:04:05".

func (Time) MarshalText

func (t Time) MarshalText() ([]byte, error)

MarshalText encodes the Time as a string in the format "15:04:05".

func (Time) String

func (t Time) String() string

String returns the string representation of the Time in the format "15:04:05".

func (*Time) UnmarshalJSON

func (t *Time) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes a JSON string into a Time type.

func (*Time) UnmarshalText

func (t *Time) UnmarshalText(text []byte) error

UnmarshalText decodes a text string into a Time type. This is required when using Time type as a map key.

The parsing lives here rather than in UnmarshalJSON because the text form is unquoted: feeding it to UnmarshalJSON would ask the JSON decoder to parse 15:04:05 as a bare token, which fails on the first colon.

Directories

Path Synopsis
cmd
next-version command
next-version computes the next semantic version for a release from the changes merged since the last tag, so the bump is derived from what shipped instead of chosen by hand.
next-version computes the next semantic version for a release from the changes merged since the last tag, so the bump is derived from what shipped instead of chosen by hand.
examples
iterator command
middleware command
oauth2 command
internal
sparsefieldsgen command
Command sparsefieldsgen generates typed string enums and per-list containers for v3 sparse fieldsets.
Command sparsefieldsgen generates typed string enums and per-list containers for v3 sparse fieldsets.

Jump to

Keyboard shortcuts

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