A little app to serve my photography from my personal website

basic validation

ericwood.org bd8c773a e9eeda00

Waiting for spindle ...
+174 -8
+160 -6
Cargo.lock
··· 3 3 version = 4 4 4 5 5 [[package]] 6 + name = "aho-corasick" 7 + version = "1.1.4" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" 10 + dependencies = [ 11 + "memchr", 12 + ] 13 + 14 + [[package]] 6 15 name = "allocator-api2" 7 16 version = "0.2.21" 8 17 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 802 811 dependencies = [ 803 812 "equivalent", 804 813 "hashbrown 0.16.0", 814 + "serde", 815 + "serde_core", 805 816 ] 806 817 807 818 [[package]] ··· 822 833 checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" 823 834 dependencies = [ 824 835 "libc", 836 + ] 837 + 838 + [[package]] 839 + name = "itertools" 840 + version = "0.14.0" 841 + source = "registry+https://github.com/rust-lang/crates.io-index" 842 + checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" 843 + dependencies = [ 844 + "either", 825 845 ] 826 846 827 847 [[package]] ··· 1108 1128 ] 1109 1129 1110 1130 [[package]] 1131 + name = "paste" 1132 + version = "1.0.15" 1133 + source = "registry+https://github.com/rust-lang/crates.io-index" 1134 + checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 1135 + 1136 + [[package]] 1111 1137 name = "pem-rfc7468" 1112 1138 version = "0.7.0" 1113 1139 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1135 1161 "minijinja-autoreload", 1136 1162 "serde", 1137 1163 "serde_html_form", 1164 + "serde_valid", 1138 1165 "sqlx", 1139 - "thiserror", 1166 + "thiserror 2.0.17", 1140 1167 "tokio", 1141 1168 "tower-http", 1142 1169 "urlencoding", ··· 1200 1227 ] 1201 1228 1202 1229 [[package]] 1230 + name = "proc-macro-error-attr2" 1231 + version = "2.0.0" 1232 + source = "registry+https://github.com/rust-lang/crates.io-index" 1233 + checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" 1234 + dependencies = [ 1235 + "proc-macro2", 1236 + "quote", 1237 + ] 1238 + 1239 + [[package]] 1240 + name = "proc-macro-error2" 1241 + version = "2.0.1" 1242 + source = "registry+https://github.com/rust-lang/crates.io-index" 1243 + checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" 1244 + dependencies = [ 1245 + "proc-macro-error-attr2", 1246 + "proc-macro2", 1247 + "quote", 1248 + ] 1249 + 1250 + [[package]] 1203 1251 name = "proc-macro2" 1204 1252 version = "1.0.101" 1205 1253 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1255 1303 dependencies = [ 1256 1304 "bitflags 2.9.4", 1257 1305 ] 1306 + 1307 + [[package]] 1308 + name = "regex" 1309 + version = "1.12.2" 1310 + source = "registry+https://github.com/rust-lang/crates.io-index" 1311 + checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" 1312 + dependencies = [ 1313 + "aho-corasick", 1314 + "memchr", 1315 + "regex-automata", 1316 + "regex-syntax", 1317 + ] 1318 + 1319 + [[package]] 1320 + name = "regex-automata" 1321 + version = "0.4.13" 1322 + source = "registry+https://github.com/rust-lang/crates.io-index" 1323 + checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" 1324 + dependencies = [ 1325 + "aho-corasick", 1326 + "memchr", 1327 + "regex-syntax", 1328 + ] 1329 + 1330 + [[package]] 1331 + name = "regex-syntax" 1332 + version = "0.8.8" 1333 + source = "registry+https://github.com/rust-lang/crates.io-index" 1334 + checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" 1258 1335 1259 1336 [[package]] 1260 1337 name = "rsa" ··· 1389 1466 ] 1390 1467 1391 1468 [[package]] 1469 + name = "serde_valid" 1470 + version = "2.0.0" 1471 + source = "registry+https://github.com/rust-lang/crates.io-index" 1472 + checksum = "de7f50148816c0fbeab7089476c91dbfae94bd891cc4e2c535f30b54ceb90ad2" 1473 + dependencies = [ 1474 + "indexmap", 1475 + "itertools", 1476 + "num-traits", 1477 + "once_cell", 1478 + "paste", 1479 + "regex", 1480 + "serde", 1481 + "serde_json", 1482 + "serde_valid_derive", 1483 + "serde_valid_literal", 1484 + "thiserror 1.0.69", 1485 + "unicode-segmentation", 1486 + ] 1487 + 1488 + [[package]] 1489 + name = "serde_valid_derive" 1490 + version = "2.0.0" 1491 + source = "registry+https://github.com/rust-lang/crates.io-index" 1492 + checksum = "c030af8a53ec37caf1ed98e5ce02cf7d4f65e637e8eaa089ed785689e86c9046" 1493 + dependencies = [ 1494 + "itertools", 1495 + "paste", 1496 + "proc-macro-error2", 1497 + "proc-macro2", 1498 + "quote", 1499 + "strsim", 1500 + "syn", 1501 + ] 1502 + 1503 + [[package]] 1504 + name = "serde_valid_literal" 1505 + version = "2.0.0" 1506 + source = "registry+https://github.com/rust-lang/crates.io-index" 1507 + checksum = "50b2fb68eefc69006cebe74b0f672ae68464c72b150dacd00d0877227c1300f7" 1508 + dependencies = [ 1509 + "paste", 1510 + "regex", 1511 + ] 1512 + 1513 + [[package]] 1392 1514 name = "sha1" 1393 1515 version = "0.10.6" 1394 1516 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1510 1632 "serde_json", 1511 1633 "sha2", 1512 1634 "smallvec", 1513 - "thiserror", 1635 + "thiserror 2.0.17", 1514 1636 "tokio", 1515 1637 "tokio-stream", 1516 1638 "tracing", ··· 1592 1714 "smallvec", 1593 1715 "sqlx-core", 1594 1716 "stringprep", 1595 - "thiserror", 1717 + "thiserror 2.0.17", 1596 1718 "tracing", 1597 1719 "whoami", 1598 1720 ] ··· 1629 1751 "smallvec", 1630 1752 "sqlx-core", 1631 1753 "stringprep", 1632 - "thiserror", 1754 + "thiserror 2.0.17", 1633 1755 "tracing", 1634 1756 "whoami", 1635 1757 ] ··· 1653 1775 "serde", 1654 1776 "serde_urlencoded", 1655 1777 "sqlx-core", 1656 - "thiserror", 1778 + "thiserror 2.0.17", 1657 1779 "tracing", 1658 1780 "url", 1659 1781 ] ··· 1676 1798 ] 1677 1799 1678 1800 [[package]] 1801 + name = "strsim" 1802 + version = "0.11.1" 1803 + source = "registry+https://github.com/rust-lang/crates.io-index" 1804 + checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1805 + 1806 + [[package]] 1679 1807 name = "subtle" 1680 1808 version = "2.6.1" 1681 1809 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1711 1839 1712 1840 [[package]] 1713 1841 name = "thiserror" 1842 + version = "1.0.69" 1843 + source = "registry+https://github.com/rust-lang/crates.io-index" 1844 + checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 1845 + dependencies = [ 1846 + "thiserror-impl 1.0.69", 1847 + ] 1848 + 1849 + [[package]] 1850 + name = "thiserror" 1714 1851 version = "2.0.17" 1715 1852 source = "registry+https://github.com/rust-lang/crates.io-index" 1716 1853 checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" 1717 1854 dependencies = [ 1718 - "thiserror-impl", 1855 + "thiserror-impl 2.0.17", 1856 + ] 1857 + 1858 + [[package]] 1859 + name = "thiserror-impl" 1860 + version = "1.0.69" 1861 + source = "registry+https://github.com/rust-lang/crates.io-index" 1862 + checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 1863 + dependencies = [ 1864 + "proc-macro2", 1865 + "quote", 1866 + "syn", 1719 1867 ] 1720 1868 1721 1869 [[package]] ··· 1928 2076 version = "0.1.3" 1929 2077 source = "registry+https://github.com/rust-lang/crates.io-index" 1930 2078 checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" 2079 + 2080 + [[package]] 2081 + name = "unicode-segmentation" 2082 + version = "1.12.0" 2083 + source = "registry+https://github.com/rust-lang/crates.io-index" 2084 + checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 1931 2085 1932 2086 [[package]] 1933 2087 name = "url"
+1
Cargo.toml
··· 13 13 minijinja-autoreload = "2.12.0" 14 14 serde = "1.0.228" 15 15 serde_html_form = "0.2.8" 16 + serde_valid = "2.0.0" 16 17 sqlx = { version = "0.8.6", features = ["runtime-tokio", "sqlite"] } 17 18 thiserror = "2.0.17" 18 19 tokio = { version = "1.47.1", features = ["rt-multi-thread"] }
+4
src/app_error.rs
··· 11 11 #[error("internal server error")] 12 12 DbError(#[from] sqlx::Error), 13 13 14 + #[error("bad request")] 15 + ValidationError(#[from] serde_valid::validation::Errors), 16 + 14 17 #[error("internal server error")] 15 18 Anyhow(#[from] anyhow::Error), 16 19 } ··· 19 22 pub fn status_code(&self) -> StatusCode { 20 23 match self { 21 24 Self::NotFound => StatusCode::NOT_FOUND, 25 + Self::ValidationError(_) => StatusCode::BAD_REQUEST, 22 26 Self::DbError(_) | Self::Anyhow(_) => StatusCode::INTERNAL_SERVER_ERROR, 23 27 } 24 28 }
+8 -1
src/routes/photos/index.rs
··· 4 4 use axum_extra::extract::Query; 5 5 use minijinja::context; 6 6 use serde::{self, Deserialize, Serialize}; 7 + use serde_valid::Validate; 7 8 8 9 use crate::{ 9 10 AppError, AppState, ··· 32 33 action: String, 33 34 } 34 35 35 - #[derive(Deserialize, Serialize, Clone)] 36 + #[derive(Deserialize, Serialize, Clone, Validate)] 36 37 pub struct IndexParams { 38 + #[validate(minimum = 1)] 37 39 page: Option<u32>, 40 + 41 + #[validate(minimum = 1)] 42 + #[validate(maximum = 100)] 38 43 limit: Option<u32>, 44 + 39 45 tags: Option<Vec<String>>, 40 46 sort: Option<SortField>, 41 47 dir: Option<SortDirection>, ··· 59 65 query: Query<IndexParams>, 60 66 State(state): State<Arc<AppState>>, 61 67 ) -> Result<Html<String>, AppError> { 68 + query.validate()?; 62 69 let query = query.0; 63 70 let default = IndexParams::default(); 64 71 let page = query.page.unwrap_or(default.page.unwrap());
+1 -1
templates/photos/index.jinja
··· 61 61 {% for tag in current_tags %} 62 62 <li class="selected"> 63 63 {{ tag.name }} 64 - <a href="/?{{ tag.action }}"> 64 + <a href="?{{ tag.action }}"> 65 65 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> 66 66 <use xlink:href="#close-icon" /> 67 67 </svg>