Skip to content

Commit

Permalink
Merge pull request #114 from xsnippet/serialization-v2
Browse files Browse the repository at this point in the history
Rework Snippet serialization
  • Loading branch information
malor committed Jan 26, 2021
2 parents a290af9 + 4342e21 commit 3add960
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 107 deletions.
2 changes: 0 additions & 2 deletions src/storage/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
mod errors;
mod models;
mod sql;
mod util;

pub use errors::StorageError;
pub use models::{Changeset, Direction, ListSnippetsQuery, Snippet};
pub use sql::SqlStorage;
pub use util::DateTime;

/// CRUD interface for storing/loading snippets from a persistent storage.
///
Expand Down
59 changes: 44 additions & 15 deletions src/storage/models.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use std::iter;

use rand::Rng;
use serde::Serialize;

use crate::storage::DateTime;
use serde::{ser::SerializeStruct, Serialize, Serializer};

const DEFAULT_LIMIT_SIZE: usize = 20;
const DEFAULT_SLUG_LENGTH: usize = 8;

type DateTime = chrono::DateTime<chrono::Utc>;

/// A code snippet
#[derive(Debug, Default, Eq, PartialEq, Serialize)]
#[derive(Debug, Default, Eq, PartialEq)]
pub struct Snippet {
/// Slug that uniquely identifies the snippet
pub id: String,
Expand Down Expand Up @@ -59,6 +59,29 @@ impl Snippet {
}
}

// We could have just derived the implementation of Serializable instead, but
// implementing this trait by hand allows us to detach the in-memory state of
// the struct from what we want to expose via the API (e.g. instead of showing
// all changesets here, we just want to show content of the most recent one).
// Also, this allows us to work around the annoying nuisance of chrono::DateTime
// not implementing Serializable.
impl Serialize for Snippet {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut s = serializer.serialize_struct("Snippet", 7)?;
s.serialize_field("id", &self.id)?;
s.serialize_field("title", &self.title)?;
s.serialize_field("syntax", &self.syntax)?;
s.serialize_field("content", &self.changesets.last().map(|v| &v.content))?;
s.serialize_field("tags", &self.tags)?;
s.serialize_field("created_at", &self.created_at.map(|v| v.to_rfc3339()))?;
s.serialize_field("updated_at", &self.updated_at.map(|v| v.to_rfc3339()))?;
s.end()
}
}

#[derive(Debug)]
pub enum Direction {
/// From older to newer snippets.
Expand Down Expand Up @@ -105,7 +128,7 @@ pub struct ListSnippetsQuery {
}

/// A particular snippet revision
#[derive(Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize)]
#[derive(Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
pub struct Changeset {
/// Changeset index. Version numbers start from 0 and are incremented by 1
pub version: usize,
Expand Down Expand Up @@ -316,24 +339,30 @@ mod tests {
id: "spam".to_string(),
title: Some("Hello".to_string()),
syntax: Some("python".to_string()),
changesets: vec![Changeset::new(0, "print('Hello, World!')".to_string())],
changesets: vec![
Changeset {
version: 0,
content: "print('Hello, World!')".to_string(),
created_at: Some(dt),
updated_at: Some(dt),
},
Changeset {
version: 1,
content: "print(42)".to_string(),
created_at: Some(dt),
updated_at: Some(dt),
},
],
tags: vec!["foo".to_string(), "bar".to_string()],
created_at: Some(dt.into()),
created_at: Some(dt),
updated_at: None,
};

let expected = "{\
\"id\":\"spam\",\
\"title\":\"Hello\",\
\"syntax\":\"python\",\
\"changesets\":[\
{\
\"version\":0,\
\"content\":\"print(\'Hello, World!\')\",\
\"created_at\":null,\
\"updated_at\":null\
}\
],\
\"content\":\"print(42)\",\
\"tags\":[\"foo\",\"bar\"],\
\"created_at\":\"2020-08-09T10:39:57+00:00\",\
\"updated_at\":null\
Expand Down
4 changes: 2 additions & 2 deletions src/storage/sql/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,14 +183,14 @@ impl Storage for SqlStorage {
Direction::Desc => query
.filter(
snippets::created_at
.le(chrono::DateTime::from(created_at))
.le(created_at)
.and(snippets::slug.ne(marker.id)),
)
.order_by(snippets::created_at.desc()),
Direction::Asc => query
.filter(
snippets::created_at
.ge(chrono::DateTime::from(created_at))
.ge(created_at)
.and(snippets::slug.ne(marker.id)),
)
.order_by(snippets::created_at.asc()),
Expand Down
48 changes: 24 additions & 24 deletions src/storage/sql/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ impl From<(SnippetRow, Vec<ChangesetRow>, Vec<TagRow>)> for Snippet {
updated_at,
}| {
let mut changeset = Changeset::new(version as usize, content);
changeset.updated_at = Some(updated_at.into());
changeset.created_at = Some(created_at.into());
changeset.updated_at = Some(updated_at);
changeset.created_at = Some(created_at);

changeset
},
Expand All @@ -60,8 +60,8 @@ impl From<(SnippetRow, Vec<ChangesetRow>, Vec<TagRow>)> for Snippet {

let mut snippet = Snippet::new(snippet_row.title, snippet_row.syntax, changesets, tags);
snippet.id = snippet_row.slug;
snippet.created_at = Some(snippet_row.created_at.into());
snippet.updated_at = Some(snippet_row.updated_at.into());
snippet.created_at = Some(snippet_row.created_at);
snippet.updated_at = Some(snippet_row.updated_at);

snippet
}
Expand Down Expand Up @@ -166,19 +166,19 @@ mod tests {
Changeset {
version: 1,
content: "print('Hello!')".to_string(),
created_at: Some(dt1.into()),
updated_at: Some(dt1.into()),
created_at: Some(dt1),
updated_at: Some(dt1),
},
Changeset {
version: 2,
content: "print('Hello, World!')".to_string(),
created_at: Some(dt2.into()),
updated_at: Some(dt2.into()),
created_at: Some(dt2),
updated_at: Some(dt2),
},
],
tags: vec!["spam".to_string(), "eggs".to_string()],
created_at: Some(dt1.into()),
updated_at: Some(dt2.into()),
created_at: Some(dt1),
updated_at: Some(dt2),
};

assert_eq!(actual, expected);
Expand Down Expand Up @@ -281,19 +281,19 @@ mod tests {
Changeset {
version: 1,
content: "print('Hello!')".to_string(),
created_at: Some(dt1.into()),
updated_at: Some(dt1.into()),
created_at: Some(dt1),
updated_at: Some(dt1),
},
Changeset {
version: 2,
content: "print('Hello, World!')".to_string(),
created_at: Some(dt2.into()),
updated_at: Some(dt2.into()),
created_at: Some(dt2),
updated_at: Some(dt2),
},
],
tags: vec!["spam".to_string(), "eggs".to_string()],
created_at: Some(dt1.into()),
updated_at: Some(dt2.into()),
created_at: Some(dt1),
updated_at: Some(dt2),
},
Snippet {
id: "eggs".to_string(),
Expand All @@ -302,12 +302,12 @@ mod tests {
changesets: vec![Changeset {
version: 1,
content: "println!(42);".to_string(),
created_at: Some(dt1.into()),
updated_at: Some(dt1.into()),
created_at: Some(dt1),
updated_at: Some(dt1),
}],
tags: vec![],
created_at: Some(dt1.into()),
updated_at: Some(dt2.into()),
created_at: Some(dt1),
updated_at: Some(dt2),
},
Snippet {
id: "bar".to_string(),
Expand All @@ -316,12 +316,12 @@ mod tests {
changesets: vec![Changeset {
version: 1,
content: "std::cout << 42;".to_string(),
created_at: Some(dt1.into()),
updated_at: Some(dt1.into()),
created_at: Some(dt1),
updated_at: Some(dt1),
}],
tags: vec!["bar".to_string()],
created_at: Some(dt1.into()),
updated_at: Some(dt2.into()),
created_at: Some(dt1),
updated_at: Some(dt2),
},
];
assert_eq!(actual, expected);
Expand Down
64 changes: 0 additions & 64 deletions src/storage/util.rs

This file was deleted.

0 comments on commit 3add960

Please sign in to comment.