1use std::str::FromStr;
4
5use atelier_core::model::{Identifier, NamespaceID, ShapeID};
6pub use handlebars::RenderError;
7use handlebars::{
8 Context, Handlebars, Helper, HelperDef, HelperResult, Output, RenderContext, ScopedJson,
9};
10use serde::Serialize;
11use serde_json::Value;
12
13use crate::{strings, JsonMap, JsonValue};
14
15const DOCUMENTATION_TRAIT: &str = "smithy.api#documentation";
18const TRAIT_TRAIT: &str = "smithy.api#trait";
19
20const SIMPLE_SHAPES: &[&str] = &[
22 "string",
23 "integer",
24 "long",
25 "blob",
26 "boolean",
27 "byte",
28 "double",
29 "float",
30 "short",
31 "bigDecimal",
32 "bigInteger",
33 "timestamp",
34 "document",
35];
36
37const BASIC_TYPES: &[&str] = &[
39 "string",
40 "integer",
41 "long",
42 "blob",
43 "boolean",
44 "byte",
45 "double",
46 "float",
47 "short",
48 "list",
49 "map",
50 "union",
51 "bigDecimal",
52 "bigInteger",
53 "timestamp",
54 "document",
55];
56
57pub type Template<'template> = (&'template str, &'template str);
60
61#[derive(Default, Debug)]
62pub struct RenderConfig<'render> {
63 pub templates: Vec<Template<'render>>,
65 pub strict_mode: bool,
69}
70
71pub struct Renderer<'gen> {
73 hb: Handlebars<'gen>,
75}
76
77impl<'gen> Default for Renderer<'gen> {
78 fn default() -> Self {
79 Self::init(&RenderConfig::default()).unwrap()
81 }
82}
83
84impl<'gen> Renderer<'gen> {
85 pub fn init(config: &RenderConfig) -> Result<Self, crate::Error> {
87 let mut hb = Handlebars::new();
88 hb.set_strict_mode(config.strict_mode);
91 hb.register_escape_fn(handlebars::no_escape); add_base_helpers(&mut hb);
95 for t in &config.templates {
96 hb.register_template_string(t.0, t.1)?;
97 }
98
99 Ok(Self { hb })
100 }
101
102 pub fn add_template(&mut self, template: Template) -> Result<(), crate::Error> {
104 self.hb.register_template_string(template.0, template.1)?;
105 Ok(())
106 }
107
108 pub fn render_template<T>(&self, template: &str, data: &T) -> Result<String, crate::Error>
110 where
111 T: Serialize,
112 {
113 let rendered = self.hb.render_template(template, data)?;
114 Ok(rendered)
115 }
116
117 pub fn render<T, W>(
119 &self,
120 template_name: &str,
121 data: &T,
122 writer: &mut W,
123 ) -> Result<(), crate::Error>
124 where
125 T: Serialize,
126 W: std::io::Write,
127 {
128 self.hb.render_to_write(template_name, data, writer)?;
129 Ok(())
130 }
131}
132
133fn arg_as_string<'reg, 'rc>(
134 h: &'reg Helper<'reg, 'rc>,
135 n: usize,
136 tag: &str,
137) -> Result<&'rc str, RenderError> {
138 h.param(n)
140 .ok_or_else(|| RenderError::new(format!("missing string param after {tag}")))?
141 .value()
142 .as_str()
143 .ok_or_else(|| {
144 RenderError::new(format!(
145 "{} expects string param, not {:?}",
146 tag,
147 h.param(n).unwrap().value()
148 ))
149 })
150}
151
152fn arg_as_obj<'reg, 'rc>(
153 h: &'reg Helper<'reg, 'rc>,
154 n: usize,
155 tag: &str,
156) -> Result<&'rc serde_json::Map<String, serde_json::Value>, RenderError> {
157 h.param(n)
159 .ok_or_else(|| RenderError::new(format!("missing object param after {tag}")))?
160 .value()
161 .as_object()
162 .ok_or_else(|| {
163 RenderError::new(format!(
164 "{} expects object param, not {:?}",
165 tag,
166 h.param(n).unwrap().value()
167 ))
168 })
169}
170
171fn arg_as_array<'reg, 'rc>(
172 h: &'reg Helper<'reg, 'rc>,
173 n: usize,
174 tag: &str,
175) -> Result<&'rc Vec<serde_json::Value>, RenderError> {
176 h.param(n)
178 .ok_or_else(|| RenderError::new(format!("missing array param after {tag}")))?
179 .value()
180 .as_array()
181 .ok_or_else(|| {
182 RenderError::new(format!(
183 "{} expects array param, not {:?}",
184 tag,
185 h.param(n).unwrap().value()
186 ))
187 })
188}
189
190#[derive(Clone, Copy)]
191struct ShapeHelper {}
192
193fn to_sorted_array<S: AsRef<str>>(mut shapes: Vec<(S, &Value)>) -> JsonValue {
195 shapes.sort_unstable_by(|a, b| {
197 lexical_sort::natural_lexical_only_alnum_cmp(a.0.as_ref(), b.0.as_ref())
198 });
199
200 let shapes = shapes
201 .into_iter()
202 .map(|(k, v)| (k.as_ref().to_string(), v.as_object().unwrap().clone()))
203 .map(|(k, mut v)| {
204 v.insert("_key".to_string(), serde_json::Value::String(k));
205 serde_json::Value::Object(v)
206 })
207 .collect::<Vec<Value>>();
208 Value::Array(shapes)
209}
210
211impl HelperDef for ShapeHelper {
212 fn call_inner<'reg: 'rc, 'rc>(
213 &self,
214 h: &Helper<'reg, 'rc>,
215 _reg: &'reg Handlebars<'reg>,
216 _ctx: &'rc Context,
217 _rc: &mut RenderContext<'reg, 'rc>,
218 ) -> Result<ScopedJson<'reg, 'rc>, RenderError> {
219 let shape_kind = arg_as_string(h, 0, "filter_shapes")?.to_string();
220 let arr = arg_as_array(h, 1, "filter_shapes")?;
221
222 let shapes = arr
224 .iter ()
225 .filter(|v| {
226 matches!(v.get("type"), Some(serde_json::Value::String(kind))
227 if (&shape_kind == "simple" && SIMPLE_SHAPES.contains(&kind.as_str()) && !val_is_trait(v))
228 || (&shape_kind == "types" && BASIC_TYPES.contains(&kind.as_str()) && !val_is_trait(v))
229 || (&shape_kind == "trait" && val_is_trait(v))
230 || (&shape_kind != "trait" && &shape_kind == kind && !val_is_trait(v))
231 )
232 })
233 .cloned()
234 .collect::<Vec<Value>>();
235 Ok(ScopedJson::Derived(Value::Array(shapes)))
236 }
237}
238
239#[derive(Clone, Copy)]
240struct NamespaceHelper {}
241
242impl HelperDef for NamespaceHelper {
243 fn call_inner<'reg: 'rc, 'rc>(
244 &self,
245 h: &Helper<'reg, 'rc>,
246 _reg: &'reg Handlebars<'reg>,
247 _ctx: &'rc Context,
248 _rc: &mut RenderContext<'reg, 'rc>,
249 ) -> Result<ScopedJson<'reg, 'rc>, RenderError> {
250 let namespace = arg_as_string(h, 0, "filter_namespace")?;
251 let namespace = NamespaceID::from_str(namespace)
252 .map_err(|e| RenderError::new(format!("invalid namespace {e}")))?;
253 let obj = arg_as_obj(h, 1, "filter_namespace")?;
254
255 let shapes = obj
256 .iter()
257 .filter_map(|(k, v)| match ShapeID::from_str(k) {
258 Ok(id) => Some((id, v)),
259 _ => None,
260 })
261 .filter(|(id, _)| id.namespace() == &namespace)
262 .map(|(id, v)| (id.to_string(), v))
263 .collect::<Vec<(String, &Value)>>();
264 Ok(ScopedJson::Derived(to_sorted_array(shapes)))
265 }
266}
267
268#[derive(Clone, Copy)]
269struct SimpleTypeHelper {}
270
271impl HelperDef for SimpleTypeHelper {
272 fn call_inner<'reg: 'rc, 'rc>(
273 &self,
274 h: &Helper<'reg, 'rc>,
275 _reg: &'reg Handlebars<'reg>,
276 _ctx: &'rc Context,
277 _rc: &mut RenderContext<'reg, 'rc>,
278 ) -> Result<ScopedJson<'reg, 'rc>, RenderError> {
279 let type_name = arg_as_string(h, 0, "is_simple")?;
280 Ok(ScopedJson::Derived(serde_json::Value::Bool(
281 SIMPLE_SHAPES.contains(&type_name),
282 )))
283 }
284}
285
286#[derive(Clone, Copy)]
287struct DocHelper {}
288
289impl HelperDef for DocHelper {
290 fn call_inner<'reg: 'rc, 'rc>(
291 &self,
292 h: &Helper<'reg, 'rc>,
293 _reg: &'reg Handlebars<'reg>,
294 _ctx: &'rc Context,
295 _rc: &mut RenderContext<'reg, 'rc>,
296 ) -> Result<ScopedJson<'reg, 'rc>, RenderError> {
297 let mut doc = String::new();
298 let shape_props = arg_as_obj(h, 0, "doc")?;
299 if let Some(JsonValue::Object(traits)) = shape_props.get("traits") {
300 if let Some(JsonValue::String(doc_value)) = traits.get(DOCUMENTATION_TRAIT) {
301 doc = doc_value.clone();
302 }
304 }
305 Ok(ScopedJson::Derived(serde_json::Value::String(doc)))
306 }
307}
308
309fn map_is_trait(shape: &JsonMap) -> bool {
352 if let Some(JsonValue::Object(traits)) = shape.get("traits") {
353 traits.get(TRAIT_TRAIT).is_some()
354 } else {
355 false
356 }
357}
358
359fn val_is_trait(shape: &JsonValue) -> bool {
361 if let Some(JsonValue::Object(traits)) = shape.get("traits") {
362 traits.get(TRAIT_TRAIT).is_some()
363 } else {
364 false
365 }
366}
367
368#[derive(Clone, Copy)]
369struct TraitsHelper {}
370
371impl HelperDef for TraitsHelper {
373 fn call_inner<'reg: 'rc, 'rc>(
374 &self,
375 h: &Helper<'reg, 'rc>,
376 _reg: &'reg Handlebars<'reg>,
377 _ctx: &'rc Context,
378 _rc: &mut RenderContext<'reg, 'rc>,
379 ) -> Result<ScopedJson<'reg, 'rc>, RenderError> {
380 let mut traits_no_doc = JsonMap::new();
381
382 let shape_props = arg_as_obj(h, 0, "traits")?;
383 if let Some(JsonValue::Object(traits)) = shape_props.get("traits") {
384 for (k, v) in traits.iter() {
385 if k != DOCUMENTATION_TRAIT && k != TRAIT_TRAIT {
386 traits_no_doc.insert(k.clone(), v.clone());
387 }
388 }
389 }
390 Ok(ScopedJson::Derived(serde_json::Value::Object(
391 traits_no_doc,
392 )))
393 }
394}
395
396#[derive(Clone, Copy)]
412struct IsTraitHelper {}
413
414impl HelperDef for IsTraitHelper {
416 fn call_inner<'reg: 'rc, 'rc>(
417 &self,
418 h: &Helper<'reg, 'rc>,
419 _reg: &'reg Handlebars<'reg>,
420 _ctx: &'rc Context,
421 _rc: &mut RenderContext<'reg, 'rc>,
422 ) -> Result<ScopedJson<'reg, 'rc>, RenderError> {
423 let shape = arg_as_obj(h, 0, "is_trait")?;
424 Ok(ScopedJson::Derived(serde_json::Value::Bool(map_is_trait(
425 shape,
426 ))))
427 }
428}
429
430fn add_base_helpers(hb: &mut Handlebars) {
432 hb.register_helper("filter_shapes", Box::new(ShapeHelper {}));
436
437 hb.register_helper("filter_namespace", Box::new(NamespaceHelper {}));
441
442 hb.register_helper("is_simple", Box::new(SimpleTypeHelper {}));
444
445 hb.register_helper("doc", Box::new(DocHelper {}));
447
448 hb.register_helper("traits", Box::new(TraitsHelper {}));
450
451 hb.register_helper("is_trait", Box::new(IsTraitHelper {}));
453
454 hb.register_helper(
458 "namespace_name",
459 Box::new(
460 |h: &Helper,
461 _r: &Handlebars,
462 _: &Context,
463 _rc: &mut RenderContext,
464 out: &mut dyn Output|
465 -> HelperResult {
466 let id = arg_as_string(h, 0, "namespace")?;
468 let id = ShapeID::from_str(id).map_err(|e| {
469 RenderError::new(format!("invalid shape id {e} for namespace_name"))
470 })?;
471 out.write(&id.namespace().to_string())?;
472 Ok(())
473 },
474 ),
475 );
476
477 hb.register_helper(
478 "typ",
479 Box::new(
480 |h: &Helper,
481 _r: &Handlebars,
482 _: &Context,
483 _rc: &mut RenderContext,
484 out: &mut dyn Output|
485 -> HelperResult {
486 let typ = arg_as_string(h, 0, "typ")?;
487 let sid = ShapeID::from_str(typ).unwrap();
488 let sid_ns = sid.namespace();
490
491 let link: String = if sid_ns == &NamespaceID::new_unchecked("smithy.api") {
492 sid.shape_name().to_string()
494 } else {
495 match arg_as_string(h, 1, "typ") {
496 Ok(ns) if sid_ns.to_string() == ns => {
498 let id_shape = sid.shape_name().to_string();
499 format!(
500 "<a href=\"#{}\">{}</a>",
501 &strings::to_snake_case(&id_shape),
502 &id_shape,
503 )
504 }
505 _ => format!(
506 "<a href=\"./{}.html#{}\">{}</a>",
507 &strings::to_snake_case(&sid_ns.to_string()),
508 &strings::to_snake_case(&sid.shape_name().to_string()),
509 sid
510 ),
511 }
512 };
513 out.write(&link)?;
514 Ok(())
515 },
516 ),
517 );
518
519 hb.register_helper(
523 "shape_name",
524 Box::new(
525 |h: &Helper,
526 _r: &Handlebars,
527 _: &Context,
528 _rc: &mut RenderContext,
529 out: &mut dyn Output|
530 -> HelperResult {
531 let id = arg_as_string(h, 0, "shape_name")?;
532 let id = ShapeID::from_str(id).map_err(|e| {
533 RenderError::new(format!("invalid shape id {e} for shape_name"))
534 })?;
535 out.write(&id.shape_name().to_string())?;
536 Ok(())
537 },
538 ),
539 );
540
541 hb.register_helper(
545 "member_name",
546 Box::new(
547 |h: &Helper,
548 _r: &Handlebars,
549 _: &Context,
550 _rc: &mut RenderContext,
551 out: &mut dyn Output|
552 -> HelperResult {
553 let id = arg_as_string(h, 0, "member_name")?;
554 let id = Identifier::from_str(id).map_err(|e| {
555 RenderError::new(format!("invalid member id {e} for member_name"))
556 })?;
557 out.write(&id.to_string())?;
558 Ok(())
559 },
560 ),
561 );
562
563 hb.register_helper(
567 "to_pascal_case",
568 Box::new(
569 |h: &Helper,
570 _r: &Handlebars,
571 _: &Context,
572 _rc: &mut RenderContext,
573 out: &mut dyn Output|
574 -> HelperResult {
575 let id = arg_as_string(h, 0, "to_pascal_case")?;
576 out.write(&strings::to_pascal_case(id))?;
577 Ok(())
578 },
579 ),
580 );
581
582 hb.register_helper(
586 "to_snake_case",
587 Box::new(
588 |h: &Helper,
589 _r: &Handlebars,
590 _: &Context,
591 _rc: &mut RenderContext,
592 out: &mut dyn Output|
593 -> HelperResult {
594 let id = arg_as_string(h, 0, "to_snake_case")?;
595 out.write(&strings::to_snake_case(id))?;
596 Ok(())
597 },
598 ),
599 );
600}