1use std::{
4 collections::{BTreeMap, HashMap},
5 fmt::Write as _,
6 path::Path,
7 str::FromStr,
8 string::ToString,
9};
10
11use atelier_core::{
12 model::{
13 shapes::{
14 AppliedTraits, HasTraits, ListOrSet, Map as MapShape, MemberShape, Operation, Service,
15 ShapeKind, Simple, StructureOrUnion,
16 },
17 values::Value,
18 HasIdentity, Identifier, Model, NamespaceID, ShapeID,
19 },
20 prelude::{
21 prelude_namespace_id, prelude_shape_named, PRELUDE_NAMESPACE, SHAPE_BIGDECIMAL,
22 SHAPE_BIGINTEGER, SHAPE_BLOB, SHAPE_BOOLEAN, SHAPE_BYTE, SHAPE_DOCUMENT, SHAPE_DOUBLE,
23 SHAPE_FLOAT, SHAPE_INTEGER, SHAPE_LONG, SHAPE_PRIMITIVEBOOLEAN, SHAPE_PRIMITIVEBYTE,
24 SHAPE_PRIMITIVEDOUBLE, SHAPE_PRIMITIVEFLOAT, SHAPE_PRIMITIVEINTEGER, SHAPE_PRIMITIVELONG,
25 SHAPE_PRIMITIVESHORT, SHAPE_SHORT, SHAPE_STRING, SHAPE_TIMESTAMP, TRAIT_DEPRECATED,
26 TRAIT_DOCUMENTATION, TRAIT_TRAIT, TRAIT_UNSTABLE,
27 },
28};
29
30#[cfg(feature = "wasmbus")]
31use crate::wasmbus_model::Wasmbus;
32use crate::{
33 config::{LanguageConfig, OutputLanguage},
34 error::{print_warning, Error, Result},
35 format::{self, SourceFormatter},
36 gen::CodeGen,
37 model::{
38 codegen_rust_trait, get_operation, get_sorted_fields, get_trait, has_default,
39 is_opt_namespace, value_to_json, wasmcloud_model_namespace, CommentKind, PackageName, Ty,
40 },
41 render::Renderer,
42 wasmbus_model::CodegenRust,
43 writer::Writer,
44 BytesMut, JsonValue, ParamMap,
45};
46
47const WASMBUS_RPC_CRATE: &str = "wasmbus_rpc";
48const DEFAULT_MAP_TYPE: &str = "std::collections::HashMap";
49const DEFAULT_LIST_TYPE: &str = "Vec";
50const DEFAULT_SET_TYPE: &str = "std::collections::BTreeSet";
51
52#[derive(Eq, Ord, PartialOrd, PartialEq)]
55struct Declaration(u8, BytesMut);
56
57type ShapeList<'model> = Vec<(&'model ShapeID, &'model AppliedTraits, &'model ShapeKind)>;
58
59#[derive(Default)]
60pub struct RustCodeGen<'model> {
61 pub(crate) namespace: Option<NamespaceID>,
63 pub(crate) packages: HashMap<String, PackageName>,
64 pub(crate) import_core: String,
65 pub(crate) model: Option<&'model Model>,
66 pub(crate) with_lifetime: BTreeMap<ShapeID, bool>,
67}
68
69impl<'model> RustCodeGen<'model> {
70 pub fn new(model: Option<&'model Model>) -> Self {
71 Self {
72 model,
73 namespace: None,
74 packages: HashMap::default(),
75 import_core: String::default(),
76 with_lifetime: BTreeMap::default(),
77 }
78 }
79}
80
81struct ServiceInfo<'model> {
82 id: &'model Identifier,
83 traits: &'model AppliedTraits,
84 service: &'model Service,
85}
86
87impl<'model> ServiceInfo<'model> {
88 fn wasmbus_contract_id(&self) -> Option<String> {
89 match get_trait(self.traits, crate::model::wasmbus_trait()) {
90 Ok(Some(Wasmbus { contract_id: Some(contract_id), .. })) => Some(contract_id),
91 _ => None,
92 }
93 }
94}
95
96#[non_exhaustive]
97enum MethodArgFlags {
98 Normal,
99 ToString,
101}
102
103#[derive(Copy)]
108pub(crate) enum Lifetime<'lt> {
109 None, Any, L(&'lt str), }
113
114impl<'lt> Lifetime<'lt> {
115 pub(crate) fn annotate(&self) -> String {
117 match self {
118 Lifetime::None => String::default(),
119 Lifetime::Any => "<'_>".to_string(),
120 Lifetime::L(s) => format!("<'{s}>"),
121 }
122 }
123
124 pub(crate) fn is_some(&self) -> bool {
126 !matches!(self, Lifetime::None)
127 }
128}
129
130impl<'lt> Clone for Lifetime<'lt> {
131 fn clone(&self) -> Self {
132 *self
133 }
134}
135
136pub fn is_rust_primitive(id: &ShapeID) -> bool {
138 (id.namespace() == prelude_namespace_id()
139 && matches!(
140 id.shape_name().to_string().as_str(),
141 "Boolean" | "Byte" | "Short" | "Integer" | "Long" | "Float" | "Double"
142 ))
143 || (id.namespace() == wasmcloud_model_namespace()
144 && matches!(
145 id.shape_name().to_string().as_str(),
146 "U64" | "U32" | "U16" | "U8" | "I64" | "I32" | "I16" | "I8" | "F64" | "F32"
147 ))
148}
149
150impl<'model> CodeGen for RustCodeGen<'model> {
151 fn output_language(&self) -> OutputLanguage {
152 OutputLanguage::Rust
153 }
154
155 fn init(
159 &mut self,
160 model: Option<&Model>,
161 _lc: &LanguageConfig,
162 _output_dir: Option<&Path>,
163 _renderer: &mut Renderer,
164 ) -> std::result::Result<(), Error> {
165 self.namespace = None;
166 self.import_core = WASMBUS_RPC_CRATE.to_string();
167
168 if let Some(model) = model {
169 if let Some(Value::Array(codegen_min)) = model.metadata_value("codegen") {
170 let current_ver =
171 semver::Version::parse(env!("CARGO_PKG_VERSION")).map_err(|e| {
172 Error::InvalidModel(format!(
173 "parse error for weld-codegen package version: {e}"
174 ))
175 })?;
176 for val in codegen_min.iter() {
177 if let Value::Object(map) = val {
178 if let Some(Value::String(lang)) = map.get("language") {
179 if lang.as_str() == "rust" {
180 if let Some(Value::String(ver)) = map.get("min_version") {
181 let min_ver = semver::Version::parse(ver).map_err(|e| {
182 Error::InvalidModel(format!(
183 "metadata parse error for codegen {{ language=rust, \
184 min_version={ver} }}: {e}"
185 ))
186 })?;
187 if min_ver.gt(¤t_ver) {
188 return Err(Error::Model(format!(
189 "model requires weld-codegen version >= {min_ver}"
190 )));
191 }
192 } else {
193 return Err(Error::Model(
194 "missing 'min_version' in metadata.codegen for lang=rust"
195 .to_string(),
196 ));
197 }
198 }
199 }
200 }
201 }
202 }
203 if let Some(packages) = model.metadata_value("package") {
204 let packages: Vec<PackageName> = serde_json::from_value(value_to_json(packages))
205 .map_err(|e| {
206 Error::Model(format!(
207 "invalid metadata format for package, expecting format \
208 '[{{namespace:\"org.example\",crate:\"path::module\"}}]': {e}"
209 ))
210 })?;
211 for p in packages.iter() {
212 self.packages.insert(p.namespace.to_string(), p.clone());
213 }
214 }
215 self.map_lifetimes(model);
216 }
217 Ok(())
218 }
219
220 fn source_formatter(&self, mut args: Vec<String>) -> Result<Box<dyn SourceFormatter>> {
223 let formatter = if args.is_empty() {
224 RustSourceFormatter::default()
225 } else {
226 let program = args.remove(0);
227 RustSourceFormatter { program, args }
228 };
229 Ok(Box::new(formatter))
230 }
231
232 #[allow(unused_variables)]
236 fn init_file(
237 &mut self,
238 w: &mut Writer,
239 model: &Model,
240 file_config: &crate::config::OutputFile,
241 params: &ParamMap,
242 ) -> Result<()> {
243 self.namespace = match &file_config.namespace {
244 Some(ns) => Some(NamespaceID::from_str(ns)?),
245 None => None,
246 };
247 if let Some(ref ns) = self.namespace {
248 if self.packages.get(&ns.to_string()).is_none() {
249 print_warning(&format!(
250 concat!(
251 "no package metadata defined for namespace {}.",
252 " Add a declaration like this at the top of fhe .smithy file: ",
253 " metadata package = [ {{ namespace: \"{}\", crate: \"crate_name\" }} ]"
254 ),
255 ns, ns
256 ));
257 }
258 }
259 self.import_core = match params.get("crate") {
260 Some(JsonValue::String(c)) if c == WASMBUS_RPC_CRATE => "crate".to_string(),
261 _ => WASMBUS_RPC_CRATE.to_string(),
262 };
263 Ok(())
264 }
265
266 fn write_source_file_header(
267 &mut self,
268 w: &mut Writer,
269 model: &Model,
270 params: &ParamMap,
271 ) -> Result<()> {
272 write!(
273 w,
274 r#"// This file is @generated by wasmcloud/weld-codegen {}.
275 // It is not intended for manual editing.
276 "#,
277 env!("CARGO_PKG_VERSION"),
278 )
279 .unwrap();
280 if let Some(ns) = &self.namespace {
281 writeln!(w, "// namespace: {ns}").unwrap();
282 }
283 w.write(b"\n");
284
285 match &self.namespace {
286 Some(n) if n == wasmcloud_model_namespace() => {
287 w.write("#[allow(unused_imports)] use serde::{{Deserialize, Serialize}};\n");
289 if !params.contains_key("no_serde") {
290 writeln!(w,
293 r#"#[allow(unused_imports)]
294 use {}::{{cbor::{{Write,Encoder,Decoder}},error::{{RpcError,RpcResult}}}};"#,
295 self.import_core,
296 ).unwrap();
297 }
298 }
299 _ => {
300 writeln!(
305 w,
306 r#"
307 #[allow(unused_imports)]
308 use {}::{{
309 cbor::*,
310 common::{{
311 Context, deserialize, Message, MessageFormat, message_format,
312 MessageDispatch, SendOpts, serialize, Transport,
313 }},
314 error::{{RpcError,RpcResult}},
315 Timestamp,
316 }};
317 #[allow(unused_imports)]
318 use serde::{{Deserialize, Serialize}};
319 #[allow(unused_imports)]
320 use async_trait::async_trait;
321 #[allow(unused_imports)]
322 use std::{{borrow::Borrow, borrow::Cow, io::Write, string::ToString}};"#,
323 &self.import_core,
324 )
325 .unwrap();
326 }
327 }
328 write!(
329 w,
330 "\n#[allow(dead_code)] pub const SMITHY_VERSION : &str = \"{}\";\n\n",
331 model.smithy_version()
332 )
333 .unwrap();
334 Ok(())
335 }
336
337 fn declare_types(&mut self, w: &mut Writer, model: &Model, params: &ParamMap) -> Result<()> {
338 let ns = self.namespace.clone();
339
340 let mut shapes = model
341 .shapes()
342 .filter(|s| is_opt_namespace(s.id(), &ns))
343 .map(|s| (s.id(), s.traits(), s.body()))
344 .collect::<ShapeList>();
345 shapes.sort_by_key(|v| v.0);
347
348 for (id, traits, shape) in shapes.into_iter() {
349 if let Some(cg) = get_trait::<CodegenRust>(traits, codegen_rust_trait())? {
350 if cg.skip {
351 continue;
352 }
353 }
354 let lt = match self.has_lifetime(id) {
355 true => Lifetime::L("v"),
356 false => Lifetime::None,
357 };
358 match shape {
359 ShapeKind::Simple(simple) => {
360 self.declare_simple_shape(w, id.shape_name(), traits, simple, lt)?;
361 }
362 ShapeKind::Map(map) => {
363 self.declare_map_shape(w, id.shape_name(), traits, map, lt)?;
364 }
365 ShapeKind::List(list) => {
366 self.declare_list_or_set_shape(
367 w,
368 id.shape_name(),
369 traits,
370 list,
371 DEFAULT_LIST_TYPE,
372 lt,
373 )?;
374 }
375 ShapeKind::Set(set) => {
376 self.declare_list_or_set_shape(
377 w,
378 id.shape_name(),
379 traits,
380 set,
381 DEFAULT_SET_TYPE,
382 lt,
383 )?;
384 }
385 ShapeKind::Structure(strukt) => {
386 self.declare_structure_shape(w, id, traits, strukt, lt)?;
387 }
388 ShapeKind::Union(strukt) => {
389 self.declare_union_shape(w, id, traits, strukt, lt)?;
390 }
391 ShapeKind::Operation(_)
392 | ShapeKind::Resource(_)
393 | ShapeKind::Service(_)
394 | ShapeKind::Unresolved => {}
395 }
396
397 if !traits.contains_key(&prelude_shape_named(TRAIT_TRAIT).unwrap())
403 && !params.contains_key("no_serde")
404 {
407 self.declare_shape_encoder(w, id, shape)?;
408 self.declare_shape_decoder(w, id, shape)?;
409 }
410 }
411 Ok(())
412 }
413
414 fn write_services(&mut self, w: &mut Writer, model: &Model, _params: &ParamMap) -> Result<()> {
415 let ns = self.namespace.clone();
416 let mut services: Vec<(&ShapeID, &AppliedTraits, &ShapeKind)> = model
417 .shapes()
418 .filter(|s| is_opt_namespace(s.id(), &ns))
419 .map(|s| (s.id(), s.traits(), s.body()))
420 .collect();
421 services.sort_by_key(|me| me.0);
423 for (id, traits, shape) in services.iter() {
424 if let ShapeKind::Service(service) = shape {
425 let service = ServiceInfo { id: id.shape_name(), service, traits };
426 self.write_service_interface(w, model, &service)?;
427 self.write_service_receiver(w, model, &service)?;
428 self.write_service_sender(w, model, &service)?;
429 }
430 }
431 Ok(())
432 }
433
434 fn write_comment(&mut self, w: &mut Writer, kind: CommentKind, line: &str) {
436 w.write(match kind {
437 CommentKind::Documentation => "/// ",
438 CommentKind::Inner => "// ",
439 CommentKind::InQuote => "// ", });
441 w.write(line);
442 w.write(b"\n");
443 }
444
445 fn to_method_name_case(&self, name: &str) -> String {
446 crate::strings::to_snake_case(name)
447 }
448
449 fn to_field_name_case(&self, name: &str) -> String {
450 crate::strings::to_snake_case(name)
451 }
452
453 fn to_type_name_case(&self, name: &str) -> String {
454 crate::strings::to_pascal_case(name)
455 }
456
457 fn get_file_extension(&self) -> &'static str {
459 "rs"
460 }
461}
462
463impl<'model> RustCodeGen<'model> {
464 fn map_lifetime(
469 &mut self,
470 model: &Model,
471 id: &ShapeID,
472 _traits: &AppliedTraits,
473 shape: &ShapeKind,
474 ) -> bool {
475 if let Some(val) = self.with_lifetime.get(id) {
476 return *val;
477 }
478 match shape {
485 ShapeKind::Simple(_) => {}
486 ShapeKind::Map(map) => {
487 let value = map.value().target();
488 if let Some(target) = model.shape(value) {
489 if self.map_lifetime(model, value, target.traits(), target.body()) {
490 self.with_lifetime.insert(id.clone(), true);
491 return true;
492 }
493 } else if self.has_lifetime(value) {
494 self.with_lifetime.insert(id.clone(), true);
495 return true;
496 }
497 let key = map.key().target();
498 if let Some(target) = model.shape(key) {
499 if self.map_lifetime(model, key, target.traits(), target.body()) {
500 self.with_lifetime.insert(id.clone(), true);
501 return true;
502 }
503 } else if self.has_lifetime(key) {
504 self.with_lifetime.insert(id.clone(), true);
505 return true;
506 }
507 }
508 ShapeKind::List(list_or_set) | ShapeKind::Set(list_or_set) => {
509 let member = list_or_set.member().target();
510 if let Some(target) = model.shape(member) {
511 if self.map_lifetime(model, member, target.traits(), target.body()) {
512 self.with_lifetime.insert(id.clone(), true);
513 return true;
514 }
515 } else if self.has_lifetime(member) {
516 self.with_lifetime.insert(id.clone(), true);
517 return true;
518 }
519 }
520 ShapeKind::Structure(struct_or_union) | ShapeKind::Union(struct_or_union) => {
521 for member in struct_or_union.members() {
522 let field = member.target();
523 if let Some(target) = model.shape(field) {
524 if self.map_lifetime(model, field, target.traits(), target.body()) {
525 self.with_lifetime.insert(id.clone(), true);
526 return true;
527 }
528 } else if self.has_lifetime(field) {
529 self.with_lifetime.insert(id.clone(), true);
530 return true;
531 }
532 }
533 }
534 ShapeKind::Operation(_)
535 | ShapeKind::Resource(_)
536 | ShapeKind::Service(_)
537 | ShapeKind::Unresolved => {}
538 }
539 self.with_lifetime.insert(id.clone(), false);
540 false
541 }
542
543 pub(crate) fn map_lifetimes(&mut self, model: &Model) {
544 let document_ref = ShapeID::new_unchecked("org.wasmcloud.common", "DocumentRef", None);
547 self.with_lifetime.insert(document_ref, true);
548 for (id, traits, shape) in model.shapes().map(|s| (s.id(), s.traits(), s.body())) {
549 self.map_lifetime(model, id, traits, shape);
550 }
551 }
552
553 pub(crate) fn has_lifetime(&self, id: &ShapeID) -> bool {
555 *self.with_lifetime.get(id).unwrap_or(&false)
556 }
557
558 fn apply_documentation_traits(
560 &mut self,
561 w: &mut Writer,
562 id: &Identifier,
563 traits: &AppliedTraits,
564 ) {
565 if let Some(Some(Value::String(text))) =
566 traits.get(&prelude_shape_named(TRAIT_DOCUMENTATION).unwrap())
567 {
568 self.write_documentation(w, id, text);
569 }
570
571 if let Some(Some(Value::Object(map))) =
573 traits.get(&prelude_shape_named(TRAIT_DEPRECATED).unwrap())
574 {
575 w.write(b"#[deprecated(");
576 if let Some(Value::String(since)) = map.get("since") {
577 writeln!(w, "since=\"{since}\"").unwrap();
578 }
579 if let Some(Value::String(message)) = map.get("message") {
580 writeln!(w, "note=\"{message}\"").unwrap();
581 }
582 w.write(b")\n");
583 }
584
585 if traits.get(&prelude_shape_named(TRAIT_UNSTABLE).unwrap()).is_some() {
587 self.write_comment(w, CommentKind::Documentation, "@unstable");
588 }
589 }
590
591 pub(crate) fn field_type_string(
593 &self,
594 field: &MemberShape,
595 lt: Lifetime<'_>,
596 ) -> Result<String> {
597 let target = field.target();
598 self.type_string(
599 if is_optional_type(field) {
600 Ty::Opt(target)
601 } else {
602 Ty::Shape(target)
603 },
604 lt,
605 )
606 }
607
608 pub(crate) fn type_string(&self, ty: Ty<'_>, lt: Lifetime<'_>) -> Result<String> {
611 let mut s = String::new();
612 match ty {
613 Ty::Opt(id) => {
614 s.push_str("Option<");
615 s.push_str(&self.type_string(Ty::Shape(id), lt)?);
616 s.push('>');
617 }
618 Ty::Ref(id) => {
619 s.push('&');
620 s.push_str(&self.type_string(Ty::Shape(id), lt)?);
621 }
622 Ty::Ptr(_) => {
623 unreachable!() }
625 Ty::Shape(id) => {
626 let name = id.shape_name().to_string();
627 if id.namespace() == prelude_namespace_id() {
628 let ty = match name.as_ref() {
629 SHAPE_BLOB => "Vec<u8>",
630 SHAPE_BOOLEAN | SHAPE_PRIMITIVEBOOLEAN => "bool",
631 SHAPE_STRING => "String",
632 SHAPE_BYTE | SHAPE_PRIMITIVEBYTE => "i8",
633 SHAPE_SHORT | SHAPE_PRIMITIVESHORT => "i16",
634 SHAPE_INTEGER | SHAPE_PRIMITIVEINTEGER => "i32",
635 SHAPE_LONG | SHAPE_PRIMITIVELONG => "i64",
636 SHAPE_FLOAT | SHAPE_PRIMITIVEFLOAT => "f32",
637 SHAPE_DOUBLE | SHAPE_PRIMITIVEDOUBLE => "f64",
638 SHAPE_DOCUMENT => {
639 s.push_str(&self.import_core);
640 "::common::Document"
641 }
642 SHAPE_TIMESTAMP => "Timestamp",
643 SHAPE_BIGINTEGER => {
644 cfg_if::cfg_if! {
645 if #[cfg(feature = "BigInteger")] { "BigInteger" } else { return Err(Error::UnsupportedBigInteger) }
646 }
647 }
648 SHAPE_BIGDECIMAL => {
649 cfg_if::cfg_if! {
650 if #[cfg(feature = "BigDecimal")] { "BigDecimal" } else { return Err(Error::UnsupportedBigDecimal) }
651 }
652 }
653 _ => return Err(Error::UnsupportedType(name)),
654 };
655 s.push_str(ty);
656 } else if id.namespace() == wasmcloud_model_namespace() {
657 match name.as_str() {
658 "U64" | "U32" | "U16" | "U8" => {
659 s.push('u');
660 s.push_str(&name[1..])
661 }
662 "I64" | "I32" | "I16" | "I8" => {
663 s.push('i');
664 s.push_str(&name[1..]);
665 }
666 "F64" => s.push_str("f64"),
667 "F32" => s.push_str("f32"),
668 _ => {
669 if self.namespace.is_none()
670 || self.namespace.as_ref().unwrap() != id.namespace()
671 {
672 s.push_str(&self.import_core);
673 s.push_str("::model::");
674 }
675 s.push_str(&self.to_type_name_case(&name));
676 }
677 };
678 } else if self.namespace.is_some()
679 && id.namespace() == self.namespace.as_ref().unwrap()
680 {
681 s.push_str(&self.to_type_name_case(&id.shape_name().to_string()));
683 if self.has_lifetime(id) {
684 s.push_str(<.annotate());
685 }
686 } else {
687 s.push_str(&self.get_crate_path(id)?);
688 s.push_str(&self.to_type_name_case(&id.shape_name().to_string()));
689 if self.has_lifetime(id) {
690 s.push_str(<.annotate());
691 }
692 }
693 }
694 }
695 Ok(s)
696 }
697
698 fn write_type(&mut self, w: &mut Writer, ty: Ty<'_>, lt: Lifetime<'_>) -> Result<()> {
700 w.write(&self.type_string(ty, lt)?);
701 Ok(())
702 }
703
704 fn declare_simple_shape(
706 &mut self,
707 w: &mut Writer,
708 id: &Identifier,
709 traits: &AppliedTraits,
710 simple: &Simple,
711 lt: Lifetime,
712 ) -> Result<()> {
713 self.apply_documentation_traits(w, id, traits);
714 w.write(b"pub type ");
715 self.write_ident(w, id);
716 if lt.is_some() {
717 w.write(lt.annotate().as_bytes());
718 }
719 w.write(b" = ");
720 let ty = match simple {
721 Simple::Blob => "Vec<u8>",
722 Simple::Boolean => "bool",
723 Simple::String => "String",
724 Simple::Byte => "i8",
725 Simple::Short => "i16",
726 Simple::Integer => "i32",
727 Simple::Long => "i64",
728 Simple::Float => "f32",
729 Simple::Double => "f64",
730 Simple::Document => {
731 w.write(&self.import_core);
732 "::common::Document"
733 }
734 Simple::Timestamp => "Timestamp",
735 Simple::BigInteger => {
736 cfg_if::cfg_if! {
737 if #[cfg(feature = "BigInteger")] { "BigInteger" } else { return Err(Error::UnsupportedBigInteger) }
738 }
739 }
740 Simple::BigDecimal => {
741 cfg_if::cfg_if! {
742 if #[cfg(feature = "BigDecimal")] { "BigDecimal" } else { return Err(Error::UnsupportedBigDecimal) }
743 }
744 }
745 };
746 w.write(ty);
747 w.write(b";\n\n");
751 Ok(())
752 }
753
754 fn declare_map_shape(
755 &mut self,
756 w: &mut Writer,
757 id: &Identifier,
758 traits: &AppliedTraits,
759 shape: &MapShape,
760 lt: Lifetime<'_>,
761 ) -> Result<()> {
762 self.apply_documentation_traits(w, id, traits);
763 w.write(b"pub type ");
764 self.write_ident(w, id);
765 if lt.is_some() {
766 w.write(lt.annotate().as_bytes());
767 }
768 w.write(b" = ");
769 w.write(DEFAULT_MAP_TYPE);
770 w.write(b"<");
771 self.write_type(w, Ty::Shape(shape.key().target()), lt)?;
772 w.write(b",");
773 self.write_type(w, Ty::Shape(shape.value().target()), lt)?;
774 w.write(b">;\n\n");
775 Ok(())
776 }
777
778 fn declare_list_or_set_shape(
779 &mut self,
780 w: &mut Writer,
781 id: &Identifier,
782 traits: &AppliedTraits,
783 shape: &ListOrSet,
784 typ: &str,
785 lt: Lifetime<'_>,
786 ) -> Result<()> {
787 self.apply_documentation_traits(w, id, traits);
788 w.write(b"pub type ");
789 self.write_ident(w, id);
790 if lt.is_some() {
791 w.write(lt.annotate().as_bytes());
792 }
793 w.write(b" = ");
794 w.write(typ);
795 w.write(b"<");
796 self.write_type(w, Ty::Shape(shape.member().target()), lt)?;
797 w.write(b">;\n\n");
798 Ok(())
799 }
800
801 fn declare_structure_shape(
802 &mut self,
803 w: &mut Writer,
804 id: &ShapeID,
805 traits: &AppliedTraits,
806 strukt: &StructureOrUnion,
807 lt: Lifetime<'_>,
808 ) -> Result<()> {
809 let is_trait_struct = traits.contains_key(&prelude_shape_named(TRAIT_TRAIT).unwrap());
810 let ident = id.shape_name();
811 self.apply_documentation_traits(w, ident, traits);
812 let preface = self.build_preface(id, traits);
813 w.write(&preface.derives());
814 if preface.non_exhaustive {
815 w.write(b"#[non_exhaustive]\n");
816 }
817 w.write(b"pub struct ");
818 self.write_ident(w, ident);
819 w.write(<.annotate());
820 w.write(b" {\n");
821 let (fields, _is_numbered) = get_sorted_fields(ident, strukt)?;
822 for member in fields.iter() {
823 self.apply_documentation_traits(w, member.id(), member.traits());
824 let (field_name, ser_name) = self.get_field_name_and_ser_name(member)?;
827 if ser_name != field_name {
828 writeln!(w, " #[serde(rename=\"{ser_name}\")] ").unwrap();
829 }
830 let lt = if self.has_lifetime(member.target()) { lt } else { Lifetime::None };
831
832 #[cfg(feature = "wasmbus")]
854 if member.target() == &ShapeID::new_unchecked("smithy.api", "Blob", None) {
855 w.write(r#" #[serde(with="serde_bytes")] "#);
856 }
857
858 if is_optional_type(member) {
859 w.write(r#" #[serde(default, skip_serializing_if = "Option::is_none")] "#);
860 } else if (is_trait_struct && !member.is_required())
861 || has_default(self.model.unwrap(), member)
862 || (member.target()
863 == &ShapeID::new_unchecked(PRELUDE_NAMESPACE, SHAPE_DOCUMENT, None))
864 {
865 w.write(r#" #[serde(default)] "#);
878 }
879 w.write(
880 format!(
881 " pub {}: {},\n",
882 &field_name,
883 self.field_type_string(member, lt)?
884 )
885 .as_bytes(),
886 );
887 }
888 w.write(b"}\n\n");
889 Ok(())
890 }
891
892 fn declare_union_shape(
893 &mut self,
894 w: &mut Writer,
895 id: &ShapeID,
896 traits: &AppliedTraits,
897 strukt: &StructureOrUnion,
898 lt: Lifetime<'_>,
899 ) -> Result<()> {
900 let ident = id.shape_name();
901 let (fields, is_numbered) = get_sorted_fields(ident, strukt)?;
902 if !is_numbered {
903 return Err(Error::Model(format!(
904 "union {ident} must have numbered fields"
905 )));
906 }
907 self.apply_documentation_traits(w, ident, traits);
908 let mut preface = self.build_preface(id, traits);
909 preface.default = false;
910 w.write(&preface.derives());
911 if preface.non_exhaustive {
912 w.write(b"#[non_exhaustive]\n");
913 }
914 w.write(b"pub enum ");
915 self.write_ident(w, ident);
916 if lt.is_some() {
917 w.write(lt.annotate().as_bytes());
918 }
919 w.write(b" {\n");
920 for member in fields.iter() {
921 self.apply_documentation_traits(w, member.id(), member.traits());
922 let field_num = member.field_num().unwrap();
923 writeln!(w, "/// n({field_num})").unwrap();
924 let variant_name = self.to_type_name_case(&member.id().to_string());
925 if member.target() == crate::model::unit_shape() {
926 writeln!(w, "{variant_name},\n").unwrap();
927 } else {
928 writeln!(
929 w,
930 "{}({}),",
931 variant_name,
932 self.type_string(Ty::Shape(member.target()), lt)?
933 )
934 .unwrap();
935 }
936 }
937 w.write(b"}\n\n");
938 Ok(())
939 }
940
941 fn write_service_interface(
943 &mut self,
944 w: &mut Writer,
945 model: &Model,
946 service: &ServiceInfo,
947 ) -> Result<()> {
948 self.apply_documentation_traits(w, service.id, service.traits);
949
950 #[cfg(feature = "wasmbus")]
951 self.add_wasmbus_comments(w, service)?;
952
953 w.write(b"#[async_trait]\npub trait ");
954 self.write_ident(w, service.id);
955 w.write(b"{\n");
956 self.write_service_contract_getter(w, service)?;
957
958 for operation in service.service.operations() {
959 if let Some(ref ns) = self.namespace {
961 if operation.namespace() != ns {
962 continue;
963 }
964 }
965 let (op, op_traits) = get_operation(model, operation, service.id)?;
966 let method_id = operation.shape_name();
967 let _flags = self.write_method_signature(w, method_id, op_traits, op)?;
968 w.write(b";\n");
969 }
970 w.write(b"}\n\n");
971 Ok(())
972 }
973
974 fn write_service_contract_getter(
976 &mut self,
977 w: &mut Writer,
978 service: &ServiceInfo,
979 ) -> Result<()> {
980 if let Some(contract_id) = service.wasmbus_contract_id() {
981 writeln!(
982 w,
983 r#"
984 /// returns the capability contract id for this interface
985 fn contract_id() -> &'static str {{ "{contract_id}" }}"#
986 )
987 .unwrap();
988 }
989 Ok(())
990 }
991
992 #[cfg(feature = "wasmbus")]
993 fn add_wasmbus_comments(&mut self, w: &mut Writer, service: &ServiceInfo) -> Result<()> {
994 let wasmbus: Option<Wasmbus> = get_trait(service.traits, crate::model::wasmbus_trait())?;
996 if let Some(wasmbus) = wasmbus {
997 if let Some(contract_id) = service.wasmbus_contract_id() {
998 let text = format!("wasmbus.contractId: {contract_id}");
999 self.write_documentation(w, service.id, &text);
1000 }
1001 if wasmbus.provider_receive {
1002 let text = "wasmbus.providerReceive";
1003 self.write_documentation(w, service.id, text);
1004 }
1005 if wasmbus.actor_receive {
1006 let text = "wasmbus.actorReceive";
1007 self.write_documentation(w, service.id, text);
1008 }
1009 }
1010 Ok(())
1011 }
1012
1013 fn write_method_signature(
1016 &mut self,
1017 w: &mut Writer,
1018 method_id: &Identifier,
1019 method_traits: &AppliedTraits,
1020 op: &Operation,
1021 ) -> Result<MethodArgFlags> {
1022 let method_name = self.to_method_name(method_id, method_traits);
1023 let mut arg_flags = MethodArgFlags::Normal;
1024 self.apply_documentation_traits(w, method_id, method_traits);
1025 let input_lt = if let Some(input_type) = op.input() {
1026 self.has_lifetime(input_type)
1027 } else {
1028 false
1029 };
1030 let output_lt = if let Some(output_type) = op.output() {
1031 self.has_lifetime(output_type)
1032 } else {
1033 false
1034 };
1035 let func_lt = if input_lt { "'vin," } else { "" };
1036 w.write(b"async fn ");
1037 w.write(&method_name);
1038 if let Some(input_type) = op.input() {
1039 if input_type == &ShapeID::new_unchecked(PRELUDE_NAMESPACE, SHAPE_STRING, None) {
1040 arg_flags = MethodArgFlags::ToString;
1041 write!(w, "<{func_lt}TS:ToString + ?Sized + std::marker::Sync>").unwrap();
1042 } else if input_lt {
1043 write!(w, "<{func_lt}>").unwrap();
1044 }
1045 }
1046 w.write(b"(&self, ctx: &Context");
1047 if let Some(input_type) = op.input() {
1048 w.write(b", arg: "); if matches!(arg_flags, MethodArgFlags::ToString) {
1050 w.write(b"&TS");
1051 } else {
1052 self.write_type(
1053 w,
1054 Ty::Ref(input_type),
1055 if input_lt { Lifetime::L("vin") } else { Lifetime::None },
1056 )?;
1057 }
1058 }
1059 w.write(b") -> RpcResult<");
1060 if let Some(output_type) = op.output() {
1061 self.write_type(
1062 w,
1063 Ty::Shape(output_type),
1064 if output_lt { Lifetime::L("static") } else { Lifetime::None },
1065 )?;
1066 } else {
1067 w.write(b"()");
1068 }
1069 w.write(b">");
1070 Ok(arg_flags)
1071 }
1072
1073 fn write_service_receiver(
1075 &mut self,
1076 w: &mut Writer,
1077 model: &Model,
1078 service: &ServiceInfo,
1079 ) -> Result<()> {
1080 let doc = format!(
1081 "{}Receiver receives messages defined in the {} service trait",
1082 service.id, service.id
1083 );
1084 self.write_comment(w, CommentKind::Documentation, &doc);
1085 self.apply_documentation_traits(w, service.id, service.traits);
1086 w.write(b"#[doc(hidden)]\n#[async_trait]\npub trait ");
1087 self.write_ident_with_suffix(w, service.id, "Receiver")?;
1088 w.write(b" : MessageDispatch + ");
1089 self.write_ident(w, service.id);
1090 let proto = crate::model::wasmbus_proto(service.traits)?;
1091 let has_cbor = proto.map(|pv| pv.has_cbor()).unwrap_or(false);
1092 w.write(
1093 br#"{
1094 async fn dispatch(&self, ctx:&Context, message:Message<'_>) -> Result<Vec<u8>, RpcError> {
1095 match message.method {
1096 "#,
1097 );
1098
1099 for method_id in service.service.operations() {
1100 if let Some(ref ns) = self.namespace {
1102 if method_id.namespace() != ns {
1103 continue;
1104 }
1105 }
1106 let method_ident = method_id.shape_name();
1107 let (op, method_traits) = get_operation(model, method_id, service.id)?;
1108 w.write(b"\"");
1109 w.write(&self.op_dispatch_name(method_ident));
1110 w.write(b"\" => {\n");
1111 if let Some(op_input) = op.input() {
1112 let borrowed = self.has_lifetime(op_input);
1113 let symbol = op_input.shape_name().to_string();
1114 if has_cbor {
1115 let crate_prefix = self.get_crate_path(op_input)?;
1116 writeln!(
1117 w,
1118 r#"
1119 let value : {} = {}::common::{}(&message.arg, &{}decode_{})
1120 .map_err(|e| RpcError::Deser(format!("'{}': {{}}", e)))?;"#,
1121 self.type_string(Ty::Shape(op_input), Lifetime::Any)?,
1122 self.import_core,
1123 if borrowed { "decode_borrowed" } else { "decode" },
1124 &crate_prefix,
1125 crate::strings::to_snake_case(&symbol),
1126 &symbol,
1127 )
1128 .unwrap()
1129 } else {
1130 writeln!(
1131 w,
1132 r#"
1133 let value: {} = {}::common::deserialize(&message.arg)
1134 .map_err(|e| RpcError::Deser(format!("'{}': {{}}", e)))?;
1135 "#,
1136 self.type_string(Ty::Shape(op_input), Lifetime::Any)?,
1137 self.import_core,
1138 &symbol,
1139 )
1140 .unwrap()
1141 }
1142 }
1143 if op.output().is_some() {
1145 w.write(b"let resp = ");
1146 } else {
1147 w.write(b"let _resp = ");
1148 }
1149 let method_name = self.to_method_name(method_ident, method_traits);
1150 self.write_ident(w, service.id); w.write(b"::");
1152 w.write(&method_name);
1153 w.write(b"(self, ctx");
1154 if op.has_input() {
1155 w.write(b", &value");
1156 }
1157 w.write(b").await?;\n");
1158
1159 if let Some(_op_output) = op.output() {
1160 if has_cbor {
1162 writeln!(
1163 w,
1164 "let mut e = {}::cbor::vec_encoder(true);",
1165 &self.import_core
1166 )
1167 .unwrap();
1168 let s = self.encode_shape_id(
1169 _op_output,
1170 crate::encode_rust::ValExpr::Plain("resp"),
1171 true,
1172 )?;
1173 w.write(&s);
1174 w.write(b"let buf = e.into_inner();\n");
1175 } else {
1176 writeln!(
1177 w,
1178 "let buf = {}::common::serialize(&resp)?;\n",
1179 &self.import_core
1180 )
1181 .unwrap();
1182 }
1183 } else {
1184 w.write(b"let buf = Vec::new();\n");
1185 }
1186 w.write(b"Ok(buf)\n}\n");
1187 }
1188 w.write(b"_ => Err(RpcError::MethodNotHandled(format!(\"");
1189 self.write_ident(w, service.id);
1190 w.write(b"::{}\", message.method))),\n");
1191 w.write(b"}\n}\n}\n\n"); Ok(())
1194 }
1195
1196 fn write_service_sender(
1199 &mut self,
1200 w: &mut Writer,
1201 model: &Model,
1202 service: &ServiceInfo,
1203 ) -> Result<()> {
1204 let doc = format!(
1205 "{}Sender sends messages to a {} service",
1206 service.id, service.id
1207 );
1208 self.write_comment(w, CommentKind::Documentation, &doc);
1209 self.apply_documentation_traits(w, service.id, service.traits);
1210 let proto = crate::model::wasmbus_proto(service.traits)?;
1211 let has_cbor = proto.map(|pv| pv.has_cbor()).unwrap_or(false);
1212 writeln!(
1213 w,
1214 r#"/// client for sending {} messages
1215 #[derive(Clone, Debug)]
1216 pub struct {}Sender<T:Transport> {{ transport: T }}
1217
1218 impl<T:Transport> {}Sender<T> {{
1219 /// Constructs a {}Sender with the specified transport
1220 pub fn via(transport: T) -> Self {{
1221 Self{{ transport }}
1222 }}
1223
1224 pub fn set_timeout(&self, interval: std::time::Duration) {{
1225 self.transport.set_timeout(interval);
1226 }}
1227 }}"#,
1228 service.id, service.id, service.id, service.id,
1229 )
1230 .unwrap();
1231 #[cfg(feature = "wasmbus")]
1232 w.write(&self.actor_receive_sender_constructors(service.id, service.traits)?);
1233 #[cfg(feature = "wasmbus")]
1234 w.write(&self.provider_receive_sender_constructors(service.id, service.traits)?);
1235
1236 w.write(b"#[async_trait]\nimpl<T:Transport + std::marker::Sync + std::marker::Send> ");
1238 self.write_ident(w, service.id);
1239 w.write(b" for ");
1240 self.write_ident_with_suffix(w, service.id, "Sender")?;
1241 w.write(b"<T> {\n");
1242
1243 for method_id in service.service.operations() {
1244 if let Some(ref ns) = self.namespace {
1246 if method_id.namespace() != ns {
1247 continue;
1248 }
1249 }
1250 let method_ident = method_id.shape_name();
1251
1252 let (op, method_traits) = get_operation(model, method_id, service.id)?;
1253 w.write(b"#[allow(unused)]\n");
1254 let arg_flags = self.write_method_signature(w, method_ident, method_traits, op)?;
1255 let _arg_is_string = matches!(arg_flags, MethodArgFlags::ToString);
1256 w.write(b" {\n");
1257 if let Some(_op_input) = op.input() {
1258 if has_cbor {
1259 if _arg_is_string {
1260 w.write(b"let arg = arg.to_string();\n");
1261 }
1262 writeln!(
1263 w,
1264 "let mut e = {}::cbor::vec_encoder(true);",
1265 &self.import_core
1266 )
1267 .unwrap();
1268 let s = self.encode_shape_id(
1269 _op_input,
1270 if _arg_is_string {
1271 crate::encode_rust::ValExpr::Ref("arg.as_ref()")
1272 } else {
1273 crate::encode_rust::ValExpr::Ref("arg")
1274 },
1275 true,
1276 )?;
1277 w.write(&s);
1278 w.write(b"let buf = e.into_inner(); \n");
1279 } else if matches!(arg_flags, MethodArgFlags::ToString) {
1282 writeln!(
1283 w,
1284 "let buf = {}::common::serialize(&arg.to_string())?;\n",
1285 &self.import_core
1286 )
1287 .unwrap();
1288 } else {
1289 writeln!(
1290 w,
1291 "let buf = {}::common::serialize(arg)?;\n",
1292 &self.import_core
1293 )
1294 .unwrap();
1295 }
1296 } else {
1297 w.write(b"let buf = *b\"\";\n");
1298 }
1299 w.write(b"let resp = self.transport.send(ctx, Message{ method: ");
1300 w.write(b"\"");
1302 w.write(&self.full_dispatch_name(service.id, method_ident));
1303 w.write(b"\", arg: Cow::Borrowed(&buf)}, None).await?;\n");
1305 if let Some(op_output) = op.output() {
1306 let symbol = op_output.shape_name().to_string();
1307 if has_cbor {
1308 let crate_prefix = self.get_crate_path(op_output)?;
1309 write!(
1310 w,
1311 r#"
1312 let value : {} = {}::common::{}(&resp, &{}decode_{})
1313 .map_err(|e| RpcError::Deser(format!("'{{}}': {}", e)))?;
1314 Ok(value)
1315 "#,
1316 self.type_string(Ty::Shape(op_output), Lifetime::L("static"))?,
1317 self.import_core,
1318 if self.has_lifetime(op_output) { "decode_owned" } else { "decode" },
1319 &crate_prefix,
1320 crate::strings::to_snake_case(&symbol),
1321 &symbol,
1322 )
1323 .unwrap();
1324 } else {
1325 write!(
1326 w,
1327 r#"
1328 let value : {} = {}::common::deserialize(&resp)
1329 .map_err(|e| RpcError::Deser(format!("'{{}}': {}", e)))?;
1330 Ok(value)
1331 "#,
1332 self.type_string(Ty::Shape(op_output), Lifetime::Any)?,
1333 self.import_core,
1334 &symbol,
1335 )
1336 .unwrap();
1337 }
1338 } else {
1339 w.write(b"Ok(())");
1340 }
1341 w.write(b" }\n");
1342 }
1343 w.write(b"}\n\n");
1344 Ok(())
1345 }
1346
1347 #[cfg(feature = "wasmbus")]
1349 fn actor_receive_sender_constructors(
1350 &mut self,
1351 service_id: &Identifier,
1352 service_traits: &AppliedTraits,
1353 ) -> Result<String> {
1354 let ctors = if let Some(Wasmbus { actor_receive: true, .. }) =
1355 get_trait(service_traits, crate::model::wasmbus_trait())?
1356 {
1357 format!(
1358 r#"
1359 #[cfg(not(target_arch="wasm32"))]
1360 impl<'send> {}Sender<{}::provider::ProviderTransport<'send>> {{
1361 /// Constructs a Sender using an actor's LinkDefinition,
1362 /// Uses the provider's HostBridge for rpc
1363 pub fn for_actor(ld: &'send {}::core::LinkDefinition) -> Self {{
1364 Self{{ transport: {}::provider::ProviderTransport::new(ld,None) }}
1365 }}
1366 }}
1367 #[cfg(target_arch = "wasm32")]
1368 impl {}Sender<{}::actor::prelude::WasmHost> {{
1369 /// Constructs a client for actor-to-actor messaging
1370 /// using the recipient actor's public key
1371 pub fn to_actor(actor_id: &str) -> Self {{
1372 let transport = {}::actor::prelude::WasmHost::to_actor(actor_id.to_string()).unwrap();
1373 Self{{ transport }}
1374 }}
1375
1376 }}
1377 "#,
1378 service_id,
1380 &self.import_core,
1381 &self.import_core,
1382 &self.import_core,
1383 service_id,
1385 &self.import_core,
1386 &self.import_core,
1388 )
1389 } else {
1390 String::new()
1391 };
1392 Ok(ctors)
1393 }
1394
1395 #[cfg(feature = "wasmbus")]
1398 fn provider_receive_sender_constructors(
1399 &mut self,
1400 service_id: &Identifier,
1401 service_traits: &AppliedTraits,
1402 ) -> Result<String> {
1403 let ctors = if let Some(Wasmbus {
1404 provider_receive: true,
1405 contract_id: Some(contract),
1406 ..
1407 }) = get_trait(service_traits, crate::model::wasmbus_trait())?
1408 {
1409 format!(
1410 r#"
1411 #[cfg(target_arch = "wasm32")]
1412 impl {}Sender<{}::actor::prelude::WasmHost> {{
1413
1414 /// Constructs a client for sending to a {} provider
1415 /// implementing the '{}' capability contract, with the "default" link
1416 pub fn new() -> Self {{
1417 let transport = {}::actor::prelude::WasmHost::to_provider("{}", "default").unwrap();
1418 Self {{ transport }}
1419 }}
1420
1421 /// Constructs a client for sending to a {} provider
1422 /// implementing the '{}' capability contract, with the specified link name
1423 pub fn new_with_link(link_name: &str) -> {}::error::RpcResult<Self> {{
1424 let transport = {}::actor::prelude::WasmHost::to_provider("{}", link_name)?;
1425 Ok(Self {{ transport }})
1426 }}
1427
1428 }}
1429 "#,
1430 service_id,
1432 &self.import_core,
1433 service_id,
1435 contract,
1436 &self.import_core,
1437 contract,
1438 service_id,
1440 contract,
1441 &self.import_core,
1442 &self.import_core,
1443 contract,
1444 )
1445 } else {
1446 String::new()
1447 };
1448 Ok(ctors)
1449 }
1450
1451 pub(crate) fn get_crate_path(&self, id: &ShapeID) -> Result<String> {
1453 let namespace = id.namespace();
1454 if id == &ShapeID::new_unchecked(PRELUDE_NAMESPACE, "Document", None) {
1456 return Ok("wasmbus_rpc::common::".to_string());
1457 }
1458
1459 if namespace == prelude_namespace_id()
1462 || namespace == wasmcloud_model_namespace()
1463 || (self.namespace.is_some()
1466 && namespace == self.namespace.as_ref().unwrap())
1467 {
1468 return Ok(String::new());
1469 }
1470
1471 match self.packages.get(&namespace.to_string()) {
1474 Some(crate::model::PackageName { crate_name: Some(crate_name), .. }) => {
1475 Ok(format!("{crate_name}::"))
1476 }
1477 _ => Err(Error::Model(format!(
1478 "undefined crate for namespace '{}' symbol '{}'. Make sure codegen.toml includes \
1479 all dependent namespaces, and that the dependent .smithy file contains package \
1480 metadata with crate: value",
1481 namespace,
1482 id.shape_name(),
1483 ))),
1484 }
1485 }
1486
1487 pub(crate) fn get_model_crate(&self) -> String {
1489 if self.namespace.is_none()
1490 || self.namespace.as_ref().unwrap() != wasmcloud_model_namespace()
1491 {
1492 format!("{}::model::", &self.import_core)
1493 } else {
1494 String::new()
1495 }
1496 }
1497
1498 fn build_preface(&self, id: &ShapeID, traits: &AppliedTraits) -> StructPreface {
1499 let mut preface = StructPreface::default();
1500 if self.has_lifetime(id) {
1501 preface.deserialize = false;
1502 }
1503 let is_trait_struct = traits.contains_key(&prelude_shape_named(TRAIT_TRAIT).unwrap());
1505 if is_trait_struct {
1506 preface.default = false
1507 }
1508
1509 if let Ok(Some(cg)) = get_trait::<CodegenRust>(traits, codegen_rust_trait()) {
1510 preface.default = !cg.no_derive_default;
1511 preface.eq = !cg.no_derive_eq;
1512 preface.non_exhaustive = cg.non_exhaustive;
1513 }
1514 preface
1515 }
1516} struct StructPreface {
1520 clone: bool,
1521 copy: bool,
1522 debug: bool,
1523 default: bool,
1524 eq: bool,
1525 partial_eq: bool,
1526 serialize: bool,
1527 deserialize: bool,
1528 non_exhaustive: bool,
1529}
1530
1531impl Default for StructPreface {
1532 fn default() -> Self {
1533 StructPreface {
1534 clone: true,
1535 copy: false,
1536 debug: true,
1537 default: true,
1538 eq: true,
1539 partial_eq: true,
1540 serialize: true,
1541 deserialize: true,
1542 non_exhaustive: false,
1543 }
1544 }
1545}
1546impl StructPreface {
1547 fn derives(&self) -> String {
1549 if self.clone
1550 || self.copy
1551 || self.debug
1552 || self.default
1553 || self.eq
1554 || self.partial_eq
1555 || self.serialize
1556 || self.deserialize
1557 {
1558 let mut s = "#[derive(".to_string();
1559 if self.clone {
1560 s.push_str("Clone,");
1561 }
1562 if self.copy {
1563 s.push_str("Copy,");
1564 }
1565 if self.debug {
1566 s.push_str("Debug,");
1567 }
1568 if self.default {
1569 s.push_str("Default,");
1570 }
1571 if self.deserialize {
1572 s.push_str("Deserialize,");
1573 }
1574 if self.eq {
1575 s.push_str("Eq,");
1576 }
1577 if self.partial_eq {
1578 s.push_str("PartialEq,");
1579 }
1580 if self.serialize {
1581 s.push_str("Serialize,");
1582 }
1583 s.push_str(")]\n");
1584 s
1585 } else {
1586 String::new()
1587 }
1588 }
1589}
1590
1591pub(crate) fn is_optional_type(field: &MemberShape) -> bool {
1595 field.is_boxed()
1596 || (!field.is_required()
1597 && ![
1598 "Boolean", "Byte", "Short", "Integer", "Long", "Float", "Double",
1599 ]
1600 .contains(&field.target().shape_name().to_string().as_str()))
1601}
1602
1603#[test]
1617fn package_semver() {
1618 let package_version = env!("CARGO_PKG_VERSION");
1619 let version = semver::Version::parse(package_version);
1620 assert!(
1621 version.is_ok(),
1622 "package version {package_version} has unexpected format"
1623 );
1624}
1625
1626pub struct RustSourceFormatter {
1628 program: String,
1631 args: Vec<String>,
1633}
1634
1635impl Default for RustSourceFormatter {
1636 fn default() -> Self {
1637 RustSourceFormatter {
1638 program: "rustfmt".into(),
1639 args: vec!["--edition".into(), "2021".into()],
1640 }
1641 }
1642}
1643
1644impl SourceFormatter for RustSourceFormatter {
1645 fn run(&self, source_files: &[&str]) -> Result<()> {
1646 let mut args: Vec<&str> = self.args.iter().map(|s| s.as_str()).collect();
1647 args.extend(source_files.iter());
1648 format::run_command(&self.program, &args)?;
1649 Ok(())
1650 }
1651}