The Prometheus monitoring system and time series database.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
prometheus/web/api/v1/openapi_helpers.go

343 lines
10 KiB

// Copyright The Prometheus Authors
// 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 v1
import (
"time"
jsoniter "github.com/json-iterator/go"
"github.com/pb33f/libopenapi/datamodel/high/base"
v3 "github.com/pb33f/libopenapi/datamodel/high/v3"
"github.com/pb33f/libopenapi/orderedmap"
yaml "go.yaml.in/yaml/v4"
"github.com/prometheus/prometheus/promql"
)
// Helper functions for building common structures.
// exampleTime is a reference time used for timestamp examples.
var exampleTime = time.Date(2026, 1, 2, 13, 37, 0, 0, time.UTC)
func boolPtr(b bool) *bool {
return &b
}
func int64Ptr(i int64) *int64 {
return &i
}
type example struct {
name string
value any
}
// exampleMap creates an Examples map from the provided examples.
func exampleMap(exs []example) *orderedmap.Map[string, *base.Example] {
examples := orderedmap.New[string, *base.Example]()
for _, ex := range exs {
examples.Set(ex.name, &base.Example{
Value: createYAMLNode(ex.value),
})
}
return examples
}
func schemaRef(ref string) *base.SchemaProxy {
return base.CreateSchemaProxyRef(ref)
}
func schemaFromType(t string) *base.SchemaProxy {
return base.CreateSchemaProxy(&base.Schema{Type: []string{t}})
}
func stringSchema() *base.SchemaProxy {
return schemaFromType("string")
}
func integerSchema() *base.SchemaProxy {
return base.CreateSchemaProxy(&base.Schema{
Type: []string{"integer"},
Format: "int64",
})
}
func stringSchemaWithDescription(description string) *base.SchemaProxy {
return base.CreateSchemaProxy(&base.Schema{
Type: []string{"string"},
Description: description,
})
}
func stringSchemaWithDescriptionAndExample(description string, example any) *base.SchemaProxy {
return base.CreateSchemaProxy(&base.Schema{
Type: []string{"string"},
Description: description,
Example: createYAMLNode(example),
})
}
func integerSchemaWithDescription(description string) *base.SchemaProxy {
return base.CreateSchemaProxy(&base.Schema{
Type: []string{"integer"},
Format: "int64",
Description: description,
})
}
func integerSchemaWithDescriptionAndExample(description string, example any) *base.SchemaProxy {
return base.CreateSchemaProxy(&base.Schema{
Type: []string{"integer"},
Format: "int64",
Description: description,
Example: createYAMLNode(example),
})
}
func stringArraySchemaWithDescription(description string) *base.SchemaProxy {
return base.CreateSchemaProxy(&base.Schema{
Type: []string{"array"},
Items: &base.DynamicValue[*base.SchemaProxy, bool]{A: stringSchema()},
Description: description,
})
}
func stringArraySchemaWithDescriptionAndExample(description string, example any) *base.SchemaProxy {
return base.CreateSchemaProxy(&base.Schema{
Type: []string{"array"},
Items: &base.DynamicValue[*base.SchemaProxy, bool]{A: stringSchema()},
Description: description,
Example: createYAMLNode(example),
})
}
func statusSchema() *base.SchemaProxy {
successNode := &yaml.Node{Kind: yaml.ScalarNode, Value: "success"}
errorNode := &yaml.Node{Kind: yaml.ScalarNode, Value: "error"}
exampleNode := &yaml.Node{Kind: yaml.ScalarNode, Value: "success"}
return base.CreateSchemaProxy(&base.Schema{
Type: []string{"string"},
Enum: []*yaml.Node{successNode, errorNode},
Description: "Response status.",
Example: exampleNode,
})
}
func warningsSchema() *base.SchemaProxy {
return base.CreateSchemaProxy(&base.Schema{
Type: []string{"array"},
Items: &base.DynamicValue[*base.SchemaProxy, bool]{A: stringSchema()},
Description: "Only set if there were warnings while executing the request. There will still be data in the data field.",
})
}
func infosSchema() *base.SchemaProxy {
return base.CreateSchemaProxy(&base.Schema{
Type: []string{"array"},
Items: &base.DynamicValue[*base.SchemaProxy, bool]{A: stringSchema()},
Description: "Only set if there were info-level annotations while executing the request.",
})
}
func timestampSchema() *base.SchemaProxy {
return base.CreateSchemaProxy(&base.Schema{
OneOf: []*base.SchemaProxy{
base.CreateSchemaProxy(&base.Schema{
Type: []string{"string"},
Format: "date-time",
Description: "RFC3339 timestamp.",
}),
base.CreateSchemaProxy(&base.Schema{
Type: []string{"number"},
Format: "unixtime",
Description: "Unix timestamp in seconds.",
}),
},
Description: "Timestamp in RFC3339 format or Unix timestamp in seconds.",
})
}
func stringSchemaWithConstValue(value string) *base.SchemaProxy {
node := &yaml.Node{Kind: yaml.ScalarNode, Value: value}
return base.CreateSchemaProxy(&base.Schema{
Type: []string{"string"},
Enum: []*yaml.Node{node},
})
}
func dateTimeSchemaWithDescription(description string) *base.SchemaProxy {
return base.CreateSchemaProxy(&base.Schema{
Type: []string{"string"},
Format: "date-time",
Description: description,
})
}
func numberSchemaWithDescription(description string) *base.SchemaProxy {
return base.CreateSchemaProxy(&base.Schema{
Type: []string{"number"},
Format: "double",
Description: description,
})
}
func errorResponse() *v3.Response {
content := orderedmap.New[string, *v3.MediaType]()
content.Set("application/json", &v3.MediaType{
Schema: schemaRef("#/components/schemas/Error"),
})
return &v3.Response{
Description: "Error",
Content: content,
}
}
func noContentResponse() *v3.Response {
return &v3.Response{Description: "No Content"}
}
func responsesNoContent() *v3.Responses {
codes := orderedmap.New[string, *v3.Response]()
codes.Set("204", noContentResponse())
codes.Set("default", errorResponse())
return &v3.Responses{Codes: codes}
}
func pathParam(name, description string, schema *base.SchemaProxy) *v3.Parameter {
return &v3.Parameter{
Name: name,
In: "path",
Description: description,
Required: boolPtr(true),
Schema: schema,
}
}
// createYAMLNode converts Go data to yaml.Node for use in examples.
func createYAMLNode(data any) *yaml.Node {
node := &yaml.Node{}
bytes, _ := yaml.Marshal(data)
_ = yaml.Unmarshal(bytes, node)
return node
}
// formRequestBodyWithExamples creates a form-encoded request body with examples.
func formRequestBodyWithExamples(schemaRef string, examples *orderedmap.Map[string, *base.Example], description string) *v3.RequestBody {
content := orderedmap.New[string, *v3.MediaType]()
mediaType := &v3.MediaType{
Schema: base.CreateSchemaProxyRef("#/components/schemas/" + schemaRef),
}
if examples != nil {
mediaType.Examples = examples
}
content.Set("application/x-www-form-urlencoded", mediaType)
return &v3.RequestBody{
Required: boolPtr(true),
Description: description,
Content: content,
}
}
// jsonResponseWithExamples creates a JSON response with examples.
func jsonResponseWithExamples(schemaRef string, examples *orderedmap.Map[string, *base.Example], description string) *v3.Response {
content := orderedmap.New[string, *v3.MediaType]()
mediaType := &v3.MediaType{
Schema: base.CreateSchemaProxyRef("#/components/schemas/" + schemaRef),
}
if examples != nil {
mediaType.Examples = examples
}
content.Set("application/json", mediaType)
return &v3.Response{
Description: description,
Content: content,
}
}
// responsesWithErrorExamples creates responses with both success and error examples.
func responsesWithErrorExamples(okSchemaRef string, successExamples, errorExamples *orderedmap.Map[string, *base.Example], successDescription, errorDescription string) *v3.Responses {
codes := orderedmap.New[string, *v3.Response]()
codes.Set("200", jsonResponseWithExamples(okSchemaRef, successExamples, successDescription))
codes.Set("default", jsonResponseWithExamples("Error", errorExamples, errorDescription))
return &v3.Responses{Codes: codes}
}
// timestampExamples returns examples for timestamp parameters (RFC3339 and epoch).
func timestampExamples(t time.Time) []example {
return []example{
{"RFC3339", t.Format(time.RFC3339Nano)},
{"epoch", t.Unix()},
}
}
// queryParamWithExample creates a query parameter with examples.
func queryParamWithExample(name, description string, required bool, schema *base.SchemaProxy, examples []example) *v3.Parameter {
param := &v3.Parameter{
Name: name,
In: "query",
Description: description,
Required: &required,
Explode: boolPtr(false),
Schema: schema,
}
if len(examples) > 0 {
param.Examples = exampleMap(examples)
}
return param
}
// marshalToYAMLNode marshals a value using jsoniter (production marshaling) and converts to yaml.Node.
// The result is an inline JSON representation that preserves integer types for timestamps.
func marshalToYAMLNode(v any) *yaml.Node {
jsonAPI := jsoniter.ConfigCompatibleWithStandardLibrary
jsonBytes, err := jsonAPI.Marshal(v)
if err != nil {
panic(err)
}
node := &yaml.Node{}
if err := yaml.Unmarshal(jsonBytes, node); err != nil {
panic(err)
}
return node
}
// vectorExample creates an example for a vector query response using production marshaling.
func vectorExample(v promql.Vector) *yaml.Node {
type response struct {
Status string `json:"status"`
Data struct {
ResultType string `json:"resultType"`
Result promql.Vector `json:"result"`
} `json:"data"`
}
resp := response{Status: "success"}
resp.Data.ResultType = "vector"
resp.Data.Result = v
return marshalToYAMLNode(resp)
}
// matrixExample creates an example for a matrix query response using production marshaling.
func matrixExample(m promql.Matrix) *yaml.Node {
type response struct {
Status string `json:"status"`
Data struct {
ResultType string `json:"resultType"`
Result promql.Matrix `json:"result"`
} `json:"data"`
}
resp := response{Status: "success"}
resp.Data.ResultType = "matrix"
resp.Data.Result = m
return marshalToYAMLNode(resp)
}