rocket_sass_fairing/
lib.rs1use normpath::PathExt;
24use rocket::{
25 error,
26 fairing::{self, Fairing, Info, Kind},
27 http::ContentType,
28 info, info_,
29 outcome::IntoOutcome,
30 request::{self, FromRequest, Request},
31 response::{self, Responder, Response},
32 Build, Orbit, Rocket,
33};
34use std::path::PathBuf;
35
36pub struct SassSheet {
37 content: String,
38 cache_max_age: i32,
39 path: PathBuf,
40}
41
42impl SassSheet {
43 pub fn fairing() -> impl Fairing {
44 SassSheetFairing
45 }
46}
47
48struct SassSheetFairing;
49
50#[rocket::async_trait]
51impl Fairing for SassSheetFairing {
52 fn info(&self) -> Info {
53 Info {
54 kind: Kind::Ignite | Kind::Liftoff,
55 name: "Sass Sheet",
56 }
57 }
58
59 async fn on_ignite(&self, rocket: Rocket<Build>) -> fairing::Result {
60 use rocket::figment::value::magic::RelativePathBuf;
61
62 let configured_path = rocket
63 .figment()
64 .extract_inner::<RelativePathBuf>("sass_sheet_path")
65 .map(|path| path.relative());
66
67 let relative_path = match configured_path {
68 Ok(path) => path,
69 Err(e) if e.missing() => "assets/style.scss".into(),
70 Err(e) => {
71 rocket::config::pretty_print_error(e);
72 return Err(rocket);
73 }
74 };
75
76 let path = match relative_path.normalize() {
77 Ok(path) => path.into_path_buf(),
78 Err(e) => {
79 error!(
80 "Invalid sass sheet file '{}': {}.",
81 relative_path.display(),
82 e
83 );
84 return Err(rocket);
85 }
86 };
87
88 info!("Compiling sass file '{}'...", relative_path.display());
89 let options = grass::Options::default().style(grass::OutputStyle::Compressed);
90 let compiled_css = match grass::from_path(&path.to_string_lossy(), &options) {
91 Ok(css) => css,
92 Err(e) => {
93 error!("Couldn't compile sass: {}", e);
94 return Err(rocket);
95 }
96 };
97
98 let cache_max_age = rocket
99 .figment()
100 .extract_inner::<i32>("assets_max_age")
101 .unwrap_or(86400);
102
103 Ok(rocket.manage(SassSheet {
104 content: compiled_css,
105 cache_max_age,
106 path,
107 }))
108 }
109
110 async fn on_liftoff(&self, rocket: &Rocket<Orbit>) {
111 use rocket::{figment::Source, log::PaintExt, yansi::Paint};
112
113 let state = rocket
114 .state::<SassSheet>()
115 .expect("SassSheet registered in on_ignite");
116
117 info!("{}{}:", Paint::emoji("📐 "), Paint::magenta("Assets"));
118 info_!("sheet path: {}", Paint::white(Source::from(&*state.path)));
119 info_!("cache max age: {}", Paint::white(state.cache_max_age));
120 }
121}
122
123#[rocket::async_trait]
124impl<'r> FromRequest<'r> for &'r SassSheet {
125 type Error = ();
126 async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, ()> {
127 req.rocket().state::<SassSheet>().or_forward(())
128 }
129}
130impl<'r, 'o: 'r> Responder<'r, 'o> for &'o SassSheet {
131 fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
132 let content: &str = self.content.as_ref();
133 Response::build_from(content.respond_to(req)?)
134 .header(ContentType::CSS)
135 .raw_header("Cache-control", format!("max-age={}", self.cache_max_age))
136 .ok()
137 }
138}