diff --git a/worker-sandbox/src/d1.rs b/worker-sandbox/src/d1.rs index 36a16fed..f36bab5a 100644 --- a/worker-sandbox/src/d1.rs +++ b/worker-sandbox/src/d1.rs @@ -1,6 +1,8 @@ +use crate::{js_sys::{Object, Reflect}, wasm_bindgen}; use crate::SomeSharedData; use serde::Deserialize; -use worker::*; +use wasm_bindgen::JsValue; +use worker::{D1PreparedArgument, D1Type, Env, Error, Request, Response, Result}; #[derive(Deserialize)] struct Person { @@ -125,3 +127,178 @@ pub async fn error(_req: Request, env: Env, _data: SomeSharedData) -> Result, + age: Option, +} + +#[worker::send] +pub async fn jsvalue_null_is_null( + _req: Request, + _env: Env, + _data: SomeSharedData, +) -> Result { + console_error_panic_hook::set_once(); + + assert!(wasm_bindgen::JsValue::NULL.is_null()); + + Response::ok("ok") +} + +#[worker::send] +pub async fn serialize_optional_none( + _req: Request, + _env: Env, + _data: SomeSharedData, +) -> Result { + console_error_panic_hook::set_once(); + let serializer = serde_wasm_bindgen::Serializer::new().serialize_missing_as_null(true); + + let none: Option = None; + let js_none = ::serde::ser::Serialize::serialize(&none, &serializer).unwrap(); + assert!(js_none.is_null()); + + Response::ok("ok") +} + +#[worker::send] +pub async fn serialize_optional_some( + _req: Request, + _env: Env, + _data: SomeSharedData, +) -> Result { + console_error_panic_hook::set_once(); + let serializer = serde_wasm_bindgen::Serializer::new().serialize_missing_as_null(true); + + let some: Option = Some("Hello".to_string()); + let js_some = ::serde::ser::Serialize::serialize(&some, &serializer).unwrap(); + assert!(js_some.is_string()); + + Response::ok("ok") +} + +#[worker::send] +pub async fn deserialize_optional_none( + _req: Request, + _env: Env, + _data: SomeSharedData, +) -> Result { + console_error_panic_hook::set_once(); + + let js_value = Object::new(); + Reflect::set(&js_value, &JsValue::from_str("id"), &JsValue::from_f64(1.0)).unwrap(); + Reflect::set(&js_value, &JsValue::from_str("name"), &JsValue::NULL).unwrap(); + Reflect::set(&js_value, &JsValue::from_str("age"), &JsValue::NULL).unwrap(); + + let js_value: JsValue = js_value.into(); + + let value: NullablePerson = serde_wasm_bindgen::from_value(js_value).unwrap(); + + assert_eq!(value.id, 1); + assert_eq!(value.name, None); + assert_eq!(value.age, None); + + Response::ok("ok") +} + +#[worker::send] +pub async fn insert_and_retrieve_optional_none( + _req: Request, + env: Env, + _data: SomeSharedData, +) -> Result { + let db = env.d1("DB")?; + + let query = worker::query!( + &db, + "INSERT INTO nullable_people (id, name, age) VALUES (?1, ?2, ?3)", + &3, + &None::, + &None:: + )?; + query.run().await?; + + let stmt = worker::query!(&db, "SELECT * FROM nullable_people WHERE id = 3"); + let person = stmt.first::(None).await?.unwrap(); + assert_eq!(person.id, 3); + assert_eq!(person.name, None); + assert_eq!(person.age, None); + + Response::ok("ok") +} + +#[worker::send] +pub async fn insert_and_retrieve_optional_some( + _req: Request, + env: Env, + _data: SomeSharedData, +) -> Result { + let db = env.d1("DB")?; + let query = worker::query!( + &db, + "INSERT INTO nullable_people (id, name, age) VALUES (?1, ?2, ?3)", + &4, + &"Dude", + &12 + )?; + query.run().await?; + + let stmt = worker::query!(&db, "SELECT * FROM nullable_people WHERE id = 4"); + let person = stmt.first::(None).await?.unwrap(); + assert_eq!(person.id, 4); + assert_eq!(person.name, Some("Dude".to_string())); + assert_eq!(person.age, Some(12)); + + Response::ok("ok") +} + +#[worker::send] +pub async fn retrieve_optional_none( + _req: Request, + env: Env, + _data: SomeSharedData, +) -> Result { + let db = env.d1("DB")?; + + let stmt = worker::query!(&db, "SELECT * FROM nullable_people WHERE id = 1"); + let person = stmt.first::(None).await?.unwrap(); + assert_eq!(person.id, 1); + assert_eq!(person.name, None); + assert_eq!(person.age, None); + + Response::ok("ok") +} + +#[worker::send] +pub async fn retrieve_optional_some( + _req: Request, + env: Env, + _data: SomeSharedData, +) -> Result { + let db = env.d1("DB")?; + + let stmt = worker::query!(&db, "SELECT * FROM nullable_people WHERE id = 2"); + let person = stmt.first::(None).await?.unwrap(); + assert_eq!(person.id, 2); + assert_eq!(person.name, Some("Wynne Ogley".to_string())); + assert_eq!(person.age, Some(67)); + + Response::ok("ok") +} + +#[worker::send] +pub async fn retrive_first_none( + _req: Request, + env: Env, + _data: SomeSharedData, +) -> Result { + let db = env.d1("DB")?; + + let stmt = worker::query!(&db, "SELECT * FROM nullable_people WHERE id = 9999"); + assert!(stmt.first::(None).await?.is_none()); + + Response::ok("ok") +} diff --git a/worker-sandbox/src/lib.rs b/worker-sandbox/src/lib.rs index 24113c49..877fabe0 100644 --- a/worker-sandbox/src/lib.rs +++ b/worker-sandbox/src/lib.rs @@ -72,7 +72,7 @@ type HandlerResponse = http::Response; #[cfg(not(feature = "http"))] type HandlerResponse = Response; -#[event(fetch)] +#[event(fetch, respond_with_errors)] pub async fn main( request: HandlerRequest, env: Env, diff --git a/worker-sandbox/src/router.rs b/worker-sandbox/src/router.rs index 025a028f..494c2f39 100644 --- a/worker-sandbox/src/router.rs +++ b/worker-sandbox/src/router.rs @@ -197,6 +197,42 @@ pub fn make_router(data: SomeSharedData, env: Env) -> axum::Router { .route("/d1/dump", get(handler!(d1::dump))) .route("/d1/exec", post(handler!(d1::exec))) .route("/d1/error", get(handler!(d1::error))) + .route( + "/d1/jsvalue_null_is_null", + get(handler!(d1::jsvalue_null_is_null)), + ) + .route( + "/d1/serialize_optional_none", + get(handler!(d1::serialize_optional_none)), + ) + .route( + "/d1/serialize_optional_some", + get(handler!(d1::serialize_optional_some)), + ) + .route( + "/d1/deserialize_optional_none", + get(handler!(d1::deserialize_optional_none)), + ) + .route( + "/d1/insert_and_retrieve_optional_none", + get(handler!(d1::insert_and_retrieve_optional_none)), + ) + .route( + "/d1/insert_and_retrieve_optional_some", + get(handler!(d1::insert_and_retrieve_optional_some)), + ) + .route( + "/d1/retrieve_optional_none", + get(handler!(d1::retrieve_optional_none)), + ) + .route( + "/d1/retrieve_optional_some", + get(handler!(d1::retrieve_optional_some)), + ) + .route( + "/d1/retrive_first_none", + get(handler!(d1::retrive_first_none)), + ) .route("/kv/get", get(handler!(kv::get))) .route("/kv/get-not-found", get(handler!(kv::get_not_found))) .route("/kv/list-keys", get(handler!(kv::list_keys))) @@ -339,6 +375,39 @@ pub fn make_router<'a>(data: SomeSharedData) -> Router<'a, SomeSharedData> { .get_async("/d1/dump", handler!(d1::dump)) .post_async("/d1/exec", handler!(d1::exec)) .get_async("/d1/error", handler!(d1::error)) + .get_async( + "/d1/jsvalue_null_is_null", + handler!(d1::jsvalue_null_is_null), + ) + .get_async( + "/d1/serialize_optional_none", + handler!(d1::serialize_optional_none), + ) + .get_async( + "/d1/serialize_optional_some", + handler!(d1::serialize_optional_some), + ) + .get_async( + "/d1/deserialize_optional_none", + handler!(d1::deserialize_optional_none), + ) + .get_async( + "/d1/insert_and_retrieve_optional_none", + handler!(d1::insert_and_retrieve_optional_none), + ) + .get_async( + "/d1/insert_and_retrieve_optional_some", + handler!(d1::insert_and_retrieve_optional_some), + ) + .get_async( + "/d1/retrieve_optional_none", + handler!(d1::retrieve_optional_none), + ) + .get_async( + "/d1/retrieve_optional_some", + handler!(d1::retrieve_optional_some), + ) + .get_async("/d1/retrive_first_none", handler!(d1::retrive_first_none)) .get_async("/kv/get", handler!(kv::get)) .get_async("/kv/get-not-found", handler!(kv::get_not_found)) .get_async("/kv/list-keys", handler!(kv::list_keys)) diff --git a/worker-sandbox/tests/d1.spec.ts b/worker-sandbox/tests/d1.spec.ts index 71a706c9..4ea2aa10 100644 --- a/worker-sandbox/tests/d1.spec.ts +++ b/worker-sandbox/tests/d1.spec.ts @@ -59,4 +59,76 @@ describe("d1", () => { const resp = await mf.dispatchFetch("http://fake.host/d1/error"); expect(resp.status).toBe(200); }); + + test("create table nullable", async () => { + let query = `CREATE TABLE IF NOT EXISTS nullable_people ( + id INTEGER PRIMARY KEY, + name TEXT, + age INTEGER + );`; + + expect(await exec(query)).toBe(1); + + query = `INSERT OR IGNORE INTO nullable_people + (id, name, age) + VALUES + (1, NULL, NULL), + (2, 'Wynne Ogley', 67)`; + + expect(await exec(query)).toBe(1); + }); + + test("jsvalue_null_is_null", async () => { + const resp = await mf.dispatchFetch("http://fake.host/d1/jsvalue_null_is_null"); + expect(await resp.text()).toBe("ok"); + expect(resp.status).toBe(200); + }); + + test("serialize_optional_none", async () => { + const resp = await mf.dispatchFetch("http://fake.host/d1/serialize_optional_none"); + expect(await resp.text()).toBe("ok"); + expect(resp.status).toBe(200); + }); + + test("serialize_optional_some", async () => { + const resp = await mf.dispatchFetch("http://fake.host/d1/serialize_optional_some"); + expect(await resp.text()).toBe("ok"); + expect(resp.status).toBe(200); + }); + + test("deserialize_optional_none", async () => { + const resp = await mf.dispatchFetch("http://fake.host/d1/deserialize_optional_none"); + expect(await resp.text()).toBe("ok"); + expect(resp.status).toBe(200); + }); + + test("insert_and_retrieve_optional_none", async () => { + const resp = await mf.dispatchFetch("http://fake.host/d1/insert_and_retrieve_optional_none"); + expect(await resp.text()).toBe("ok"); + expect(resp.status).toBe(200); + }); + + test("insert_and_retrieve_optional_some", async () => { + const resp = await mf.dispatchFetch("http://fake.host/d1/insert_and_retrieve_optional_some"); + expect(await resp.text()).toBe("ok"); + expect(resp.status).toBe(200); + }); + + test("retrieve_optional_none", async () => { + const resp = await mf.dispatchFetch("http://fake.host/d1/retrieve_optional_none"); + expect(await resp.text()).toBe("ok"); + expect(resp.status).toBe(200); + }); + + test("retrieve_optional_some", async () => { + const resp = await mf.dispatchFetch("http://fake.host/d1/retrieve_optional_some"); + expect(await resp.text()).toBe("ok"); + expect(resp.status).toBe(200); + }); + + test("retrive_first_none", async () => { + const resp = await mf.dispatchFetch("http://fake.host/d1/retrive_first_none"); + expect(await resp.text()).toBe("ok"); + expect(resp.status).toBe(200); + }); }); diff --git a/worker-sandbox/tests/mf-socket.ts b/worker-sandbox/tests/mf-socket.ts index c5dd3013..e8e62511 100644 --- a/worker-sandbox/tests/mf-socket.ts +++ b/worker-sandbox/tests/mf-socket.ts @@ -11,7 +11,7 @@ export const server = createServer(function (socket) { export const mf = new Miniflare({ scriptPath: "./build/worker/shim.mjs", - compatibilityDate: "2023-05-18", + compatibilityDate: "2024-12-05", modules: true, modulesRules: [ { type: "CompiledWasm", include: ["**/*.wasm"], fallthrough: true }, diff --git a/worker-sandbox/tests/mf.ts b/worker-sandbox/tests/mf.ts index 469f4f40..a4f58df7 100644 --- a/worker-sandbox/tests/mf.ts +++ b/worker-sandbox/tests/mf.ts @@ -34,7 +34,7 @@ mockAgent export const mf = new Miniflare({ scriptPath: "./build/worker/shim.mjs", - compatibilityDate: "2023-05-18", + compatibilityDate: "2024-12-05", cache: true, cachePersist: false, d1Databases: ["DB"],