Baggage
public struct Baggage
extension Baggage: CustomStringConvertible
A Baggage
is a heterogeneous storage type with value semantics for keyed values in a type-safe fashion.
Its values are uniquely identified via Baggage.Key
s (by type identity). These keys also dictate the type of
value allowed for a specific key-value pair through their associated type Value
.
Defining keys and accessing values
Baggage keys are defined as types, most commonly case-less enums (as no actual instances are actually required)
which conform to the Baggage.Key
protocol:
private enum TestIDKey: Baggage.Key {
typealias Value = String
}
While defining a key, one should also immediately declare an extension on Baggage
,
to allow convenient and discoverable ways to interact with the baggage item, the extension should take the form of:
extension Baggage {
var testID: String? {
get {
self[TestIDKey.self]
} set {
self[TestIDKey.self] = newValue
}
}
}
For consistency, it is recommended to name key types with the ...Key
suffix (e.g. SomethingKey
) and the property
used to access a value identifier by such key the prefix of the key (e.g. something
). Please also observe the usual
Swift naming conventions, e.g. prefer ID
to Id
etc.
Usage
Using a baggage container is fairly straight forward, as it boils down to using the prepared computed properties:
var baggage = Baggage.topLevel
// set a new value
baggage.testID = "abc"
// retrieve a stored value
let testID = baggage.testID ?? "default"
// remove a stored value
baggage.testIDKey = nil
Note that normally a baggage should not be “created” ad-hoc by user code, but rather it should be passed to it from
a runtime. For example, when working in an HTTP server framework, it is most likely that the baggage is already passed
directly or indirectly (e.g. in a FrameworkContext
)
Accessing all values
The only way to access “all” values in a baggage context is by using the forEach
function.
The baggage container on purpose does not expose more functions to prevent abuse and treating it as too much of an
arbitrary value smuggling container, but only make it convenient for tracing and instrumentation systems which need
to access either specific or all items carried inside a baggage.
-
Undocumented
Declaration
Swift
public typealias Key = BaggageKey
-
Creates a new empty “top level” baggage, generally used as an “initial” baggage to immediately be populated with some values by a framework or runtime. Another use case is for tasks starting in the “background” (e.g. on a timer), which don’t have a “request context” per se that they can pick up, and as such they have to create a “top level” baggage for their work.
Usage in frameworks and libraries
This function is really only intended to be used frameworks and libraries, at the “top-level” where a request’s, message’s or task’s processing is initiated. For example, a framework handling requests, should create an empty context when handling a request only to immediately populate it with useful trace information extracted from e.g. request headers.
Usage in applications
Application code should never have to create an empty context during the processing lifetime of any request, and only should create contexts if some processing is performed in the background - thus the naming of this property.
Usually, a framework such as an HTTP server or similar “request handler” would already provide users with a context to be passed along through subsequent calls.
If unsure where to obtain a context from, prefer using
.TODO("Not sure where I should get a context from here?")
, in order to inform other developers that the lack of context passing was not done on purpose, but rather because either not being sure where to obtain a context from, or other framework limitations – e.g. the outer framework not being baggage context aware just yet.Declaration
Swift
public static var topLevel: Baggage { get }
-
A baggage intended as a placeholder until a real value can be passed through a function call.
It should ONLY be used while prototyping or when the passing of the proper context is not yet possible, e.g. because an external library did not pass it correctly and has to be fixed before the proper context can be obtained where the TO-DO is currently used.
Crashing on TO-DO context creation
You may set the
BAGGAGE_CRASH_TODOS
variable while compiling a project in order to make calls to this function crash with a fatal error, indicating where a to-do baggage context was used. This comes in handy when wanting to ensure that a project never ends up using with code initially was written as “was lazy, did not pass context”, yet the project requires context passing to be done correctly throughout the application. Similar checks can be performed at compile time easily using linters (not yet implemented), since it is always valid enough to detect a to-do context being passed as illegal and warn or error when spotted.Example
let baggage = Baggage.TODO("The framework XYZ should be modified to pass us a context here, and we'd pass it along"))
Declaration
Swift
public static func TODO(_ reason: StaticString? = "", function: String = #function, file: String = #file, line: UInt = #line) -> Baggage
Parameters
reason
Informational reason for developers, why a placeholder context was used instead of a proper one,
Return Value
Empty “to-do” baggage context which should be eventually replaced with a carried through one, or
background
. -
Provides type-safe access to the baggage’s values. This API should ONLY be used inside of accessor implementations.
End users rather than using this subscript should use “accessors” the key’s author MUST define, following this pattern:
internal enum TestID: Baggage.Key { typealias Value = TestID } extension Baggage { public internal(set) var testID: TestID? { get { self[TestIDKey.self] } set { self[TestIDKey.self] = newValue } } }
This is in order to enforce a consistent style across projects and also allow for fine grained control over who may set and who may get such property. Just access control to the Key type itself lacks such fidelity.
Note that specific baggage and context types MAY (and usually do), offer also a way to set baggage values, however in the most general case it is not required, as some frameworks may only be able to offer reading.
Declaration
Swift
public subscript<Key>(key: Key.Type) -> Key.Value? where Key : BaggageKey { get set }
-
Number of contained baggage items.
Declaration
Swift
public var count: Int { get }
-
Undocumented
Declaration
Swift
public var isEmpty: Bool { get }
-
Calls the given closure for each item contained in the underlying
Baggage
.Order of those invocations is NOT guaranteed and should not be relied on.
Declaration
Swift
public func forEach(_ body: (AnyBaggageKey, Any) throws -> Void) rethrows
Parameters
body
A closure invoked with the type erased key and value stored for the key in this baggage.
-
A context’s description prints only keys of the contained values. This is in order to prevent spilling a lot of detailed information of carried values accidentally.
Baggage
s are not intended to be printed “raw” but rather inter-operate with tracing, logging and other systems, which can use theforEach
function providing access to its underlying values.Declaration
Swift
public var description: String { get }