Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 48 additions & 1 deletion client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"net/http"
"strconv"
"sync"

"github.com/go-resty/resty/v2"
)
Expand All @@ -14,13 +15,24 @@ type Client struct {
master_api_key string
baseURL string
client *resty.Client

// Resolvers are cached because building one costs three requests and callers need
// one per entity they write. Keyed on project and entity, and guarded by
// resolverMu since callers such as the Terraform provider work concurrently.
resolverMu sync.Mutex
resolverCache map[string]*MetadataFieldResolver
}

func NewClient(masterAPIKey string, baseURL string) *Client {
if baseURL == "" {
baseURL = BaseAPIURL
}
c := &Client{master_api_key: masterAPIKey, baseURL: baseURL, client: resty.New()}
c := &Client{
master_api_key: masterAPIKey,
baseURL: baseURL,
client: resty.New(),
resolverCache: map[string]*MetadataFieldResolver{},
}
c.client.SetHeaders(map[string]string{
"Accept": "application/json",
"Content-type": "application/json",
Expand Down Expand Up @@ -132,9 +144,44 @@ func (c *Client) GetFeature(featureUUID string) (*Feature, error) {
return nil, err
}
feature.ProjectUUID = project.UUID

// `features/get-by-uuid/` omits metadata, so it has to be read
// from the project scoped retrieve endpoint instead.
if feature.ID != nil {
metadata, err := c.getFeatureMetadata(*feature.ProjectID, *feature.ID)
if err != nil {
return nil, err
}
feature.Metadata = metadata
}

return &feature, nil
}

// getFeatureMetadata reads just the metadata of a feature from the project scoped
// retrieve endpoint.
//
// It decodes into an anonymous struct rather than a Feature so that the response cannot
// overwrite fields the caller has already populated.
func (c *Client) getFeatureMetadata(projectID, featureID int64) (*[]Metadata, error) {
url := fmt.Sprintf("%s/projects/%d/features/%d/", c.baseURL, projectID, featureID)
result := struct {
Metadata *[]Metadata `json:"metadata"`
}{}

resp, err := c.client.R().SetResult(&result).Get(url)

if err != nil {
return nil, err
}

if !resp.IsSuccess() {
return nil, fmt.Errorf("flagsmithapi: Error getting feature metadata: %s", resp)
}

return result.Metadata, nil
}

func (c *Client) CreateFeature(feature *Feature) error {
if feature.ProjectID == nil {
projectID, err := c.getProjectID(feature.ProjectUUID)
Expand Down
34 changes: 34 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package flagsmithapi

import (
"fmt"
"strings"
)

type FeatureNotFoundError struct {
Expand All @@ -20,6 +21,39 @@ type UserNotFoundError struct {
email string
}

type MetadataFieldNotFoundError struct {
Name string
Entity string
KnownNames []string
}
type MetadataFieldNotBoundError struct {
Name string
Entity string
}
type MetadataFieldInvalidValueError struct {
Name string
FieldType string
Value string
Reason string
}

func (e MetadataFieldNotFoundError) Error() string {
if len(e.KnownNames) == 0 {
return fmt.Sprintf("flagsmithapi: no custom fields are enabled for %ss in this project", e.Entity)
}
return fmt.Sprintf("flagsmithapi: no custom field named '%s' is available for %ss in this project (available: %s)",
e.Name, e.Entity, strings.Join(e.KnownNames, ", "))
}
Comment thread
matthewelwell marked this conversation as resolved.

func (e MetadataFieldNotBoundError) Error() string {
return fmt.Sprintf("flagsmithapi: the custom field '%s' exists but is not enabled for %ss",
e.Name, e.Entity)
}

func (e MetadataFieldInvalidValueError) Error() string {
return fmt.Sprintf("flagsmithapi: invalid value for custom field '%s': %s", e.Name, e.Reason)
}

func (e FeatureNotFoundError) Error() string {
return fmt.Sprintf("flagsmithapi: feature '%s' not found", e.featureUUID)
}
Expand Down
Loading
Loading