internal/promapi/promapi.go (70 lines of code) (raw):
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package promapi
import (
"encoding/json"
"net/http"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
promapiv1 "github.com/prometheus/prometheus/web/api/v1"
)
// Redundant code for API compliance below can be DRY'ed up if/when this issue is addressed:
// https://github.com/prometheus/prometheus/issues/14962
// https://prometheus.io/docs/prometheus/latest/querying/api/#format-overview
// Response is the prometheus-compatible Response format.
type Response[T RulesResponseData | AlertsResponseData | GenericResponseData] struct {
Status status `json:"status"`
Data T `json:"data,omitempty"`
ErrorType ErrorType `json:"errorType,omitempty"`
Error string `json:"error,omitempty"`
Warnings []string `json:"warnings,omitempty"`
Infos []string `json:"infos,omitempty"`
}
type RulesResponseData struct {
Groups []*promapiv1.RuleGroup `json:"groups"`
}
type AlertsResponseData struct {
Alerts []*promapiv1.Alert `json:"alerts"`
}
type GenericResponseData interface{}
type ErrorType string
const (
ErrorNone ErrorType = ""
ErrorTimeout ErrorType = "timeout"
ErrorCanceled ErrorType = "canceled"
ErrorExec ErrorType = "execution"
ErrorBadData ErrorType = "bad_data"
ErrorInternal ErrorType = "internal"
ErrorUnavailable ErrorType = "unavailable"
ErrorNotFound ErrorType = "not_found"
)
// https://prometheus.io/docs/prometheus/latest/querying/api/#format-overview
// status is the prometheus-compatible status type.
type status string
const (
statusSuccess status = "success"
statusError status = "error"
)
// writeResponse writes a Response to given responseWriter w if it can, otherwise it logs the error and writes a generic error.
func writeResponse[T RulesResponseData | AlertsResponseData | GenericResponseData](logger log.Logger, w http.ResponseWriter, httpResponseCode int, endpointURI string, resp Response[T]) {
logger = log.With(logger, "endpointURI", endpointURI, "intendedStatusCode", httpResponseCode)
w.Header().Set("Content-Type", "application/json")
jsonResponse, err := json.Marshal(resp)
if err != nil {
_ = level.Error(logger).Log("msg", "failed to marshal Response", "err", err)
w.WriteHeader(http.StatusInternalServerError)
if _, err = w.Write([]byte(`{"status":"error","ErrorType":"internal","error":"failed to marshal Response"}`)); err != nil {
_ = level.Error(logger).Log("msg", "failed to write error Response to responseWriter", "err", err)
}
return
}
w.WriteHeader(httpResponseCode)
if _, err = w.Write(jsonResponse); err != nil {
_ = level.Error(logger).Log("msg", "failed to write Response to responseWriter", "err", err)
}
}
// WriteSuccessResponse writes a successful Response to the given responseWriter w.
func WriteSuccessResponse[T RulesResponseData | AlertsResponseData | promapiv1.PrometheusVersion](logger log.Logger, w http.ResponseWriter, httpResponseCode int, endpointURI string, responseData T) {
writeResponse(logger, w, httpResponseCode, endpointURI, Response[T]{
Status: statusSuccess,
Data: responseData,
})
}
// WriteError writes an error Response to the given responseWriter w.
func WriteError(logger log.Logger, w http.ResponseWriter, errType ErrorType, errMsg string, httpResponseCode int, endpointURI string) {
writeResponse(logger, w, httpResponseCode, endpointURI, Response[GenericResponseData]{
Status: statusError,
ErrorType: errType,
Error: errMsg,
Data: nil,
})
}