1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
//! actix-web adapter (behind the `actix` cargo feature).
//!
//! [`EverscribeLayer`] installs a per-request event (actor via your resolver,
//! origin from headers) and auto-records it on response finish when an
//! `action` was set. Handlers reach the event with
//! [`everscribe::event::current()`](crate::event::current) - actix-web
//! handlers are plain async functions/methods, not [`FromRequestParts`](axum::extract::FromRequestParts)-style
//! extractors, so unlike the `axum` feature this adapter needs no
//! `CurrentEvent` wrapper type at all: the task-local [`crate::event::current`]
//! already is the thin binding.
//!
//! Like the `axum` feature, this adapter supplies only transport bindings on
//! top of the framework-neutral record lifecycle in [`crate::event`]: the
//! dedupe flag, idempotency-key stamp, and "no response written" sentinel
//! rule all live in core, not here.
//!
//! # The written-vs-default question
//!
//! `sdk-go`'s gin and echo adapters both need a "was anything written" flag
//! alongside the final status, because both frameworks hand handlers a
//! mutable response writer that is pre-initialized to status 200 before the
//! handler does anything; a handler that returns without calling `Write` at
//! all is indistinguishable, by reading status alone, from one that
//! deliberately wrote a 200. Investigating actix-web's own types shows this
//! ambiguity cannot arise here, for a structural reason rather than an
//! incidental one: an actix-web handler does not mutate a shared writer, it
//! *constructs and returns* one complete [`actix_web::HttpResponse`] value
//! (via the [`actix_web::Responder`] trait), so there is no ambient default
//! for a no-op handler to accidentally inherit. actix-web does not implement
//! `Responder` for bare `()` at all - seen directly in its source
//! (`response/responder.rs`), citing
//! <https://github.com/actix/actix-web/issues/1108> for the reasoning - so a
//! handler that "returns having written nothing" does not compile. The
//! closest legal analogue, a handler returning `Result<(), E>`, is special
//! cased to answer with **`204 No Content`** on `Ok(())`
//! (<https://github.com/actix/actix-web/pull/3560>), a genuinely different,
//! deliberately-chosen status from `200`. Extractor failures and 404s are
//! likewise converted to a real `HttpResponse` before this middleware's
//! `call` future ever resolves (see `handler_service` in actix-web's
//! `handler.rs`): every [`ServiceResponse`] this middleware observes on the
//! success path already carries a genuine, deliberately-produced status. So
//! this adapter's [`crate::event::outcome_from_http_status`] call needs no
//! "written" flag of its own the way `stdlibResponseWriter` or gin's
//! `ginCapture` do in `sdk-go` - the ambiguous state those exist to guard
//! against is not reachable through actix-web's own API shape. (A panic
//! mid-handler is a different case, not this one: it unwinds the whole
//! future including this middleware's own code after `.await`, so nothing
//! after that point runs at all - the same as it would for the axum
//! adapter's tower `Service`, with or without a written flag.)
//!
//! ```no_run
//! use actix_web::{post, App};
//! use everscribe::actix::EverscribeLayer;
//! use everscribe::event::{self, Actor};
//!
//! #[post("/login")]
//! async fn login() -> &'static str {
//! event::current().with(|e| e.action = "user.login".into());
//! "ok"
//! }
//!
//! // A real multi-worker HttpServer::new(move || ...) factory is called
//! // once per worker and so needs an owned recorder per call - wrap it in
//! // an Arc-backed type of your own (or one recorder per worker, if it's
//! // cheap to construct) rather than moving the same value in repeatedly.
//! # fn build(rec: everscribe::recorder::BufferedRecorder) {
//! let _app = App::new()
//! .wrap(EverscribeLayer::new(rec, |req: &actix_web::dev::ServiceRequest| {
//! match req.headers().get("x-demo-actor").and_then(|v| v.to_str().ok()) {
//! Some(id) => Actor { r#type: "user".into(), id: id.into(), ..Default::default() },
//! None => Actor::new("anonymous"),
//! }
//! }))
//! .service(login);
//! # }
//! ```
use Future;
use Pin;
use Rc;
use Arc;
use ;
use Error;
use crate;
/// A boxed, non-`Send` future: actix-web runs each worker on its own
/// single-threaded `LocalSet`, so unlike axum's tower `Service` (which must
/// be `Send` to move across a multi-threaded runtime), this middleware's
/// future never needs to cross a thread once it starts.
type LocalBoxFuture<'a, T> = ;
/// Derives the [`Actor`] for a request from the pre-handler
/// [`ServiceRequest`] (headers, extensions such as a session set by an
/// earlier middleware, etc.).
/// actix-web middleware factory that installs the per-request event and
/// auto-records it. Add it with `.wrap(EverscribeLayer::new(recorder,
/// resolve_actor))`.
///
/// `recorder` and `resolve` live behind `Arc` (not `Rc`): [`crate::recorder::Recorder`]
/// requires `Send + Sync` since [`crate::event::end`] awaits it from inside
/// this middleware's boxed future, and actix workers run on their own
/// threads (one per CPU by default), so the same `EverscribeLayer` value is
/// shared across worker threads even though any one request is handled on a
/// single worker's `LocalSet`.
// Manual Clone for the same reason as axum's EverscribeLayer: derive would
// wrongly require R: Clone + F: Clone.
/// The [`Service`] produced by [`EverscribeLayer`].