-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
263 additions
and
47 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
use std::{collections::HashSet, sync::Arc}; | ||
|
||
use async_trait::async_trait; | ||
use tokio::sync::Mutex; | ||
|
||
use self::create_expo_push_token::CanCreateExpoPushToken; | ||
|
||
pub mod create_expo_push_token; | ||
|
||
#[derive(Clone, Debug, Default)] | ||
pub struct App { | ||
// TODO: hide | ||
pub expo_push_tokens: Arc<Mutex<HashSet<String>>>, | ||
} | ||
|
||
#[async_trait] | ||
impl CanCreateExpoPushToken for App { | ||
async fn create_expo_push_token( | ||
&self, | ||
input: create_expo_push_token::Input, | ||
) -> Result<create_expo_push_token::Output, create_expo_push_token::Error> { | ||
create_expo_push_token::handle(self, input).await | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
use async_trait::async_trait; | ||
|
||
use super::App; | ||
|
||
#[async_trait] | ||
#[cfg_attr(test, mockall::automock)] | ||
pub trait CanCreateExpoPushToken { | ||
async fn create_expo_push_token(&self, input: Input) -> Result<Output, Error>; | ||
} | ||
|
||
pub struct Input { | ||
pub expo_push_token: String, | ||
} | ||
|
||
pub struct Output; | ||
pub struct Error; | ||
|
||
pub async fn handle(state: &App, Input { expo_push_token }: Input) -> Result<Output, Error> { | ||
let mut expo_push_tokens = state.expo_push_tokens.lock().await; | ||
// TODO: already exists | ||
expo_push_tokens.insert(expo_push_token); | ||
Ok(Output) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,120 @@ | ||
use axum::{extract::State, http::StatusCode, routing, Json, Router}; | ||
use axum::{ | ||
extract::State, | ||
http::StatusCode, | ||
response::{IntoResponse, Response}, | ||
routing, Json, Router, | ||
}; | ||
|
||
use super::App; | ||
use crate::command::create_expo_push_token::{CanCreateExpoPushToken, Input, Output}; | ||
|
||
pub fn route() -> Router<App> { | ||
Router::new().route("/expo_push_tokens", routing::post(handler)) | ||
pub fn route<T>() -> Router<T> | ||
where | ||
T: Clone + CanCreateExpoPushToken + Send + Sync + 'static, | ||
{ | ||
Router::new().route("/expo_push_tokens", routing::post(handler::<T>)) | ||
} | ||
|
||
#[derive(Debug, serde::Deserialize, serde::Serialize)] | ||
pub struct CreateExpoPushTokenRequestBody { | ||
pub expo_push_token: String, | ||
} | ||
|
||
async fn handler( | ||
State(App { expo_push_tokens }): State<App>, | ||
Json(CreateExpoPushTokenRequestBody { expo_push_token }): Json<CreateExpoPushTokenRequestBody>, | ||
) -> StatusCode { | ||
let mut expo_push_tokens = expo_push_tokens.lock().await; | ||
// TODO: already exists | ||
expo_push_tokens.insert(expo_push_token); | ||
StatusCode::CREATED | ||
impl TryFrom<CreateExpoPushTokenRequestBody> for Input { | ||
type Error = StatusCode; | ||
|
||
fn try_from( | ||
CreateExpoPushTokenRequestBody { expo_push_token }: CreateExpoPushTokenRequestBody, | ||
) -> Result<Self, Self::Error> { | ||
Ok(Self { expo_push_token }) | ||
} | ||
} | ||
|
||
#[derive(Debug, serde::Deserialize, serde::Serialize)] | ||
struct CreateExpoPushTokenResponseBody; | ||
|
||
impl From<Output> for CreateExpoPushTokenResponseBody { | ||
fn from(_: Output) -> Self { | ||
Self | ||
} | ||
} | ||
|
||
impl IntoResponse for CreateExpoPushTokenResponseBody { | ||
fn into_response(self) -> Response { | ||
StatusCode::CREATED.into_response() | ||
} | ||
} | ||
|
||
async fn handler<T>( | ||
State(app): State<T>, | ||
Json(body): Json<CreateExpoPushTokenRequestBody>, | ||
) -> Result<CreateExpoPushTokenResponseBody, StatusCode> | ||
where | ||
T: CanCreateExpoPushToken, | ||
{ | ||
let input = Input::try_from(body)?; | ||
let output = app.create_expo_push_token(input).await.map_err(|_| { | ||
// TODO | ||
StatusCode::INTERNAL_SERVER_ERROR | ||
})?; | ||
Ok(CreateExpoPushTokenResponseBody::from(output)) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use std::sync::Arc; | ||
|
||
use anyhow::Context; | ||
use async_trait::async_trait; | ||
use axum::{ | ||
body::Body, | ||
http::{header::CONTENT_TYPE, Request}, | ||
}; | ||
use tower::ServiceExt as _; | ||
|
||
use crate::{ | ||
command::create_expo_push_token::{self, MockCanCreateExpoPushToken}, | ||
tests::ResponseExt as _, | ||
}; | ||
|
||
use super::*; | ||
|
||
#[tokio::test] | ||
async fn test_create_expo_push_token() -> anyhow::Result<()> { | ||
let mut app = MockApp::default(); | ||
Arc::get_mut(&mut app.mock_create_expo_push_token) | ||
.context("Arc::get_mut")? | ||
.expect_create_expo_push_token() | ||
.return_once(|_| Box::pin(async { Ok(Output) })); | ||
let router = route().with_state(app); | ||
let request = Request::builder() | ||
.method("POST") | ||
.uri("/expo_push_tokens") | ||
.header(CONTENT_TYPE, "application/json") | ||
.body(Body::from(serde_json::to_string( | ||
&CreateExpoPushTokenRequestBody { | ||
expo_push_token: "ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]".to_string(), | ||
}, | ||
)?))?; | ||
let response = router.oneshot(request).await?; | ||
assert_eq!(response.status(), StatusCode::CREATED); | ||
assert_eq!(response.into_body_string().await?, ""); | ||
Ok(()) | ||
} | ||
|
||
#[derive(Clone, Default)] | ||
struct MockApp { | ||
mock_create_expo_push_token: Arc<MockCanCreateExpoPushToken>, | ||
} | ||
|
||
#[async_trait] | ||
impl CanCreateExpoPushToken for MockApp { | ||
async fn create_expo_push_token( | ||
&self, | ||
input: create_expo_push_token::Input, | ||
) -> Result<create_expo_push_token::Output, create_expo_push_token::Error> { | ||
self.mock_create_expo_push_token | ||
.create_expo_push_token(input) | ||
.await | ||
} | ||
} | ||
} |
Oops, something went wrong.