1 # Header / trailer metadata helpers. Pure. 2 # Metadata maps are Map<&2, List<&2, String>> with lowercase keys. 3 import Base 4 5 def trailer_prefix() -> String: 6 "trailer-" 7 8 def trailers_to_headers.go(xs: List<&2, Sigma<&2, &2, String, _ => List<&2, String>>>, acc: Map<&2, List<&2, String>>) -> Map<&2, List<&2, String>>: 9 match xs: 10 case Nil{}: 11 acc 12 case Con{Tuple{k, v}, rest}: 13 trailers_to_headers.go(rest, Map.set(&2, List<&2, String>, acc, String.append(trailer_prefix(), k), v)) 14 15 # Every key gets the "trailer-" prefix (unary responses carry trailers as headers). 16 def trailers_to_headers(m: Map<&2, List<&2, String>>) -> Map<&2, List<&2, String>>: 17 trailers_to_headers.go(Map.to_list(&2, List<&2, String>, m), Map.new(&2, List<&2, String>)) 18 19 # Adds k (with prefix stripped when want) when its prefix status equals want. 20 def bool_eq(a: Bool, +b: Bool) -> Bool: 21 Bool.pick(Bool, a, b, Bool.not(b)) 22 23 def split.add(take: Bool, +want: Bool, +k: String, v: List<&2, String>, acc: Map<&2, List<&2, String>>) -> Map<&2, List<&2, String>>: 24 match take: 25 case False{}: 26 acc 27 case True{}: 28 Map.set(&2, List<&2, String>, acc, Bool.pick(String, want, String.drop(k, 8n), k), v) 29 30 def split.go(xs: List<&2, Sigma<&2, &2, String, _ => List<&2, String>>>, +want: Bool, acc: Map<&2, List<&2, String>>) -> Map<&2, List<&2, String>>: 31 match xs: 32 case Nil{}: 33 acc 34 case Con{Tuple{+k, v}, rest}: 35 split.go(rest, want, split.add(bool_eq(String.starts_with(k, trailer_prefix()), want), want, k, v, acc)) 36 37 # (non-trailer headers, trailers with the "trailer-" prefix stripped) 38 def split_trailers(h: Map<&2, List<&2, String>>) -> Map<&2, List<&2, String>> & Map<&2, List<&2, String>>: 39 +l = Map.to_list(&2, List<&2, String>, h) 40 (split.go(l, False{}, Map.new(&2, List<&2, String>)), split.go(l, True{}, Map.new(&2, List<&2, String>))) 41 42 # Keys starting with "connect-" belong to the protocol. 43 def is_reserved(+key: String) -> Bool: 44 String.starts_with(key, "connect-") 45 46 # Keys ending in "-bin" carry base64 values. 47 def is_binary(+key: String) -> Bool: 48 String.ends_with(key, "-bin") 49 50 def key_char(+c: U32) -> Bool: 51 Bool.or(Bool.and(U32.is_ge(c, 48), U32.is_le(c, 57)), 52 Bool.or(Bool.and(U32.is_ge(c, 97), U32.is_le(c, 122)), 53 Bool.or(U32.is_eq(c, 95), Bool.or(U32.is_eq(c, 46), U32.is_eq(c, 45))))) 54 55 def key_chars(s: String) -> Bool: 56 match s: 57 case SNil{}: 58 True{} 59 case SCon{Chr{+c}, t}: 60 Bool.and(key_char(c), key_chars(t)) 61 62 # 1*[0-9a-z_.-] 63 def valid_key(+key: String) -> Bool: 64 Bool.and(Bool.not(String.is_empty(key)), key_chars(key))