native_http.bend

bend-trace-context@0.1.0.0 · 156 lines · 0 definitions

1# bend-trace-context: native HTTP headers
2# =======================================
3#
4# The reference adapter for native HTTP on bend-kit's HTTP package
5# (paymog/bend-kit, bend-kit-http on BendHub). bend-kit keeps the header
6# fields of a request or a response in a Base Map from each field name to its
7# values in arrival order; its parser lowercases the names and trims the
8# values. This module turns such a map into the carrier of trace_context.bend
9# and back, and gives a service its receive-to-send shortcuts around
10# bend-kit's server and client on a caller's source.
11# generation.bend gives the same shortcuts on the host's cryptographic source
12# (NativeHttp.continue_or_start and NativeHttp.send there).
13#
14# The module decides nothing about Trace Context. Which fields are context
15# fields, how they are read and what is written all come from
16# trace_context.bend: the shortcuts are the package's operations on the
17# carrier of a map (laws http_continue and http_send), the carrier keeps
18# every value of every name (law http_carrier), and the map of a carrier
19# holds every value of every name (law http_headers). It imports only Base
20# and trace_context.bend, not bend-kit or the host's entropy, so it builds
21# wherever the package builds and every def in it is pure; a service imports
22# bend-kit for the transport.
23#
24# Names in the Headers namespace are internal: Bend lets any module import
25# them, but they are not a compatibility contract.
26
27import Base
28import bend-trace-context@0.1.0.0/trace_context.bend as TC
29
30# The header fields of a bend-kit request or response: each name, as bend-kit
31# stores it, with its values in arrival order.
32def HeaderMap() -> Data:
33  Map<&2, List<&2, String>>
34
35# A request to send: the context fields the package gave it (`sent`), and
36# the header map to send it with, which holds those fields.
37type Outbound is Data:
38  Outbound{sent: TC.Sent, headers: Map<&2, List<&2, String>>}
39
40# One field for each value of `name`, pushed onto `fields` in order, so that
41# the last value ends up first. A loop: a received head of 64 KiB can hold
42# thousands of values.
43def Headers.push(+name: String, values: List<&2, String>, fields: List<&2, TC.Header>) -> List<&2, TC.Header>:
44  match values:
45    case Nil{}:
46      fields
47    case Con{value, rest}:
48      Headers.push(name, rest, Con{TC.Header{name, value}, fields})
49
50# The fields of every entry pushed onto `fields`, entry by entry.
51def Headers.gather(entries: List<&2, Sigma<&2, &2, String, _ => List<&2, String>>>, fields: List<&2, TC.Header>) ->
52  List<&2, TC.Header>:
53  match entries:
54    case Nil{}:
55      fields
56    case Con{Tuple{name, values}, rest}:
57      Headers.gather(rest, Headers.push(name, values, fields))
58
59# The header fields of a map as a carrier for TC.Context.extract or
60# TC.Context.send: every value of every name, in arrival order within each
61# name (law http_carrier). The order between different names is the map's
62# own, which no Trace Context rule depends on.
63def carrier(headers: HeaderMap()) -> List<&2, TC.Header>:
64  List.reverse(&2, TC.Header, Headers.gather(Map.to_list(&2, List<&2, String>, headers), Nil{}))
65
66# The values a map holds under `name`, none when it has no entry for it.
67def Headers.values(+headers: HeaderMap(), name: String) -> List<&2, String>:
68  Pair.snd(HeaderMap(), List<&2, String>, Map.get(List<&2, String>, Nil{}, headers, name))
69
70# Put one field's value first among its name's values.
71def Headers.add(+headers: HeaderMap(), header: TC.Header) -> HeaderMap():
72  match header:
73    case TC.Header{+name, value}:
74      Map.set(&2, List<&2, String>, headers, name, Con{value, Headers.values(headers, name)})
75
76# Put every field of `fields` first among its name's values, in order. A
77# loop, as Headers.push is.
78def Headers.write(fields: List<&2, TC.Header>, +headers: HeaderMap()) -> HeaderMap():
79  match fields:
80    case Nil{}:
81      headers
82    case Con{header, rest}:
83      Headers.write(rest, Headers.add(headers, header))
84
85# The header map of a carrier, such as the one TC.Sent.carrier gives for a
86# message to send: under each name, the carrier's values of that name in
87# order (law http_headers), as bend-kit's Http.add would add them one by one,
88# with the names as the carrier spells them. The fields are put in from the
89# last, each value first among its name's, which leaves every name's values
90# in order. bend-kit writes one header line for each value.
91def headers(carrier: List<&2, TC.Header>) -> HeaderMap():
92  Headers.write(List.reverse(&2, TC.Header, carrier), Map.new(&2, List<&2, String>))
93
94# The service's own operation for a received message, from the header map of
95# its request, on a caller's source: TC.Context.continue_or_start_with over
96# the extraction of the map's carrier (law http_continue). `base` is the
97# context to keep when the request carries no usable traceparent.
98def continue_or_start_with(~S: Type, ~read: S -> IO(S & Result<&1, &1, U32 & String, U32>), source: S,
99  +limits: TC.Limits, headers: HeaderMap(), base: Maybe<&2, TC.BaseContext>, reception: TC.Reception,
100  sampling: TC.Sampling, +policy: TC.FailurePolicy) -> IO(S & TC.FailurePolicy.result(policy, TC.Service)):
101  TC.Context.continue_or_start_with(~S, ~read, source, TC.Context.extract(limits, carrier(headers), base), reception,
102    sampling, policy)
103
104# The request to send for a message: the message with the map of the fields
105# that TC.Sent.carrier gives.
106def Headers.outbound(+sent: TC.Sent) -> Outbound:
107  Outbound{sent, headers(TC.Sent.carrier(sent))}
108
109# The same for a strict result: a failure stays a failure.
110def Headers.strict(result: Result<&2, &2, TC.GenerationError, TC.Sent>) ->
111  Result<&2, &2, TC.GenerationError, Outbound>:
112  match result:
113    case Fail{error}:
114      Fail{error}
115    case Done{sent}:
116      Done{Headers.outbound(sent)}
117
118# A message sent under `policy`, with its header map.
119def Headers.written(policy: TC.FailurePolicy, result: TC.FailurePolicy.result(policy, TC.Sent)) ->
120  TC.FailurePolicy.result(policy, Outbound):
121  match policy:
122    case TC.Lenient{}:
123      Headers.outbound(result)
124    case TC.Strict{}:
125      Headers.strict(result)
126
127# The source's final state with a message sent under `policy` and its map.
128def Headers.written.done(-S: Type, +policy: TC.FailurePolicy, done: S & TC.FailurePolicy.result(policy, TC.Sent)) ->
129  S & TC.FailurePolicy.result(policy, Outbound):
130  (source, result) = done
131  (source, Headers.written(policy, result))
132
133# Give one request that the service sends the context of a new operation, on
134# a caller's source: TC.Context.send_with on the carrier of `headers`, the
135# request's own header map, and the map of the fields it gives back (law
136# http_send). The request's old context fields go, whatever the case of
137# their names, and the new ones replace them; without a new operation, the
138# map forwards the received pair or carries no context fields.
139def send_with(~S: Type, ~read: S -> IO(S & Result<&1, &1, U32 & String, U32>), source: S, +limits: TC.Limits,
140  service: TC.Service, sampling: TC.Sampling, +policy: TC.FailurePolicy, headers: HeaderMap()) ->
141  IO(S & TC.FailurePolicy.result(policy, Outbound)):
142  IO.bind(S & TC.FailurePolicy.result(policy, TC.Sent), S & TC.FailurePolicy.result(policy, Outbound),
143    TC.Context.send_with(~S, ~read, source, limits, service, sampling, policy, carrier(headers)),
144    done => IO.pure(S & TC.FailurePolicy.result(policy, Outbound), Headers.written.done(S, policy, done)))
145
146# The context fields that the package gave the request.
147def Outbound.sent(outbound: Outbound) -> TC.Sent:
148  match outbound:
149    case Outbound{sent, headers}:
150      sent
151
152# The header map to send the request with.
153def Outbound.headers(outbound: Outbound) -> HeaderMap():
154  match outbound:
155    case Outbound{sent, headers}:
156      headers