mirror of
https://github.com/mre/idiomatic-rust
synced 2024-11-21 19:43:04 +00:00
make code typesafe
This commit is contained in:
parent
e3c051ab97
commit
e7447c8789
3 changed files with 181 additions and 15 deletions
62
render/Cargo.lock
generated
62
render/Cargo.lock
generated
|
@ -73,6 +73,15 @@ version = "1.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.3"
|
||||
|
@ -88,6 +97,16 @@ dependencies = [
|
|||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.2.1"
|
||||
|
@ -199,6 +218,7 @@ dependencies = [
|
|||
"itertools",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -249,6 +269,21 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.7.0"
|
||||
|
@ -258,12 +293,39 @@ dependencies = [
|
|||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
|
|
|
@ -11,3 +11,4 @@ indexmap = "2.2.1"
|
|||
itertools = "0.12.1"
|
||||
serde = { version = "1.0.196", features = ["serde_derive"] }
|
||||
serde_json = "1.0.113"
|
||||
url = { version = "2.5.0", features = ["serde"] }
|
||||
|
|
|
@ -3,7 +3,8 @@ use std::fs;
|
|||
use askama::Template;
|
||||
use indexmap::IndexMap;
|
||||
use itertools::Itertools;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "README.md")]
|
||||
|
@ -16,28 +17,130 @@ struct ReadmeTemplate {
|
|||
forum: YearMap,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
/// A tag is a special kind of string that
|
||||
/// - is lowercase
|
||||
/// - has no whitespace
|
||||
/// - has no special characters except for `-`
|
||||
/// - has no leading or trailing `-`
|
||||
/// - has no consecutive `-`
|
||||
/// - has no more than 50 characters
|
||||
/// - is not empty
|
||||
/// - only contains ASCII characters
|
||||
/// - does not contain numbers
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
||||
struct Tag(String);
|
||||
|
||||
impl TryFrom<String> for Tag {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
println!("value: {:?}", value);
|
||||
if value.is_empty() {
|
||||
return Err("Tag cannot be empty");
|
||||
}
|
||||
|
||||
if value.len() > 50 {
|
||||
return Err("Tag cannot be longer than 50 characters");
|
||||
}
|
||||
|
||||
if value.contains(|c: char| !c.is_ascii_lowercase() && c != '-') {
|
||||
return Err("Tag can only contain lowercase ASCII characters");
|
||||
}
|
||||
|
||||
if value.contains(|c: char| c.is_ascii_digit()) {
|
||||
return Err("Tag cannot contain numbers");
|
||||
}
|
||||
|
||||
if value.contains(|c: char| !c.is_ascii() && c != '-') {
|
||||
return Err("Tag can only contain ASCII characters and hyphens");
|
||||
}
|
||||
|
||||
if value.starts_with('-') || value.ends_with('-') {
|
||||
return Err("Tag cannot start or end with a hyphen");
|
||||
}
|
||||
|
||||
if value.contains("--") {
|
||||
return Err("Tag cannot contain consecutive hyphens");
|
||||
}
|
||||
|
||||
if value.contains(char::is_whitespace) {
|
||||
return Err("Tag cannot contain whitespace");
|
||||
}
|
||||
|
||||
Ok(Tag(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Tag {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Tag, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
Tag::try_from(s).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, Serialize)]
|
||||
enum Difficulty {
|
||||
#[serde(rename = "all")]
|
||||
All,
|
||||
#[serde(rename = "beginner")]
|
||||
Beginner,
|
||||
#[serde(rename = "intermediate")]
|
||||
Intermediate,
|
||||
#[serde(rename = "advanced")]
|
||||
Advanced,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, Serialize)]
|
||||
enum InteractivityLevel {
|
||||
#[serde(rename = "low")]
|
||||
Low,
|
||||
#[serde(rename = "medium")]
|
||||
Medium,
|
||||
#[serde(rename = "high")]
|
||||
High,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, Serialize, Eq, PartialEq, Ord, PartialOrd)]
|
||||
enum Category {
|
||||
#[serde(rename = "project")]
|
||||
Project,
|
||||
#[serde(rename = "workshop")]
|
||||
Workshop,
|
||||
#[serde(rename = "book")]
|
||||
Book,
|
||||
#[serde(rename = "article")]
|
||||
Article,
|
||||
#[serde(rename = "talk")]
|
||||
Talk,
|
||||
#[serde(rename = "forum")]
|
||||
Forum,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
struct Resource {
|
||||
title: String,
|
||||
url: String,
|
||||
url: Url,
|
||||
description: String,
|
||||
tags: Vec<String>,
|
||||
tags: Vec<Tag>,
|
||||
official: bool,
|
||||
year: usize,
|
||||
#[serde(rename = "difficultyLevel")]
|
||||
difficulty_level: String,
|
||||
difficulty_level: Difficulty,
|
||||
duration: Option<String>,
|
||||
#[serde(rename = "interactivityLevel")]
|
||||
interactivity_level: String,
|
||||
interactivity_level: InteractivityLevel,
|
||||
free: bool,
|
||||
category: String,
|
||||
category: Category,
|
||||
}
|
||||
|
||||
type Resources = Vec<Resource>;
|
||||
|
||||
type YearMap = IndexMap<usize, Resources>;
|
||||
|
||||
fn group_by_year(resources: &Resources, category: &str) -> YearMap {
|
||||
fn group_by_year(resources: &Resources, category: Category) -> YearMap {
|
||||
resources
|
||||
.iter()
|
||||
.filter(|r| r.category == category)
|
||||
|
@ -49,7 +152,7 @@ fn group_by_year(resources: &Resources, category: &str) -> YearMap {
|
|||
})
|
||||
}
|
||||
|
||||
fn sort_by_title(resources: &Resources, category: &str) -> Resources {
|
||||
fn sort_by_title(resources: &Resources, category: Category) -> Resources {
|
||||
resources
|
||||
.iter()
|
||||
.filter(|r| r.category == category)
|
||||
|
@ -63,12 +166,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
let resources: Resources = serde_json::from_reader(file)?;
|
||||
|
||||
let readme = ReadmeTemplate {
|
||||
projects: sort_by_title(&resources, "project"),
|
||||
workshops: sort_by_title(&resources, "workshop"),
|
||||
books: sort_by_title(&resources, "book"),
|
||||
articles: group_by_year(&resources, "article"),
|
||||
talks: group_by_year(&resources, "talk"),
|
||||
forum: group_by_year(&resources, "forum"),
|
||||
projects: sort_by_title(&resources, Category::Project),
|
||||
workshops: sort_by_title(&resources, Category::Workshop),
|
||||
books: sort_by_title(&resources, Category::Book),
|
||||
articles: group_by_year(&resources, Category::Article),
|
||||
talks: group_by_year(&resources, Category::Talk),
|
||||
forum: group_by_year(&resources, Category::Forum),
|
||||
};
|
||||
|
||||
fs::write("README.md", readme.render()?)?;
|
||||
|
|
Loading…
Reference in a new issue