Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh

Commit

Stripe's events replay and the saved card sync again: both bound an empty value as JavaScript undefined, which D1 refuses ("D1_TYPE_ERROR: Type 'undefined' not supported"). A finished replay's cursor (replay_after, since, until, failed: the pagination of 0062) is None, so every tick's replay failed at its last write and never recorded its progress (no live sync row after the switch to the live key); and a customer with no card (flagon-io's enrollment) made customer.updated fail, so Stripe kept retrying it. Every optional value now goes through or_null, which binds NULL, and a test refuses Option::into in a binding there. Found in production right after going live (#47). Checked: cargo test -p g1t-billing (291) and its wasm32 check.

G1t-Session:
ses_01m4y8v7hae5csmqcavv6wprjy
syntaqxcommitted Parentf2c1aa0Browse files
1 file+25−70/1 viewed
+25−7
6464 }
6565 }
6666
67+/// A value to bind, or SQL NULL. `Option::into` gives JavaScript
68+/// `undefined` for None, which D1 refuses ("Type 'undefined' not
69+/// supported"): a customer without a card, or a replay that finished.
70+fn or_null<T: Into<worker::wasm_bindgen::JsValue>>(value: Option<T>) -> worker::wasm_bindgen::JsValue {
71+ value.map_or(worker::wasm_bindgen::JsValue::NULL, Into::into)
72+}
73+
6774 impl Billing {
6875 /// The workspace's saved card as last synced. Stripe is asked only when
6976 /// it never was, or g1t changed the card since; a Stripe that does not
130137 )
131138 .bind(&[
132139 customer.into(),
133− card.as_ref().map(|c| c.brand.clone()).into(),
134− card.as_ref().map(|c| c.last4.clone()).into(),
135− card.as_ref().map(|c| c.exp_month as f64).into(),
136− card.as_ref().map(|c| c.exp_year as f64).into(),
140+ or_null(card.as_ref().map(|c| c.brand.clone())),
141+ or_null(card.as_ref().map(|c| c.last4.clone())),
142+ or_null(card.as_ref().map(|c| c.exp_month as f64)),
143+ or_null(card.as_ref().map(|c| c.exp_year as f64)),
137144 rfc3339(now_ms()).into(),
138145 ])?
139146 .run()
249256 .bind(&[
250257 mode.into(), (through as f64).into(), rfc3339(now_ms()).into(),
251258 crate::optional(if complete { None } else { after.as_deref() }),
252− (!complete).then_some(since as f64).into(),
253− (!complete).then_some(until as f64).into(),
254− if complete { None } else { first_failed.map(|v| v as f64) }.into(),
259+ or_null((!complete).then_some(since as f64)),
260+ or_null((!complete).then_some(until as f64)),
261+ or_null(if complete { None } else { first_failed.map(|v| v as f64) }),
255262 ])?
256263 .run()
257264 .await?;
354361
355362 #[cfg(test)]
356363 mod tests {
364+
365+ #[test]
366+ fn no_binding_turns_none_into_undefined() {
367+ // D1 refuses `undefined`; every optional value goes through or_null.
368+ let source = include_str!("stripe_sync.rs");
369+ let body = source.split("#[cfg(test)]").next().unwrap();
370+ for line in body.lines().filter(|l| l.trim_end().ends_with(".into(),")) {
371+ assert!(!line.contains("then_some(") && !line.contains(".map(|"), "{line}");
372+ }
373+ }
374+
357375 use super::*;
358376
359377 #[test]

Comments

Sign in to comment.