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
15 changes: 1 addition & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,13 @@ A Go SDK for Switcher API

The **Switcher Client SDK for Go** provides integration with [Switcher-API](https://github.com/switcherapi/switcher-api), enabling feature flag management in Go applications.

> Features marked as **Under development** are part of the current SDK roadmap and may not be available in the repository yet.

### Key Features

- **Clean & Maintainable**: Simple package-level access with an instance-based core
- **Local Mode**: Offline execution using snapshot files from your Switcher-API domain
- **Silent Mode**: Hybrid configuration with automatic fallback for connectivity issues
- **Built-in Testing Helpers (Under development)**: Test-oriented mocking support adapted for Go
- **Built-in Testing Helpers**: Test-oriented mocking support adapted for Go
- **Zero Latency**: Local snapshot execution for high-performance scenarios
- **Secure**: Regex protections and configurable remote transport settings
- **Monitoring**: Execution logging, caching, and error notification hooks

## Quick Start
Expand Down Expand Up @@ -184,8 +181,6 @@ func main() {
SilentMode: 5 * time.Minute,
RestrictRelay: true,
ThrottleMaxWorkers: 2,
RegexMaxBlacklist: 10,
RegexMaxTimeLimit: 100 * time.Millisecond,
Remote: client.RemoteOptions{
CertPath: "./certs/client.pem",
AutoRenewToken: true,
Expand All @@ -212,8 +207,6 @@ func main() {
| `SilentMode` | `time.Duration` | Silent mode retry time before returning to remote mode | `0` |
| `RestrictRelay` | `bool` | Enable relay restrictions in local mode | `true` |
| `ThrottleMaxWorkers` | `int` | Max workers for throttling refresh tasks | runtime-defined |
| `RegexMaxBlacklist` | `int` | Max cached entries for failed regex | `100` |
| `RegexMaxTimeLimit` | `time.Duration` | Regex execution time limit | `3s` |
| `Remote` | `RemoteOptions` | Remote transport settings | `RemoteOptions{}` |

`RemoteOptions` fields:
Expand All @@ -227,12 +220,6 @@ func main() {

**Note:** lower remote connect timeouts help silent mode fall back faster when the upstream is unavailable.

#### Security Features

- **ReDoS Protection (Under development)**: Regex safety features with bounded execution time
- **Time Limits**: Configurable timeouts for regex and remote operations
- **Certificate Support**: PEM client-certificate bundles for secure API connections

## Usage Examples

### Basic Feature Flag Checking
Expand Down
14 changes: 1 addition & 13 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ const (
DefaultLogger = false
DefaultFreeze = false
DefaultRestrictRelay = true
DefaultRegexMaxBlacklist = 100
DefaultRegexMaxTimeLimit = 3 * time.Second
DefaultRemoteConnectTimeout = 300 * time.Millisecond
DefaultRemoteTimeout = 5 * time.Second
)
Expand All @@ -24,7 +22,7 @@ type RemoteOptions struct {
}

// ContextOptions exposes advanced SDK behaviors such as local mode, snapshot management,
// throttling and regex safety configuration. See README advanced configuration for details.
// throttling. See README advanced configuration for details.
type ContextOptions struct {
Local bool
Logger bool
Expand All @@ -34,8 +32,6 @@ type ContextOptions struct {
SilentMode time.Duration
RestrictRelay bool
ThrottleMaxWorkers int
RegexMaxBlacklist int
RegexMaxTimeLimit time.Duration
Remote RemoteOptions
}

Expand Down Expand Up @@ -64,14 +60,6 @@ func (o ContextOptions) withDefaults() ContextOptions {
o.RestrictRelay = DefaultRestrictRelay
}

if o.RegexMaxBlacklist == 0 {
o.RegexMaxBlacklist = DefaultRegexMaxBlacklist
}

if o.RegexMaxTimeLimit == 0 {
o.RegexMaxTimeLimit = DefaultRegexMaxTimeLimit
}

o.Remote = o.Remote.withDefaults()
return o
}
Expand Down
2 changes: 0 additions & 2 deletions context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ func TestBuildContext(t *testing.T) {

assert.Equal(t, DefaultEnvironment, ctx.Environment)
assert.True(t, ctx.Options.RestrictRelay)
assert.Equal(t, DefaultRegexMaxBlacklist, ctx.Options.RegexMaxBlacklist)
assert.Equal(t, DefaultRegexMaxTimeLimit, ctx.Options.RegexMaxTimeLimit)
assert.Equal(t, DefaultRemoteConnectTimeout, ctx.Options.Remote.ConnectTimeout)
assert.Equal(t, DefaultRemoteTimeout, ctx.Options.Remote.Timeout)
})
Expand Down
8 changes: 8 additions & 0 deletions local_strategies_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,14 @@ func TestLocalStrategyOperations(t *testing.T) {
assert.False(t, processLocalStrategy(makeStrategy("REGEX_VALIDATION", "INVALID_OP", []string{`USER_[0-9]{1,2}`}), "USER_11"))
})

t.Run("should avoid catastrophic backtracking for regex strategy", func(t *testing.T) {
assert.False(t, processLocalStrategy(makeStrategy("REGEX_VALIDATION", "EXIST", []string{`^(([a-z])+.)+[A-Z]([a-z])+$`}), "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"))
assert.False(t, processLocalStrategy(makeStrategy("REGEX_VALIDATION", "EXIST", []string{`^(a+)+$`}), "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!"))
assert.False(t, processLocalStrategy(makeStrategy("REGEX_VALIDATION", "EQUAL", []string{`^(a+)+$`}), "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!"))
assert.True(t, processLocalStrategy(makeStrategy("REGEX_VALIDATION", "NOT_EXIST", []string{`^(a+)+$`}), "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!"))
assert.True(t, processLocalStrategy(makeStrategy("REGEX_VALIDATION", "NOT_EQUAL", []string{`^(a+)+$`}), "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!"))
})

t.Run("should return false for unknown strategy", func(t *testing.T) {
assert.False(t, processLocalStrategy(makeStrategy("UNKNOWN", "EQUAL", []string{"USER_1"}), "USER_1"))
})
Expand Down