Compare commits

...

1 Commits

Author SHA1 Message Date
Naman Verma
1c00dfa8aa chore: add ability to mark api stability as beta/alpha 2026-09-23 09:26:44 +05:30
6 changed files with 318 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -179,6 +179,7 @@ The `handler.New` function ties the HTTP handler to OpenAPI metadata via `OpenAP
- **SuccessStatusCode**: The HTTP status for successful responses (for example, `http.StatusOK`, `http.StatusCreated`, `http.StatusNoContent`).
- **ErrorStatusCodes**: Additional error status codes beyond the standard ones automatically added by `handler.New`.
- **SecuritySchemes**: Auth mechanisms and scopes required by the operation.
- **Stability**: Maturity marker (`handler.StabilityAlpha`, `handler.StabilityBeta`, `handler.StabilityStable`) emitted as the `x-stability` extension on every operation. Unset is emitted as `stable`.
The generic handler:

View File

@@ -145,6 +145,7 @@ func (provider *provider) addAlertmanagerRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusCreated,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
Deprecated: false,
Stability: handler.StabilityBeta,
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceNotificationChannel.Scope(coretypes.VerbCreate)}),
},
handler.WithResourceDefs(handler.BasicResourceDef{
@@ -173,6 +174,7 @@ func (provider *provider) addAlertmanagerRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest},
Deprecated: false,
Stability: handler.StabilityBeta,
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceNotificationChannel.Scope(coretypes.VerbList)}),
},
handler.WithResourceDefs(handler.BasicResourceDef{
@@ -199,6 +201,7 @@ func (provider *provider) addAlertmanagerRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
Stability: handler.StabilityBeta,
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceNotificationChannel.Scope(coretypes.VerbRead)}),
},
handler.WithResourceDefs(handler.BasicResourceDef{
@@ -226,6 +229,7 @@ func (provider *provider) addAlertmanagerRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
Stability: handler.StabilityBeta,
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceNotificationChannel.Scope(coretypes.VerbUpdate)}),
},
handler.WithResourceDefs(handler.BasicResourceDef{
@@ -253,6 +257,7 @@ func (provider *provider) addAlertmanagerRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
Stability: handler.StabilityBeta,
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceNotificationChannel.Scope(coretypes.VerbDelete)}),
},
handler.WithResourceDefs(handler.BasicResourceDef{
@@ -281,6 +286,7 @@ func (provider *provider) addAlertmanagerRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
Stability: handler.StabilityBeta,
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceNotificationChannel.Scope(coretypes.VerbUpdate)}),
},
handler.WithResourceDefs(handler.BasicResourceDef{
@@ -308,6 +314,7 @@ func (provider *provider) addAlertmanagerRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest},
Deprecated: false,
Stability: handler.StabilityBeta,
SecuritySchemes: newScopedSecuritySchemes([]string{coretypes.ResourceMetaResourceNotificationChannel.Scope(coretypes.VerbCreate)}),
},
handler.WithResourceDefs(handler.BasicResourceDef{

View File

@@ -62,6 +62,10 @@ func (handler *handler) ServeOpenAPI(opCtx openapi.OperationContext) {
opCtx.SetDescription(handler.openAPIDef.Description)
opCtx.SetIsDeprecated(handler.openAPIDef.Deprecated)
if exposer, ok := opCtx.(openapi3.OperationExposer); ok {
exposer.Operation().WithMapOfAnythingItem(openAPIStabilityKey, handler.openAPIDef.Stability.StringValue())
}
// Add security schemes
for _, securityScheme := range handler.openAPIDef.SecuritySchemes {
opCtx.AddSecurity(securityScheme.Name, securityScheme.Scopes...)

View File

@@ -0,0 +1,52 @@
package handler
import (
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/swaggest/openapi-go/openapi3"
)
func TestServeOpenAPIStability(t *testing.T) {
testCases := []struct {
subtestName string
stability Stability
expectedExtensionValue any
}{
{
subtestName: "beta is emitted as x-stability",
stability: StabilityBeta,
expectedExtensionValue: "beta",
},
{
subtestName: "alpha is emitted as x-stability",
stability: StabilityAlpha,
expectedExtensionValue: "alpha",
},
{
subtestName: "unset is emitted as stable",
stability: Stability{},
expectedExtensionValue: "stable",
},
}
for _, testCase := range testCases {
t.Run(testCase.subtestName, func(t *testing.T) {
reflector := openapi3.NewReflector()
opCtx, err := reflector.NewOperationContext(http.MethodGet, "/test")
require.NoError(t, err)
New(func(http.ResponseWriter, *http.Request) {}, OpenAPIDef{
ID: "test",
SuccessStatusCode: http.StatusOK,
Stability: testCase.stability,
}).ServeOpenAPI(opCtx)
require.NoError(t, reflector.AddOperation(opCtx))
operation := reflector.Spec.Paths.MapOfPathItemValues["/test"].MapOfOperationValues["get"]
assert.Equal(t, testCase.expectedExtensionValue, operation.MapOfAnything["x-stability"])
})
}
}

View File

@@ -3,12 +3,32 @@ package handler
import (
"reflect"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/gorilla/mux"
"github.com/swaggest/jsonschema-go"
openapigo "github.com/swaggest/openapi-go"
"github.com/swaggest/rest/openapi"
)
const openAPIStabilityKey string = "x-stability"
var (
StabilityAlpha = Stability{valuer.NewString("alpha")}
StabilityBeta = Stability{valuer.NewString("beta")}
StabilityStable = Stability{valuer.NewString("stable")}
)
// Stability is emitted as the x-stability extension on every operation; unset means stable.
type Stability struct{ valuer.String }
func (stability Stability) StringValue() string {
if stability.IsZero() {
return StabilityStable.String.StringValue()
}
return stability.String.StringValue()
}
// OpenAPIExample is a named example for an OpenAPI operation.
type OpenAPIExample struct {
Name string
@@ -32,6 +52,7 @@ type OpenAPIDef struct {
SuccessStatusCode int
ErrorStatusCodes []int
Deprecated bool
Stability Stability
SecuritySchemes []OpenAPISecurityScheme
}