blob: 8aaffb830b84db23fd0fdc31f93bec56ee7087cf [file] [log] [blame]
# Copyright 2016 The LUCI Authors. All rights reserved.
# Use of this source code is governed under the Apache License, Version 2.0
# that can be found in the LICENSE file.
import re
import string
import types
_ALNUM_CHARS = string.ascii_letters + string.digits
_SEGMENT_RE_BASE = r'[a-zA-Z0-9][a-zA-Z0-9:_\-.]*'
_STREAM_NAME_RE = re.compile('^(' + _SEGMENT_RE_BASE + ')(/' +
_SEGMENT_RE_BASE + ')*$')
_MAX_STREAM_NAME_LENGTH = 4096
_MAX_TAG_KEY_LENGTH = 64
_MAX_TAG_VALUE_LENGTH = 4096
def validate_stream_name(v, maxlen=None):
"""Verifies that a given stream name is valid.
Args:
v (str): The stream name string.
Raises:
ValueError if the stream name is invalid.
"""
maxlen = maxlen or _MAX_STREAM_NAME_LENGTH
if len(v) > maxlen:
raise ValueError('Maximum length exceeded (%d > %d)' % (len(v), maxlen))
if _STREAM_NAME_RE.match(v) is None:
raise ValueError('Invalid stream name')
def validate_tag(key, value):
"""Verifies that a given tag key/value is valid.
Args:
k (str): The tag key.
v (str): The tag value.
Raises:
ValueError if the tag is not valid.
"""
validate_stream_name(key, maxlen=_MAX_TAG_KEY_LENGTH)
validate_stream_name(value, maxlen=_MAX_TAG_VALUE_LENGTH)
def normalize(v, prefix=None):
"""Given a string, "v", mutate it into a valid stream name.
This operates by replacing invalid stream naem characters with underscores (_)
when encountered.
A special case is when "v" begins with an invalid character. In this case, we
will replace it with the "prefix", if one is supplied.
See _STREAM_NAME_RE for a description of a valid stream name.
Raises:
ValueError: If normalization could not be successfully performed.
"""
if len(v) == 0:
if not prefix:
raise ValueError('Cannot normalize empty name with no prefix.')
v = prefix
else:
out = []
for i, ch in enumerate(v):
if i == 0 and not _is_valid_stream_char(ch, first=True):
# The first letter is special, and must be alphanumeric.
# If we have a prefix, prepend that to the resulting string.
if prefix is None:
raise ValueError('Name has invalid beginning, and no prefix was '
'provided.')
out.append(prefix)
if not _is_valid_stream_char(ch):
ch = '_'
out.append(ch)
v = ''.join(out)
# Validate the resulting string.
validate_stream_name(v)
return v
def _is_valid_stream_char(ch, first=False):
"""Returns (bool): True if a character is alphanumeric.
The first character must be alphanumeric, matching [a-zA-Z0-9].
Additional characters must either be alphanumeric or one of: (: _ - .).
Args:
ch (str): the character to evaluate.
first (bool): if true, apply special first-character constraints.
"""
# Alphanumeric check.
if ch in _ALNUM_CHARS:
return True
if first:
# The first character must be alphanumeric.
return False
# Check additional middle-name characters:
return ch in ':_-./'