Skip to content

Commit 4a8fbaf

Browse files
authored
Merge pull request #898 from itowlson/utility-templates
Templates for static fileserver and redirect
2 parents cfc203e + a5c37a0 commit 4a8fbaf

File tree

9 files changed

+143
-12
lines changed

9 files changed

+143
-12
lines changed

crates/templates/src/filters.rs

+35
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,38 @@ impl Filter for SnakeCaseFilter {
8585
Ok(input.to_value())
8686
}
8787
}
88+
89+
#[derive(Clone, liquid_derive::ParseFilter, liquid_derive::FilterReflection)]
90+
#[filter(
91+
name = "http_wildcard",
92+
description = "Add Spin HTTP wildcard suffix (/...) if needed.",
93+
parsed(HttpWildcardFilter)
94+
)]
95+
pub(crate) struct HttpWildcardFilterParser;
96+
97+
#[derive(Debug, Default, liquid_derive::Display_filter)]
98+
#[name = "http_wildcard"]
99+
struct HttpWildcardFilter;
100+
101+
impl Filter for HttpWildcardFilter {
102+
fn evaluate(
103+
&self,
104+
input: &dyn ValueView,
105+
_runtime: &dyn Runtime,
106+
) -> Result<liquid::model::Value, liquid_core::error::Error> {
107+
let input = input
108+
.as_scalar()
109+
.ok_or_else(|| liquid_core::error::Error::with_msg("String expected"))?;
110+
111+
let route = input.into_string().to_string();
112+
let wildcard_route = if route.ends_with("/...") {
113+
route
114+
} else if route.ends_with('/') {
115+
format!("{route}...")
116+
} else {
117+
format!("{route}/...")
118+
};
119+
120+
Ok(wildcard_route.to_value())
121+
}
122+
}

crates/templates/src/manager.rs

+27-2
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ mod tests {
391391

392392
use tempfile::tempdir;
393393

394-
use crate::RunOptions;
394+
use crate::{RunOptions, TemplateVariantKind};
395395

396396
use super::*;
397397

@@ -414,7 +414,7 @@ mod tests {
414414
PathBuf::from(crate_dir).join("tests")
415415
}
416416

417-
const TPLS_IN_THIS: usize = 9;
417+
const TPLS_IN_THIS: usize = 11;
418418

419419
#[tokio::test]
420420
async fn can_install_into_new_directory() {
@@ -945,4 +945,29 @@ mod tests {
945945
.expect_err("Expected to fail to add component, but it succeeded");
946946
}
947947
}
948+
949+
#[tokio::test]
950+
async fn cannot_new_a_component_only_template() {
951+
let temp_dir = tempdir().unwrap();
952+
let store = TemplateStore::new(temp_dir.path());
953+
let manager = TemplateManager { store };
954+
let source = TemplateSource::File(project_root());
955+
956+
manager
957+
.install(&source, &InstallOptions::default(), &DiscardingReporter)
958+
.await
959+
.unwrap();
960+
961+
let redirect = manager.get("redirect").unwrap().unwrap();
962+
assert!(!redirect.supports_variant(&TemplateVariantKind::NewApplication));
963+
assert!(redirect.supports_variant(&TemplateVariantKind::AddComponent));
964+
965+
let http_rust = manager.get("http-rust").unwrap().unwrap();
966+
assert!(http_rust.supports_variant(&TemplateVariantKind::NewApplication));
967+
assert!(http_rust.supports_variant(&TemplateVariantKind::AddComponent));
968+
969+
let http_empty = manager.get("http-empty").unwrap().unwrap();
970+
assert!(http_empty.supports_variant(&TemplateVariantKind::NewApplication));
971+
assert!(!http_empty.supports_variant(&TemplateVariantKind::AddComponent));
972+
}
948973
}

crates/templates/src/reader.rs

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub(crate) struct RawTemplateManifestV1 {
1818
pub id: String,
1919
pub description: Option<String>,
2020
pub trigger_type: Option<String>,
21+
pub new_application: Option<RawTemplateVariant>,
2122
pub add_component: Option<RawTemplateVariant>,
2223
pub parameters: Option<IndexMap<String, RawParameter>>,
2324
pub custom_filters: Option<Vec<RawCustomFilter>>,
@@ -26,6 +27,7 @@ pub(crate) struct RawTemplateManifestV1 {
2627
#[derive(Debug, Deserialize)]
2728
#[serde(deny_unknown_fields, rename_all = "snake_case")]
2829
pub(crate) struct RawTemplateVariant {
30+
pub supported: Option<bool>,
2931
pub skip_files: Option<Vec<String>>,
3032
pub skip_parameters: Option<Vec<String>>,
3133
pub snippets: Option<HashMap<String, String>>,

crates/templates/src/run.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,8 @@ impl Run {
415415
let mut builder = liquid::ParserBuilder::with_stdlib()
416416
.filter(crate::filters::KebabCaseFilterParser)
417417
.filter(crate::filters::PascalCaseFilterParser)
418-
.filter(crate::filters::SnakeCaseFilterParser);
418+
.filter(crate::filters::SnakeCaseFilterParser)
419+
.filter(crate::filters::HttpWildcardFilterParser);
419420
for filter in self.template.custom_filters() {
420421
builder = builder.filter(filter);
421422
}
@@ -470,13 +471,18 @@ impl PreparedTemplate {
470471
.into_iter()
471472
.map(|(path, content)| Self::render_one(path, content, &globals))
472473
.collect::<anyhow::Result<Vec<_>>>()?;
473-
let outputs = HashMap::from_iter(rendered);
474474

475475
let deltas = self
476476
.snippets
477477
.into_iter()
478478
.map(|so| Self::render_snippet(so, &globals))
479479
.collect::<anyhow::Result<Vec<_>>>()?;
480+
481+
if rendered.is_empty() && deltas.is_empty() {
482+
return Err(anyhow!("Nothing to create"));
483+
}
484+
485+
let outputs = HashMap::from_iter(rendered);
480486
Ok(TemplateOutputs {
481487
files: outputs,
482488
deltas,

crates/templates/src/template.rs

+28-8
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ impl Template {
104104
id: raw.id.clone(),
105105
description: raw.description.clone(),
106106
trigger: Self::parse_trigger_type(raw.trigger_type, layout),
107-
variants: Self::parse_template_variants(raw.add_component),
107+
variants: Self::parse_template_variants(raw.new_application, raw.add_component),
108108
parameters: Self::parse_parameters(&raw.parameters)?,
109109
custom_filters: Self::load_custom_filters(layout, &raw.custom_filters)?,
110110
snippets_dir,
@@ -200,21 +200,41 @@ impl Template {
200200
}
201201

202202
fn parse_template_variants(
203+
new_application: Option<RawTemplateVariant>,
203204
add_component: Option<RawTemplateVariant>,
204205
) -> HashMap<TemplateVariantKind, TemplateVariant> {
205206
let mut variants = HashMap::default();
206-
// TODO: in future we might have component-only templates
207-
variants.insert(
208-
TemplateVariantKind::NewApplication,
209-
TemplateVariant::default(),
210-
);
211-
if let Some(ac) = add_component {
212-
let vt = Self::parse_template_variant(ac);
207+
if let Some(vt) = Self::get_variant(new_application, true) {
208+
variants.insert(TemplateVariantKind::NewApplication, vt);
209+
}
210+
if let Some(vt) = Self::get_variant(add_component, false) {
213211
variants.insert(TemplateVariantKind::AddComponent, vt);
214212
}
215213
variants
216214
}
217215

216+
fn get_variant(
217+
raw: Option<RawTemplateVariant>,
218+
default_supported: bool,
219+
) -> Option<TemplateVariant> {
220+
match raw {
221+
None => {
222+
if default_supported {
223+
Some(Default::default())
224+
} else {
225+
None
226+
}
227+
}
228+
Some(rv) => {
229+
if rv.supported.unwrap_or(true) {
230+
Some(Self::parse_template_variant(rv))
231+
} else {
232+
None
233+
}
234+
}
235+
}
236+
}
237+
218238
fn parse_template_variant(raw: RawTemplateVariant) -> TemplateVariant {
219239
TemplateVariant {
220240
skip_files: raw.skip_files.unwrap_or_default(),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[[component]]
2+
source = { url = "https://github.com/fermyon/spin-redirect/releases/download/v0.0.1/redirect.wasm", digest = "sha256:d57c3d91e9b62a6b628516c6d11daf6681e1ca2355251a3672074cddefd7f391" }
3+
id = "{{ project-name }}"
4+
environment = { DESTINATION = "{{ redirect-to }}" }
5+
[component.trigger]
6+
route = "{{ redirect-from }}"
7+
executor = { type = "wagi" }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
manifest_version = "1"
2+
id = "redirect"
3+
description = "Redirects a HTTP route"
4+
trigger_type = "http"
5+
6+
[new_application]
7+
supported = false
8+
9+
[add_component]
10+
[add_component.snippets]
11+
component = "component.txt"
12+
13+
[parameters]
14+
redirect-from = { type = "string", prompt = "Redirect from", pattern = "^/\\S*$" }
15+
redirect-to = { type = "string", prompt = "Redirect to", pattern = "^/\\S*$" }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[[component]]
2+
source = { url = "https://github.com/fermyon/spin-fileserver/releases/download/v0.0.1/spin_static_fs.wasm", digest = "sha256:650376c33a0756b1a52cad7ca670f1126391b79050df0321407da9c741d32375" }
3+
id = "{{ project-name }}"
4+
files = [ { source = "{{ files-path }}", destination = "/" } ]
5+
[component.trigger]
6+
route = "{{ http-path | http_wildcard }}"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
manifest_version = "1"
2+
id = "static-fileserver"
3+
description = "Serves static files from an asset directory"
4+
trigger_type = "http"
5+
6+
[new_application]
7+
supported = false
8+
9+
[add_component]
10+
[add_component.snippets]
11+
component = "component.txt"
12+
13+
[parameters]
14+
http-path = { type = "string", prompt = "HTTP path", default = "/static/...", pattern = "^/\\S*$" }
15+
files-path = { type = "string", prompt = "Directory containing the files to serve", default = "assets", pattern = "^\\S+$" }

0 commit comments

Comments
 (0)