Ctrl K
ComunidadGuías…

pkg/runtimeevidence/evidence.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package runtimeevidence

import (
	"bytes"
	"encoding/json"
	"io"
	"net/http"
	"strings"
	"unicode"
	"unicode/utf8"
)

type Adapter string

const (
	RabbitMQ     Adapter = "rabbitmq"
	NATS         Adapter = "nats"
	Vault        Adapter = "vault"
	MaxBodyBytes         = 256 * 1024
	MaxConsumers         = 20
)

type Reason string

func (r Reason) Error() string { return string(r) }

const (
	UnsupportedAdapter   Reason = "unsupported_adapter"
	ResponseTooLarge     Reason = "response_too_large"
	EndpointDenied       Reason = "endpoint_denied"
	RedirectRefused      Reason = "redirect_refused"
	UnexpectedHTTPStatus Reason = "unexpected_http_status"
	UnexpectedShape      Reason = "unexpected_shape"
	MissingAlarmMetrics  Reason = "missing_alarm_metrics"
	InvalidAlarmMetric   Reason = "invalid_alarm_metric"
	AmbiguousAlarmMetric Reason = "ambiguous_alarm_metric"
)

type Facts struct {
	RabbitMQ *RabbitMQFacts `json:"rabbitmq,omitempty"`
	NATS     *NATSFacts     `json:"nats,omitempty"`
	Vault    *VaultFacts    `json:"vault,omitempty"`
}

// Parse interprets one endpoint response, not application or cluster health.
// Errors contain bounded reason codes only; upstream bodies may contain secrets.
func Parse(adapter Adapter, httpStatus int, body []byte) (Facts, error) {
	if len(body) > MaxBodyBytes {
		return Facts{}, ResponseTooLarge
	}
	if httpStatus == http.StatusUnauthorized || httpStatus == http.StatusForbidden {
		return Facts{}, EndpointDenied
	}
	if httpStatus >= 300 && httpStatus < 400 {
		return Facts{}, RedirectRefused
	}
	if httpStatus < 200 || httpStatus > 599 || (adapter != Vault && httpStatus != http.StatusOK) {
		return Facts{}, UnexpectedHTTPStatus
	}
	switch adapter {
	case RabbitMQ:
		facts, err := parseRabbitMQ(body)
		if err != nil {
			return Facts{}, err
		}
		return Facts{RabbitMQ: facts}, nil
	case NATS:
		facts, err := parseNATS(body)
		if err != nil {
			return Facts{}, err
		}
		return Facts{NATS: facts}, nil
	case Vault:
		facts, err := parseVault(body)
		if err != nil {
			return Facts{}, err
		}
		return Facts{Vault: facts}, nil
	default:
		return Facts{}, UnsupportedAdapter
	}
}

func decode(body []byte, target any) error {
	if !utf8.Valid(body) {
		return UnexpectedShape
	}
	decoder := json.NewDecoder(bytes.NewReader(body))
	decoder.UseNumber()
	if checkJSONValue(decoder, 0) != nil {
		return UnexpectedShape
	}
	if _, err := decoder.Token(); err != io.EOF {
		return UnexpectedShape
	}
	if json.Unmarshal(body, target) != nil {
		return UnexpectedShape
	}
	return nil
}

func validIdentifier(value string) bool {
	if len(value) == 0 || len(value) > 256 {
		return false
	}
	for _, r := range value {
		if !unicode.IsPrint(r) {
			return false
		}
	}
	return true
}

// encoding/json otherwise silently lets the last conflicting observation win.
func checkJSONValue(decoder *json.Decoder, depth int) error {
	if depth > 64 {
		return UnexpectedShape
	}
	token, err := decoder.Token()
	if err != nil {
		return UnexpectedShape
	}
	delimiter, ok := token.(json.Delim)
	if !ok {
		return nil
	}
	switch delimiter {
	case '{':
		seen := make(map[string]bool)
		for decoder.More() {
			token, err := decoder.Token()
			key, ok := token.(string)
			if err != nil || !ok {
				return UnexpectedShape
			}
			key = foldJSONKey(key)
			if seen[key] {
				return UnexpectedShape
			}
			seen[key] = true
			if err := checkJSONValue(decoder, depth+1); err != nil {
				return err
			}
		}
	case '[':
		for decoder.More() {
			if err := checkJSONValue(decoder, depth+1); err != nil {
				return err
			}
		}
	default:
		return UnexpectedShape
	}
	if _, err := decoder.Token(); err != nil {
		return UnexpectedShape
	}
	return nil
}

// Match encoding/json's field-name equivalence, including non-ASCII case variants.
func foldJSONKey(key string) string {
	return strings.Map(func(r rune) rune {
		minimum := r
		for next := unicode.SimpleFold(r); next != r; next = unicode.SimpleFold(next) {
			if next < minimum {
				minimum = next
			}
		}
		return minimum
	}, key)
}