summaryrefslogtreecommitdiff
path: root/vendor/open-telemetry/exporter-otlp/HttpEndpointResolver.php
blob: b5034642731bce62ef2209f6d82f8066d5b5d060 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
<?php

declare(strict_types=1);

namespace OpenTelemetry\Contrib\Otlp;

use InvalidArgumentException;
use OpenTelemetry\API\Signals;
use OpenTelemetry\SDK\Common\Adapter\HttpDiscovery\MessageFactoryResolver;
use OpenTelemetry\SDK\Common\Http\Psr\Message\FactoryResolverInterface;
use Psr\Http\Message\UriInterface;

/**
 * Resolves non-signal-specific OTLP HTTP endpoints to signal-specific ones according to the specification.
 * @see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#endpoint-urls-for-otlphttp
 */
class HttpEndpointResolver implements HttpEndpointResolverInterface
{
    private const SCHEME_ATTRIBUTE = 'scheme';
    private const HOST_ATTRIBUTE = 'host';
    private const PORT_ATTRIBUTE = 'port';
    private const USER_ATTRIBUTE = 'user';
    private const PASS_ATTRIBUTE = 'pass';
    private const PATH_ATTRIBUTE = 'path';
    private const DEFAULT_SCHEME = 'https';
    private const ROOT_PATH = '/';

    private FactoryResolverInterface $httpFactoryResolver;

    public function __construct(?FactoryResolverInterface $httpFactoryResolver = null)
    {
        $this->httpFactoryResolver = $httpFactoryResolver ?? MessageFactoryResolver::create();
    }

    public static function create(?FactoryResolverInterface $httpFactoryResolver = null): self
    {
        return new self($httpFactoryResolver);
    }

    public function resolve(string $endpoint, string $signal): UriInterface
    {
        $components = self::parseEndpoint($endpoint);

        return self::addPort(
            self::addUserInfo(
                $this->createDefaultUri($components, $signal),
                $components
            ),
            $components
        );
    }

    public function resolveToString(string $endpoint, string $signal): string
    {
        return (string) $this->resolve($endpoint, $signal);
    }

    private function createUri(): UriInterface
    {
        return $this->httpFactoryResolver->resolveUriFactory()
            ->createUri();
    }

    private function createDefaultUri(array $components, string $signal): UriInterface
    {
        if (isset($components[self::SCHEME_ATTRIBUTE])) {
            self::validateScheme($components[self::SCHEME_ATTRIBUTE]);
        }

        return $this->createUri()
            ->withScheme($components[self::SCHEME_ATTRIBUTE] ?? self::DEFAULT_SCHEME)
            ->withPath(self::resolvePath($components[self::PATH_ATTRIBUTE] ?? self::ROOT_PATH, $signal))
            ->withHost($components[self::HOST_ATTRIBUTE]);
    }

    private static function validateScheme(string $protocol): void
    {
        if (!in_array($protocol, HttpEndpointResolverInterface::VALID_SCHEMES, true)) {
            throw new InvalidArgumentException(sprintf(
                'Expected protocol to be http or https, given: "%s"',
                $protocol
            ));
        }
    }

    private static function validateSignal(string $signal): void
    {
        if (!in_array($signal, Signals::SIGNALS)) {
            throw new InvalidArgumentException(sprintf(
                'Signal must be one of "%s". Given "%s"',
                implode(', ', Signals::SIGNALS),
                $signal
            ));
        }
    }

    private static function parseEndpoint(string $endpoint): array
    {
        $result = parse_url($endpoint);

        if (!is_array($result) || !isset($result[self::HOST_ATTRIBUTE])) {
            throw new InvalidArgumentException(sprintf(
                'Failed to parse endpoint "%s"',
                $endpoint
            ));
        }

        return $result;
    }

    private static function addUserInfo(UriInterface $uri, array $components): UriInterface
    {
        if (isset($components[self::USER_ATTRIBUTE])) {
            $uri = $uri->withUserInfo(
                $components[self::USER_ATTRIBUTE],
                $components[self::PASS_ATTRIBUTE] ?? null
            );
        }

        return $uri;
    }

    private static function addPort(UriInterface $uri, array $components): UriInterface
    {
        if (isset($components[self::PORT_ATTRIBUTE])) {
            $uri = $uri->withPort(
                $components[self::PORT_ATTRIBUTE]
            );
        }

        return $uri;
    }

    private static function resolvePath(string $path, string $signal): string
    {
        self::validateSignal($signal);

        return str_replace('//', '/', sprintf('%s/%s', $path, self::getDefaultPath($signal)));
    }

    private static function getDefaultPath(string $signal): string
    {
        return HttpEndpointResolverInterface::DEFAULT_PATHS[$signal];
    }
}