1// Copyright 2015 The Prometheus Authors
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14package expfmt
15
16import (
17	"fmt"
18	"io"
19	"net/http"
20
21	"github.com/golang/protobuf/proto"
22	"github.com/matttproud/golang_protobuf_extensions/pbutil"
23	"github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg"
24
25	dto "github.com/prometheus/client_model/go"
26)
27
28// Encoder types encode metric families into an underlying wire protocol.
29type Encoder interface {
30	Encode(*dto.MetricFamily) error
31}
32
33type encoder func(*dto.MetricFamily) error
34
35func (e encoder) Encode(v *dto.MetricFamily) error {
36	return e(v)
37}
38
39// Negotiate returns the Content-Type based on the given Accept header.
40// If no appropriate accepted type is found, FmtText is returned.
41func Negotiate(h http.Header) Format {
42	for _, ac := range goautoneg.ParseAccept(h.Get(hdrAccept)) {
43		// Check for protocol buffer
44		if ac.Type+"/"+ac.SubType == ProtoType && ac.Params["proto"] == ProtoProtocol {
45			switch ac.Params["encoding"] {
46			case "delimited":
47				return FmtProtoDelim
48			case "text":
49				return FmtProtoText
50			case "compact-text":
51				return FmtProtoCompact
52			}
53		}
54		// Check for text format.
55		ver := ac.Params["version"]
56		if ac.Type == "text" && ac.SubType == "plain" && (ver == TextVersion || ver == "") {
57			return FmtText
58		}
59	}
60	return FmtText
61}
62
63// NewEncoder returns a new encoder based on content type negotiation.
64func NewEncoder(w io.Writer, format Format) Encoder {
65	switch format {
66	case FmtProtoDelim:
67		return encoder(func(v *dto.MetricFamily) error {
68			_, err := pbutil.WriteDelimited(w, v)
69			return err
70		})
71	case FmtProtoCompact:
72		return encoder(func(v *dto.MetricFamily) error {
73			_, err := fmt.Fprintln(w, v.String())
74			return err
75		})
76	case FmtProtoText:
77		return encoder(func(v *dto.MetricFamily) error {
78			_, err := fmt.Fprintln(w, proto.MarshalTextString(v))
79			return err
80		})
81	case FmtText:
82		return encoder(func(v *dto.MetricFamily) error {
83			_, err := MetricFamilyToText(w, v)
84			return err
85		})
86	}
87	panic("expfmt.NewEncoder: unknown format")
88}
89