Skip to content

Commit

Permalink
Finish baggage -> context rename (#53)
Browse files Browse the repository at this point in the history
## Summary

For 1.0, the rename from baggage to context didn't cover everything -
this PR finishes the rename to make things consistent.

Also renamed some stale files.

## Test Plan

N/A
  • Loading branch information
czechboy0 authored Jan 2, 2025
1 parent fe8bd92 commit 8946c93
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 69 deletions.
7 changes: 0 additions & 7 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,6 @@ A good patch is:
3. Documented, adding API documentation as needed to cover new functions and properties.
4. Accompanied by a great commit message, using our commit message template.

### Commit Message Template

We require that your commit messages match our template. The easiest way to do that is to get git to help you by explicitly using the template. To do that, `cd` to the root of our repository and run:

git config commit.template dev/git.commit.template


### Run CI checks locally

You can run the Github Actions workflows locally using [act](https://github.com/nektos/act). For detailed steps on how to do this please see [https://github.com/swiftlang/github-workflows?tab=readme-ov-file#running-workflows-locally](https://github.com/swiftlang/github-workflows?tab=readme-ov-file#running-workflows-locally).
Expand Down
11 changes: 0 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,3 @@ targets: [
// ...
]
```

## Contributing

Please make sure to run the `./scripts/soundness.sh` script when contributing, it checks formatting and similar things.

You can ensure it always runs and passes before you push by installing a pre-push hook with git:

```
echo './scripts/soundness.sh' > .git/hooks/pre-push
chmod +x .git/hooks/pre-push
```
5 changes: 2 additions & 3 deletions Sources/ServiceContextModule/Docs.docc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@ Common currency type for type-safe and Swift concurrency aware context propagati

## Overview


``ServiceContext`` is a minimal (zero-dependency) context propagation container, intended to "carry" baggage items
``ServiceContext`` is a minimal (zero-dependency) context propagation container, intended to "carry" context items
for purposes of cross-cutting tools to be built on top of it.

It is modeled after the concepts explained in [W3C Baggage](https://w3c.github.io/baggage/) and the
in the spirit of [Tracing Plane](https://cs.brown.edu/~jcmace/papers/mace18universal.pdf)'s "Baggage Context" type,
although by itself it does not define a specific serialization format.

See https://github.com/apple/swift-distributed-tracing for actual instrument types and implementations which can be used to
deploy various cross-cutting instruments all reusing the same baggage type. More information can be found in the
deploy various cross-cutting instruments all reusing the same context type. More information can be found in the
[SSWG meeting notes](https://gist.github.com/ktoso/4d160232407e4d5835b5ba700c73de37#swift-baggage-context--distributed-tracing).

> Automatic propagation through task-locals by using `ServiceContext.current` is supported in Swift >= 5.5
Expand Down
94 changes: 48 additions & 46 deletions Sources/ServiceContextModule/ServiceContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
/// }
///
/// While defining a key, one should also immediately declare an extension on `ServiceContext` to allow convenient and discoverable ways to interact
/// with the baggage item. The extension should take the form of:
/// with the context item. The extension should take the form of:
///
/// extension ServiceContext {
/// var testID: String? {
Expand All @@ -43,103 +43,105 @@
/// 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:
/// Using a context container is fairly straight forward, as it boils down to using the prepared computed properties:
///
/// var baggage = ServiceContext.topLevel
/// var context = ServiceContext.topLevel
/// // set a new value
/// baggage.testID = "abc"
/// context.testID = "abc"
/// // retrieve a stored value
/// let testID = baggage.testID ?? "default"
/// let testID = context.testID ?? "default"
/// // remove a stored value
/// baggage.testIDKey = nil
/// context.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
/// Note that normally a context should not be "created" ad-hoc by user code, but rather it should be passed to it from
/// a runtime. A `ServiceContext` may already be available to you through ServiceContext.$current when using structured concurrency.
/// Otherwise, for example when working in an HTTP server framework, it is most likely that the baggage is already passed
/// Otherwise, for example when working in an HTTP server framework, it is most likely that the context is already passed
/// directly or indirectly (e.g. in a `FrameworkContext`).
///
/// ### Accessing all values
///
/// The only way to access "all" values in a baggage is by using the `forEach` function.
/// The only way to access "all" values in a context is by using the `forEach` function.
/// `ServiceContext` does not expose more functions on purpose 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.
/// to access either specific or all items carried inside a context.
public struct ServiceContext: Sendable {
private var _storage = [AnyServiceContextKey: Sendable]()

/// Internal on purpose, please use ``ServiceContext/TODO(_:function:file:line:)`` or ``ServiceContext/topLevel`` to create an "empty" baggage,
/// which carries more meaning to other developers why an empty baggage was used.
/// Internal on purpose, please use ``ServiceContext/TODO(_:function:file:line:)`` or ``ServiceContext/topLevel`` to create an "empty" context,
/// which carries more meaning to other developers why an empty context was used.
init() {}
}

// MARK: - Creating ServiceContext

extension ServiceContext {
/// Creates a new empty "top level" baggage, generally used as an "initial" baggage to immediately be populated with
/// Creates a new empty "top level" context, generally used as an "initial" context 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.
/// context for their work.
///
/// ## Usage in frameworks and libraries
/// This function is really only intended to be used by 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
/// baggage when handling a request only to immediately populate it with useful trace information extracted from e.g.
/// 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 baggage during the processing lifetime of any request,
/// and only should create baggage if some processing is performed in the background - thus the naming of this property.
/// Application code should never have to create an empty context during the processing lifetime of any request,
/// and only should create context 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, either implicitly through the task-local `ServiceContext.$current`
/// or explicitly as part of some kind of "FrameworkContext".
///
/// If unsure where to obtain a baggage from, prefer using `.TODO("Not sure where I should get a context from here?")`
/// in order to inform other developers that the lack of baggage passing was not done on purpose, but rather because either
/// not being sure where to obtain a baggage from, or other framework limitations -- e.g. the outer framework not being
/// baggage aware just yet.
/// 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
/// context aware just yet.
public static var topLevel: ServiceContext {
ServiceContext()
}
}

extension ServiceContext {
/// A baggage intended as a placeholder until a real value can be passed through a function call.
/// A context 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 baggage is not yet possible,
/// e.g. because an external library did not pass it correctly and has to be fixed before the proper baggage
/// 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 was used. This comes in handy when wanting to ensure that
/// a project never ends up using code which initially was written as "was lazy, did not pass baggage", yet the
/// project requires baggage passing to be done correctly throughout the application. Similar checks can be performed
/// You may set the `SERVICE_CONTEXT_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 context was used. This comes in handy when wanting to ensure that
/// a project never ends up using code which 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 = ServiceContext.TODO("The framework XYZ should be modified to pass us a baggage here, and we'd pass it along"))
/// let context = ServiceContext.TODO("The framework XYZ should be modified to pass us a context here, and we'd pass it along"))
///
/// - Parameters:
/// - reason: Informational reason for developers, why a placeholder context was used instead of a proper one,
/// - function: The function to which the TODO refers.
/// - file: The file to which the TODO refers.
/// - line: The line to which the TODO refers.
/// - Returns: Empty "to-do" baggage which should be eventually replaced with a carried through one, or `topLevel`.
/// - Returns: Empty "to-do" context which should be eventually replaced with a carried through one, or `topLevel`.
public static func TODO(
_ reason: StaticString? = "",
function: String = #function,
file: String = #file,
line: UInt = #line
) -> ServiceContext {
var baggage = ServiceContext()
var context = ServiceContext()
#if BAGGAGE_CRASH_TODOS
fatalError("BAGGAGE_CRASH_TODOS: at \(file):\(line) (function \(function)), reason: \(reason)")
#elseif SERVICE_CONTEXT_CRASH_TODOS
fatalError("SERVICE_CONTEXT_CRASH_TODOS: at \(file):\(line) (function \(function)), reason: \(reason)")
#else
baggage[TODOKey.self] = .init(file: file, line: line)
return baggage
context[TODOKey.self] = .init(file: file, line: line)
return context
#endif
}

Expand All @@ -151,8 +153,8 @@ extension ServiceContext {
}
}

/// Carried automatically by a "to do" baggage.
/// It can be used to track where a baggage originated and which "to do" baggage must be fixed into a real one to avoid this.
/// Carried automatically by a "to do" context.
/// It can be used to track where a context originated and which "to do" context must be fixed into a real one to avoid this.
public struct TODOLocation: Sendable {
/// Source file location where the to-do ``ServiceContext`` was created
public let file: String
Expand All @@ -163,7 +165,7 @@ public struct TODOLocation: Sendable {
// MARK: - Interacting with ServiceContext

extension ServiceContext {
/// Provides type-safe access to the baggage's values.
/// Provides type-safe access to the context's values.
/// This API should ONLY be used inside of accessor implementations.
///
/// End users should use "accessors" the key's author MUST define rather than using this subscript, following this pattern:
Expand All @@ -173,20 +175,20 @@ extension ServiceContext {
/// }
///
/// extension ServiceContext {
/// public internal(set) var testID: TestID? {
/// get {
/// self[TestIDKey.self]
/// public internal(set) var testID: TestID? {
/// get {
/// self[TestIDKey.self]
/// }
/// set {
/// self[TestIDKey.self] = newValue
/// }
/// }
/// 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,
/// Note that specific context and context types MAY (and usually do), offer also a way to set context values,
/// however in the most general case it is not required, as some frameworks may only be able to offer reading.
public subscript<Key: ServiceContextKey>(_ key: Key.Type) -> Key.Value? {
get {
Expand All @@ -201,12 +203,12 @@ extension ServiceContext {
}

extension ServiceContext {
/// The number of items in the baggage.
/// The number of items in the context.
public var count: Int {
self._storage.count
}

/// A Boolean value that indicates whether the baggage is empty.
/// A Boolean value that indicates whether the context is empty.
public var isEmpty: Bool {
self._storage.isEmpty
}
Expand Down
4 changes: 2 additions & 2 deletions Sources/ServiceContextModule/ServiceContextKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//

/// Baggage keys provide type-safe access to ``ServiceContext``s by declaring the type of value they "key" at compile-time.
/// Context keys provide type-safe access to ``ServiceContext``s by declaring the type of value they "key" at compile-time.
/// To give your `ServiceContextKey` an explicit name, override the ``ServiceContextKey/nameOverride-6shk1`` property.
///
/// In general, any `ServiceContextKey` should be `internal` or `private` to the part of a system using it.
Expand All @@ -25,7 +25,7 @@
/// static var nameOverride: String? { "test-id" }
/// }
///
/// extension Baggage {
/// extension ServiceContext {
/// /// This is some useful property documentation.
/// public internal(set) var testID: String? {
/// get {
Expand Down

0 comments on commit 8946c93

Please sign in to comment.