Module: LogBrew::TelemetryContextValue

Defined in:
lib/logbrew/telemetry_context_value.rb

Overview

Shared validation and detachment rules for schema-v1 telemetry context.

Constant Summary collapse

MAX_CONTEXT_STRING =
256
MAX_CONTEXT_ID =
200
MAX_TAGS =
32
MAX_TAG_KEY =
64
TAG_KEY =
/\A[A-Za-z][A-Za-z0-9_.-]*\z/.freeze
CONTROL_CHARACTERS =
/[\u0000-\u001f\u007f-\u009f]/.freeze
LOWER_HEX =
/\A[0-9a-f]+\z/.freeze

Class Method Summary collapse

Class Method Details

.deep_copy(value) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/logbrew/telemetry_context_value.rb', line 101

def deep_copy(value)
  case value
  when Hash
    value.each_with_object({}) { |(key, item), copy| copy[key.dup] = deep_copy(item) }
  when Array
    value.map { |item| deep_copy(item) }
  when String
    value.dup
  else
    value
  end
end

.deep_freeze(value) ⇒ Object



114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/logbrew/telemetry_context_value.rb', line 114

def deep_freeze(value)
  case value
  when Hash
    value.each do |key, item|
      key.freeze
      deep_freeze(item)
    end
  when Array
    value.each { |item| deep_freeze(item) }
  end
  value.freeze
end

.invalid(message) ⇒ Object



127
128
129
# File 'lib/logbrew/telemetry_context_value.rb', line 127

def invalid(message)
  SdkError.new("validation_error", message)
end

.object(value, label) ⇒ Object



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/logbrew/telemetry_context_value.rb', line 16

def object(value, label)
  raise invalid("#{label} must be an object") unless value.is_a?(Hash)

  value.each_with_object({}) do |(key, item), normalized|
    unless key.is_a?(String) || key.is_a?(Symbol)
      raise invalid("#{label} keys must be strings")
    end

    normalized_key = key.to_s
    if normalized.key?(normalized_key)
      raise invalid("#{label} contains duplicate field #{normalized_key}")
    end
    normalized[normalized_key] = item
  end
end

.optional_id(value, label) ⇒ Object



64
65
66
67
68
# File 'lib/logbrew/telemetry_context_value.rb', line 64

def optional_id(value, label)
  return nil if value.nil?

  required_id(value, label)
end

.optional_span_id(value, label) ⇒ Object



78
79
80
81
82
# File 'lib/logbrew/telemetry_context_value.rb', line 78

def optional_span_id(value, label)
  return nil if value.nil?

  span_id(value, label)
end

.optional_string(value, label, maximum = MAX_CONTEXT_STRING) ⇒ Object



54
55
56
57
58
# File 'lib/logbrew/telemetry_context_value.rb', line 54

def optional_string(value, label, maximum = MAX_CONTEXT_STRING)
  return nil if value.nil?

  required_string(value, label, maximum)
end

.reject_unknown_fields(value, allowed, label) ⇒ Object



32
33
34
35
36
37
# File 'lib/logbrew/telemetry_context_value.rb', line 32

def reject_unknown_fields(value, allowed, label)
  unknown = value.keys - allowed
  return if unknown.empty?

  raise invalid("#{label} has unknown field #{unknown.sort.first}")
end

.require_tag_count(count) ⇒ Object



95
96
97
98
99
# File 'lib/logbrew/telemetry_context_value.rb', line 95

def require_tag_count(count)
  unless count.between?(1, MAX_TAGS)
    raise invalid("telemetry context must contain 1 to at most #{MAX_TAGS} tags")
  end
end

.required_id(value, label) ⇒ Object



60
61
62
# File 'lib/logbrew/telemetry_context_value.rb', line 60

def required_id(value, label)
  required_string(value, label, MAX_CONTEXT_ID)
end

.required_string(value, label, maximum = MAX_CONTEXT_STRING) ⇒ Object



39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/logbrew/telemetry_context_value.rb', line 39

def required_string(value, label, maximum = MAX_CONTEXT_STRING)
  raise invalid("#{label} must be a string") unless value.is_a?(String)

  normalized = utf8_string(value, label).strip
  raise invalid("#{label} must not be empty") if normalized.empty? || !normalized.match?(/\S/)
  if normalized.length > maximum
    raise invalid("#{label} must contain at most #{maximum} characters")
  end
  if normalized.match?(CONTROL_CHARACTERS)
    raise invalid("#{label} must not contain control characters")
  end

  normalized
end

.span_id(value, label) ⇒ Object



74
75
76
# File 'lib/logbrew/telemetry_context_value.rb', line 74

def span_id(value, label)
  normalized_hex_id(value, 16, "0" * 16, label)
end

.tag_key(value) ⇒ Object



84
85
86
87
88
89
90
91
92
93
# File 'lib/logbrew/telemetry_context_value.rb', line 84

def tag_key(value)
  raise invalid("tag key must be a string") unless value.is_a?(String)

  normalized = utf8_string(value, "tag key")
  unless normalized.length <= MAX_TAG_KEY && normalized.match?(TAG_KEY)
    raise invalid("tag key #{normalized} must start with a letter and contain only letters, numbers, _, ., or -")
  end

  normalized
end

.trace_id(value, label = "traceId") ⇒ Object



70
71
72
# File 'lib/logbrew/telemetry_context_value.rb', line 70

def trace_id(value, label = "traceId")
  normalized_hex_id(value, 32, "0" * 32, label)
end