Skip to content
This repository has been archived by the owner on Feb 4, 2020. It is now read-only.

Commit

Permalink
Upsert entry in handler
Browse files Browse the repository at this point in the history
  • Loading branch information
popon-01 committed Oct 26, 2018
1 parent 4f0fd19 commit f4a6610
Show file tree
Hide file tree
Showing 18 changed files with 407 additions and 348 deletions.
208 changes: 103 additions & 105 deletions burning-pro-server/src/admin/form.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
//! Form types.
use std::collections::HashMap;
use std::fmt;

use chrono::{DateTime, Local};
use chrono::{DateTime, FixedOffset, Local, TimeZone};
use serde::de;

/// A phrase.
#[derive(Debug, Clone, Serialize, Deserialize)]
Expand All @@ -16,17 +18,19 @@ pub struct Phrase {
/// Author's person id.
pub person_id: i32,
/// URL of the phrase if it is posted or published to the WWW.
#[serde(with = "optstr_fmt")]
#[serde(deserialize_with = "deserialize_optstr")]
pub url: Option<String>,
/// Whether the source web page is deleted or not.
pub deleted: bool,
/// Datetime when the phrase is published.
#[serde(with = "optdate_fmt")]
#[serde(deserialize_with = "deserialize_optdate")]
pub published_at: Option<DateTime<Local>>,
/// Extra form field.
/// Contains selected tag id.
///
/// Contains selected tag_ids, map person_id to display_name, and
/// mapping of persons / tags from indices to names.
#[serde(flatten)]
pub extra: HashMap<String, String>
pub extra: HashMap<String, String>,
}

/// A person.
Expand All @@ -35,16 +39,17 @@ pub struct Person {
/// Row ID (`None` for new entry)
pub person_id: Option<i32>,
/// Real name.
#[serde(with = "optstr_fmt")]
#[serde(deserialize_with = "deserialize_optstr")]
pub real_name: Option<String>,
/// Display name.
#[serde(with = "optstr_fmt")]
#[serde(deserialize_with = "deserialize_optstr")]
pub display_name: Option<String>,
/// URLs of web pages of the person.
#[serde(with = "strvec_fmt")]
#[serde(deserialize_with = "deserialize_strvec")]
//#[serde(with = "strvec_fmt")]
pub url: Vec<String>,
/// Twitter account.
#[serde(with = "optstr_fmt")]
#[serde(deserialize_with = "deserialize_optstr")]
pub twitter: Option<String>,
}

Expand All @@ -56,115 +61,108 @@ pub struct Tag {
/// Name.
pub name: String,
/// Description of tag.
#[serde(with = "optstr_fmt")]
#[serde(deserialize_with = "deserialize_optstr")]
pub description: Option<String>,
}

/// Custom Serde format for `Vec<String>`.
/// Convert comma-separated string <-> `Vec<String>`.
mod strvec_fmt {
use serde::{Deserialize, Serializer, Deserializer};

pub fn serialize<S>(
str_vec: &Vec<String>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let s = format!("{}", str_vec.join(","));
serializer.serialize_str(&s)
}

pub fn deserialize<'de, D>(
deserializer: D,
) -> Result<Vec<String>, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
if s.is_empty() {
Ok(Vec::new())
} else {
Ok(s.split(',')
.map(|s| s.trim().to_string())
.collect::<Vec<_>>())
/// Custom deserializer for `Option<String>`.
///
/// Convert `""` -> `None`, `$non_empty` -> `Some($non_empty)`.
/// (By default, empty form values are deserialized to `Some("")`.)
fn deserialize_strvec<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
where
D: de::Deserializer<'de>,
{
struct StrvecVisitor;

impl<'de> de::Visitor<'de> for StrvecVisitor {
type Value = Vec<String>;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a comma-separated string")
}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
let s = v.to_string();
if s.is_empty() {
Ok(Vec::new())
} else {
Ok(s.split(',')
.map(|s| s.trim().to_string())
.collect::<Vec<_>>())
}
}
}

deserializer.deserialize_any(StrvecVisitor)
}

/// Custom Serde format for `Option<String>`.
/// Convert `""` <-> `None`, `$non_empty` <-> `Some($non_empty)`.
/// Custom deserializer for `Option<String>`.
///
/// Convert `""` -> `None`, `$non_empty` -> `Some($non_empty)`.
/// (By default, empty form values are deserialized to `Some("")`.)
mod optstr_fmt {
use serde::{Deserialize, Serializer, Deserializer};

pub fn serialize<S>(
optdate: &Option<String>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match optdate {
Some(val) => serializer.serialize_str(val),
None => serializer.serialize_none()
fn deserialize_optstr<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: de::Deserializer<'de>,
{
struct OptstrVisitor;

impl<'de> de::Visitor<'de> for OptstrVisitor {
type Value = Option<String>;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string")
}
}

pub fn deserialize<'de, D>(
deserializer: D,
) -> Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
if s.is_empty() {
Ok(None)
} else {
Ok(Some(s))

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
if v.is_empty() {
Ok(None)
} else {
Ok(Some(v.to_string()))
}
}
}

deserializer.deserialize_any(OptstrVisitor)
}

/// Custom Serde format for `Option<DateTime<Local>>`
/// Convert YYYY-mm-ddTHH:MM format(maybe empty) <-> `Option<DateTime<Local>>`
mod optdate_fmt {
use serde::{Deserialize, Serializer, Deserializer};
use serde::de::Error;
use chrono::{DateTime, Local, FixedOffset, TimeZone};

const FORMAT: &'static str = "%Y-%m-%dT%H:%M";

pub fn serialize<S>(
optdate: &Option<DateTime<Local>>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let s = match optdate {
Some(date) => format!("{}", date.format(FORMAT)),
None => String::new()
};
serializer.serialize_str(&s)
}

pub fn deserialize<'de, D>(
deserializer: D,
) -> Result<Option<DateTime<Local>>, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
if s.is_empty() {
Ok(None)
} else {
let tz_offset = FixedOffset::east(9 * 60 * 60);
Local::from_offset(&tz_offset)
.datetime_from_str(&s, FORMAT)
.map(|dt| Some(dt))
.map_err(Error::custom)
/// Custom deserializer for `Option<DateTime<Local>>`
///
/// Convert YYYY-MM-DDThh:mm:ss format(maybe empty) -> `Option<DateTime<Local>>`
fn deserialize_optdate<'de, D>(deserializer: D) -> Result<Option<DateTime<Local>>, D::Error>
where
D: de::Deserializer<'de>,
{
struct OptdateVisitor;

impl<'de> de::Visitor<'de> for OptdateVisitor {
type Value = Option<DateTime<Local>>;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string `YYYY-MM-DDThh:mm:ss`")
}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
if v.is_empty() {
Ok(None)
} else {
let tz_offset = FixedOffset::east(9 * 60 * 60);
Local::from_offset(&tz_offset)
.datetime_from_str(v, "%Y-%m-%dT%H:%M:%S")
.map(|dt| Some(dt))
.map_err(de::Error::custom)
}
}
}

deserializer.deserialize_any(OptdateVisitor)
}
23 changes: 12 additions & 11 deletions burning-pro-server/src/admin/mod.rs
Original file line number Diff line number Diff line change
@@ -1,34 +1,31 @@
//! DB update service.
use actix_web::{Error, HttpResponse, HttpRequest};
use actix_web::error::{UrlencodedError, ResponseError};
use tera::{Tera, Context};
use actix_web::error::{ResponseError, UrlencodedError};
use actix_web::{Error, HttpRequest, HttpResponse};
use tera::{Context, Tera};

use app::AppState;

pub mod form;
pub mod person;
pub mod phrase;
pub mod tag;
pub mod person;

#[derive(Fail, Debug)]
#[fail(display="form request error")]
#[fail(display = "form request error")]
struct FormRequestError {
err: UrlencodedError,
}

impl ResponseError for FormRequestError {
fn error_response(&self) -> HttpResponse {
HttpResponse::InternalServerError()
.body(format!("Bad Request: {:#?}", self.err))
HttpResponse::BadRequest().body(format!("Bad Request: {:#?}", self.err))
}
}

/// Handles form deserialization error
#[allow(unknown_lints, needless_pass_by_value)]
pub fn post_err_handler(
err: UrlencodedError, req: &HttpRequest<AppState>
) -> Error {
pub fn post_err_handler(err: UrlencodedError, req: &HttpRequest<AppState>) -> Error {
error!("fail to deserialize request: {:?}", req);
(FormRequestError { err }).into()
}
Expand All @@ -49,5 +46,9 @@ fn render(template: &Tera, ctx: &Context, path: &str) -> HttpResponse {
#[allow(unknown_lints, needless_pass_by_value)]
pub fn index(req: HttpRequest<AppState>) -> HttpResponse {
debug!("request for `db_update::index()`: {:?}", req);
render(req.state().template(), &Context::new(), "register/index.html")
render(
req.state().template(),
&Context::new(),
"register/index.html",
)
}
Loading

0 comments on commit f4a6610

Please sign in to comment.