From dc660d21c824baf8cce9e36944e24d1ff2320c41 Mon Sep 17 00:00:00 2001 From: bouzuya Date: Mon, 22 Aug 2022 22:30:17 +0900 Subject: [PATCH] twiq: Add firestore_rest::begin_transaction --- twiq/crates/db/src/event_store.rs | 3 +- twiq/crates/db/src/firestore_rest.rs | 73 ++++++++++++++++++++++++++++ twiq/crates/db/src/lib.rs | 31 ++++++++++-- 3 files changed, 102 insertions(+), 5 deletions(-) diff --git a/twiq/crates/db/src/event_store.rs b/twiq/crates/db/src/event_store.rs index 993a8e83..c51cad3d 100644 --- a/twiq/crates/db/src/event_store.rs +++ b/twiq/crates/db/src/event_store.rs @@ -1,12 +1,11 @@ use std::{collections::HashMap, env, str::FromStr}; use reqwest::Response; -use serde::__private::doc; use crate::{ event::Event, event_stream_id::EventStreamId, - event_stream_seq::{self, EventStreamSeq}, + event_stream_seq::EventStreamSeq, firestore_rest::{self, Document, Timestamp, Value}, }; diff --git a/twiq/crates/db/src/firestore_rest.rs b/twiq/crates/db/src/firestore_rest.rs index 5d9f9f66..253f2c68 100644 --- a/twiq/crates/db/src/firestore_rest.rs +++ b/twiq/crates/db/src/firestore_rest.rs @@ -147,6 +147,50 @@ pub struct Document { pub update_time: Timestamp, } +#[derive(Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub enum TransactionOptions { + ReadOnly { + read_time: String, + }, + ReadWrite { + #[serde(skip_serializing_if = "Option::is_none")] + retry_transaction: Option, + }, +} + +#[derive(Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct BeginTransactionRequestBody { + pub options: TransactionOptions, +} + +#[derive(Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct BeginTransactionResponse { + pub transaction: String, +} + +pub async fn begin_transaction( + (token, project_id): (&str, &str), + database: &str, + body: BeginTransactionRequestBody, +) -> anyhow::Result { + // + let method = Method::POST; + let url = format!( + "https://firestore.googleapis.com/v1/{}/documents:beginTransaction", + database + ); + + Ok(Client::new() + .request(method, url) + .header("Authorization", format!("Bearer {}", token)) + .header("Content-Type", "application/json") + .header("X-Goog-User-Project", project_id) + .body(serde_json::to_string(&body)?) + .send() + .await?) +} + pub async fn create_document( (token, project_id): (&str, &str), parent: &str, @@ -397,4 +441,33 @@ mod tests { assert!(serde_json::to_string(&document).is_ok()); Ok(()) } + + #[test] + fn begin_transaction_request_body_test() -> anyhow::Result<()> { + assert_eq!( + serde_json::to_string(&BeginTransactionRequestBody { + options: TransactionOptions::ReadWrite { + retry_transaction: None, + }, + })?, + r#"{"options":{"readWrite":{}}}"# + ); + assert_eq!( + serde_json::to_string(&BeginTransactionRequestBody { + options: TransactionOptions::ReadWrite { + retry_transaction: Some("abc".to_owned()), + }, + })?, + r#"{"options":{"readWrite":{"retry_transaction":"abc"}}}"# + ); + assert_eq!( + serde_json::to_string(&BeginTransactionRequestBody { + options: TransactionOptions::ReadOnly { + read_time: "2000-01-02T03:04:05Z".to_owned() + } + })?, + r#"{"options":{"readOnly":{"read_time":"2000-01-02T03:04:05Z"}}}"# + ); + Ok(()) + } } diff --git a/twiq/crates/db/src/lib.rs b/twiq/crates/db/src/lib.rs index d65e999f..8d245148 100644 --- a/twiq/crates/db/src/lib.rs +++ b/twiq/crates/db/src/lib.rs @@ -11,7 +11,29 @@ use std::{collections::HashMap, env}; use anyhow::ensure; use reqwest::Response; -use crate::firestore_rest::{Document, Value}; +use crate::firestore_rest::{ + BeginTransactionRequestBody, BeginTransactionResponse, Document, TransactionOptions, Value, +}; + +async fn begin_transaction_example() -> anyhow::Result { + let bearer_token = env::var("GOOGLE_BEARER_TOKEN")?; + let project_id = env::var("PROJECT_ID")?; + let database_id = "(default)"; + let database = format!("projects/{}/databases/{}", project_id, database_id); + let response = firestore_rest::begin_transaction( + (&bearer_token, &project_id), + &database, + BeginTransactionRequestBody { + options: TransactionOptions::ReadWrite { + retry_transaction: None, + }, + }, + ) + .await?; + ensure!(response.status() == 200); + let response: BeginTransactionResponse = response.json().await?; + Ok(response) +} // select (one) async fn get_example() -> anyhow::Result { @@ -106,14 +128,17 @@ mod tests { use super::*; #[tokio::test] + #[ignore] async fn test() -> anyhow::Result<()> { + let response = begin_transaction_example().await?; + assert_eq!(serde_json::to_string(&response)?, ""); // let response = create_document_example().await?; // let status = response.status(); // assert_eq!(status, 200); // let response = patch_example().await?; - let document = get_example().await?; - assert_eq!(serde_json::to_string(&document)?, ""); + // let document = get_example().await?; + // assert_eq!(serde_json::to_string(&document)?, ""); Ok(()) } }