Skip to content

Commit 20f5ce5

Browse files
Add server URL normalization hook
Prepends https:// to server URLs when no scheme is provided and strips trailing slashes, improving developer experience for SDK initialization.
1 parent 128c3ef commit 20f5ce5

File tree

3 files changed

+96
-0
lines changed

3 files changed

+96
-0
lines changed

src/glean/api_client/_hooks/registration.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from .types import Hooks
2+
from .server_url_normalizer import ServerURLNormalizerHook
23
from .multipart_fix_hook import MultipartFileFieldFixHook
34
from .agent_file_upload_error_hook import AgentFileUploadErrorHook
45
from .x_glean import XGlean
@@ -15,6 +16,9 @@ def init_hooks(hooks: Hooks):
1516
with an instance of a hook that implements that specific Hook interface
1617
Hooks are registered per SDK instance, and are valid for the lifetime of the SDK instance"""
1718

19+
# Register hook to normalize server URLs (prepend https:// if no scheme provided)
20+
hooks.register_sdk_init_hook(ServerURLNormalizerHook())
21+
1822
# Register hook to fix multipart file field names that incorrectly have '[]' suffix
1923
hooks.register_sdk_init_hook(MultipartFileFieldFixHook())
2024

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""Hook to normalize server URLs, prepending https:// if no scheme is provided."""
2+
3+
import re
4+
from typing import Tuple
5+
from .types import SDKInitHook
6+
from glean.api_client.httpclient import HttpClient
7+
8+
9+
def normalize_server_url(url: str) -> str:
10+
normalized = url
11+
if not re.match(r'^https?://', normalized, re.IGNORECASE):
12+
normalized = f'https://{normalized}'
13+
normalized = normalized.rstrip('/')
14+
return normalized
15+
16+
17+
class ServerURLNormalizerHook(SDKInitHook):
18+
"""Normalizes server URLs by prepending https:// if no scheme is provided."""
19+
20+
def sdk_init(self, base_url: str, client: HttpClient) -> Tuple[str, HttpClient]:
21+
return normalize_server_url(base_url), client
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"""Tests for the server URL normalizer hook."""
2+
3+
from unittest.mock import Mock
4+
5+
import pytest
6+
7+
from src.glean.api_client._hooks.server_url_normalizer import (
8+
ServerURLNormalizerHook,
9+
normalize_server_url,
10+
)
11+
from src.glean.api_client.httpclient import HttpClient
12+
13+
14+
class TestNormalizeServerUrl:
15+
"""Test cases for the normalize_server_url function."""
16+
17+
def test_no_scheme_prepends_https(self):
18+
assert normalize_server_url("example.glean.com") == "https://example.glean.com"
19+
20+
def test_https_preserved(self):
21+
assert normalize_server_url("https://example.glean.com") == "https://example.glean.com"
22+
23+
def test_http_localhost_preserved(self):
24+
assert normalize_server_url("http://localhost:8080") == "http://localhost:8080"
25+
26+
def test_http_non_localhost_preserved(self):
27+
assert normalize_server_url("http://example.glean.com") == "http://example.glean.com"
28+
29+
def test_trailing_slash_stripped(self):
30+
assert normalize_server_url("https://example.glean.com/") == "https://example.glean.com"
31+
32+
def test_multiple_trailing_slashes_stripped(self):
33+
assert normalize_server_url("https://example.glean.com///") == "https://example.glean.com"
34+
35+
def test_no_scheme_with_trailing_slash(self):
36+
assert normalize_server_url("example.glean.com/") == "https://example.glean.com"
37+
38+
def test_url_with_path(self):
39+
assert normalize_server_url("https://example.glean.com/api/v1") == "https://example.glean.com/api/v1"
40+
41+
def test_url_with_path_and_trailing_slash(self):
42+
assert normalize_server_url("https://example.glean.com/api/v1/") == "https://example.glean.com/api/v1"
43+
44+
def test_no_scheme_with_path(self):
45+
assert normalize_server_url("example.glean.com/api/v1") == "https://example.glean.com/api/v1"
46+
47+
def test_case_insensitive_scheme(self):
48+
assert normalize_server_url("HTTPS://example.glean.com") == "HTTPS://example.glean.com"
49+
assert normalize_server_url("HTTP://localhost") == "HTTP://localhost"
50+
51+
52+
class TestServerURLNormalizerHook:
53+
"""Test cases for the ServerURLNormalizerHook."""
54+
55+
def setup_method(self):
56+
self.hook = ServerURLNormalizerHook()
57+
self.mock_client = Mock(spec=HttpClient)
58+
59+
def test_sdk_init_normalizes_url(self):
60+
result_url, result_client = self.hook.sdk_init("example.glean.com", self.mock_client)
61+
assert result_url == "https://example.glean.com"
62+
assert result_client == self.mock_client
63+
64+
def test_sdk_init_preserves_client(self):
65+
result_url, result_client = self.hook.sdk_init("https://example.glean.com", self.mock_client)
66+
assert result_url == "https://example.glean.com"
67+
assert result_client is self.mock_client
68+
69+
70+
if __name__ == "__main__":
71+
pytest.main([__file__])

0 commit comments

Comments
 (0)