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
95 changes: 95 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Remember that each namespace requires its own authentication token type as descr
* [Server Selection](#server-selection)
* [Custom HTTP Client](#custom-http-client)
* [Special Types](#special-types)
* [Experimental Features and Deprecation Testing](#experimental-features-and-deprecation-testing)
* [Development](#development)
* [Maturity](#maturity)
* [Contributions](#contributions)
Expand Down Expand Up @@ -1499,6 +1500,100 @@ d6 := types.MustDateFromString("2019-01-01") // returns types.Date and panics on
```
<!-- End Special Types [types] -->

## Experimental Features and Deprecation Testing

The SDK provides options to test upcoming API changes before they become the default behavior. This is useful for:

- **Testing experimental features** before they are generally available
- **Preparing for deprecations** by excluding deprecated endpoints ahead of their removal

### Configuration Options

You can configure these options either via environment variables or SDK constructor options:

#### Using Environment Variables

```bash
export X_GLEAN_EXCLUDE_DEPRECATED_AFTER="2026-10-15"
export X_GLEAN_INCLUDE_EXPERIMENTAL="true"
```

```go
package main

import (
"context"
"log"
"os"

apiclientgo "github.com/gleanwork/api-client-go"
"github.com/gleanwork/api-client-go/models/components"
)

func main() {
ctx := context.Background()

s := apiclientgo.New(
apiclientgo.WithSecurity(os.Getenv("GLEAN_API_TOKEN")),
apiclientgo.WithInstance(os.Getenv("GLEAN_INSTANCE")),
)

res, err := s.Client.Search.Query(ctx, components.SearchRequest{
Query: "test",
}, nil)
if err != nil {
log.Fatal(err)
}
// Headers are automatically set based on environment variables
log.Println(res)
}
```

#### Using SDK Constructor Options

```go
package main

import (
"context"
"log"
"os"

apiclientgo "github.com/gleanwork/api-client-go"
"github.com/gleanwork/api-client-go/models/components"
)

func main() {
ctx := context.Background()

s := apiclientgo.New(
apiclientgo.WithSecurity(os.Getenv("GLEAN_API_TOKEN")),
apiclientgo.WithInstance(os.Getenv("GLEAN_INSTANCE")),
apiclientgo.WithExcludeDeprecatedAfter("2026-10-15"),
apiclientgo.WithIncludeExperimental(true),
)

res, err := s.Client.Search.Query(ctx, components.SearchRequest{
Query: "test",
}, nil)
if err != nil {
log.Fatal(err)
}
log.Println(res)
}
```

### Option Reference

| Option | Environment Variable | Type | Description |
| ------ | -------------------- | ---- | ----------- |
| `WithExcludeDeprecatedAfter` | `X_GLEAN_EXCLUDE_DEPRECATED_AFTER` | `string` (date) | Exclude API endpoints that will be deprecated after this date (format: `YYYY-MM-DD`). Use this to test your integration against upcoming deprecations. |
| `WithIncludeExperimental` | `X_GLEAN_INCLUDE_EXPERIMENTAL` | `bool` | When `true`, enables experimental API features that are not yet generally available. Use this to preview and test new functionality. |

> **Note:** Environment variables take precedence over SDK constructor options when both are set.

> **Warning:** Experimental features may change or be removed without notice. Do not rely on experimental features in production environments.

<!-- Placeholder for Future Speakeasy SDK Sections -->

# Development
Expand Down
3 changes: 3 additions & 0 deletions internal/hooks/registration.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@ package hooks
func initHooks(h *Hooks) {
agentFileUploadHook := &AgentFileUploadHook{}
h.registerAfterErrorHook(agentFileUploadHook)

// Register the X-Glean header hook for experimental features and deprecation testing
h.registerBeforeRequestHook(&XGleanHook{})
}
32 changes: 32 additions & 0 deletions internal/hooks/x_glean_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package hooks

import "sync"

// XGleanConfig holds configuration for X-Glean headers.
// This is used to pass configuration from SDK options to the hook.
type XGleanConfig struct {
// ExcludeDeprecatedAfter excludes API endpoints that will be deprecated after this date.
// Format: YYYY-MM-DD (e.g., "2026-10-15")
ExcludeDeprecatedAfter string

// IncludeExperimental enables experimental API features when true.
IncludeExperimental bool
}

// xGleanConfigs maps SDK instances to their X-Glean configuration.
// Uses a sync.Map for thread-safe access across multiple SDK instances.
var xGleanConfigs sync.Map

// SetXGleanConfig stores the X-Glean configuration for a given SDK instance.
func SetXGleanConfig(sdk any, config XGleanConfig) {
xGleanConfigs.Store(sdk, config)
}

// GetXGleanConfig retrieves the X-Glean configuration for a given SDK instance.
// Returns an empty config if none is set.
func GetXGleanConfig(sdk any) XGleanConfig {
if config, ok := xGleanConfigs.Load(sdk); ok {
return config.(XGleanConfig)
}
return XGleanConfig{}
}
52 changes: 52 additions & 0 deletions internal/hooks/x_glean_hook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package hooks

import (
"net/http"
"os"
)

// XGleanHook is a beforeRequest hook that sets X-Glean headers for
// experimental features and deprecation testing.
type XGleanHook struct{}

// BeforeRequest sets the X-Glean-Exclude-Deprecated-After and X-Glean-Experimental
// headers based on environment variables or SDK configuration.
// Environment variables take precedence over SDK options.
func (h *XGleanHook) BeforeRequest(hookCtx BeforeRequestContext, req *http.Request) (*http.Request, error) {
// Get SDK configuration
sdkConfig := GetXGleanConfig(hookCtx.SDK)

// Get deprecatedAfter value - environment variable takes precedence
deprecatedAfter := getFirstValue(
os.Getenv("X_GLEAN_EXCLUDE_DEPRECATED_AFTER"),
sdkConfig.ExcludeDeprecatedAfter,
)

// Get experimental value - environment variable takes precedence
experimentalEnv := os.Getenv("X_GLEAN_INCLUDE_EXPERIMENTAL")
experimentalValue := ""
if experimentalEnv != "" {
experimentalValue = experimentalEnv
} else if sdkConfig.IncludeExperimental {
experimentalValue = "true"
}

// Set headers if values are present
if deprecatedAfter != "" {
req.Header.Set("X-Glean-Exclude-Deprecated-After", deprecatedAfter)
}

if experimentalValue != "" {
req.Header.Set("X-Glean-Experimental", experimentalValue)
}

return req, nil
}

// getFirstValue returns the first non-empty value from the provided arguments.
func getFirstValue(aValue, bValue string) string {
if aValue != "" {
return aValue
}
return bValue
}
Loading