1use proc_macro2::TokenStream;
38
39pub struct CodeLiteralEmitter;
41
42impl CodeLiteralEmitter {
43 pub fn type_tokens(type_name: &str) -> TokenStream {
45 type_name.parse().expect("valid type tokens")
46 }
47
48 pub fn enum_unit_variant_literal(type_name: &str, variant_name: &str) -> TokenStream {
50 format!("{type_name}::{variant_name}")
51 .parse()
52 .expect("valid unit variant literal")
53 }
54}
55
56pub struct EmitEntry {
79 pub tool: &'static str,
81 pub crate_name: &'static str,
83 pub constructor: fn(serde_json::Value) -> Result<Box<dyn EmitCode>, String>,
85}
86
87inventory::collect!(EmitEntry);
88
89pub fn dispatch_emit(tool: &str, params: serde_json::Value) -> Result<Box<dyn EmitCode>, String> {
97 inventory::iter::<EmitEntry>()
98 .find(|e| e.tool == tool)
99 .ok_or_else(|| format!("unknown emit tool: '{tool}'"))
100 .and_then(|e| (e.constructor)(params))
101}
102
103pub fn dispatch_emit_from(
108 tool: &str,
109 crate_name: &str,
110 params: serde_json::Value,
111) -> Result<Box<dyn EmitCode>, String> {
112 inventory::iter::<EmitEntry>()
113 .find(|e| e.tool == tool && e.crate_name == crate_name)
114 .ok_or_else(|| format!("unknown emit tool: '{crate_name}::{tool}'"))
115 .and_then(|e| (e.constructor)(params))
116}
117
118#[macro_export]
135macro_rules! register_emit {
136 ($tool:literal, $T:ty) => {
137 const _: () = {
138 fn __emit_constructor(
139 v: elicitation::serde_json::Value,
140 ) -> ::std::result::Result<
141 ::std::boxed::Box<dyn elicitation::emit_code::EmitCode>,
142 ::std::string::String,
143 > {
144 elicitation::serde_json::from_value::<$T>(v)
145 .map(|p| {
146 ::std::boxed::Box::new(p)
147 as ::std::boxed::Box<dyn elicitation::emit_code::EmitCode>
148 })
149 .map_err(|e| e.to_string())
150 }
151 elicitation::inventory::submit! {
152 elicitation::emit_code::EmitEntry {
153 tool: $tool,
154 crate_name: env!("CARGO_PKG_NAME"),
155 constructor: __emit_constructor,
156 }
157 }
158 };
159 };
160}
161
162pub trait ToCodeLiteral {
169 fn to_code_literal(&self) -> TokenStream;
172
173 fn type_tokens() -> TokenStream
179 where
180 Self: Sized,
181 {
182 quote::quote! { _ }
183 }
184}
185
186pub trait CustomEmit<P> {
205 fn emit_code(params: &P) -> TokenStream;
207}
208
209pub trait EmitCode {
222 fn emit_code(&self) -> TokenStream;
228
229 fn crate_deps(&self) -> Vec<CrateDep> {
235 vec![]
236 }
237
238 fn shared_scope(&self) -> bool {
249 false
250 }
251}
252
253#[derive(Debug, Clone)]
271pub struct RawFragment(pub String);
272
273impl EmitCode for RawFragment {
274 fn emit_code(&self) -> TokenStream {
275 self.0
276 .parse()
277 .unwrap_or_else(|_| quote::quote!())
278 }
279
280 fn crate_deps(&self) -> Vec<CrateDep> {
281 vec![]
282 }
283}
284
285pub fn parse_expr_tokens<S>(src: S, context: &str) -> TokenStream
291where
292 S: AsRef<str>,
293{
294 let src = src.as_ref();
295 let expr = syn::parse_str::<syn::Expr>(src)
296 .unwrap_or_else(|error| panic!("invalid {context} expression `{src}`: {error}"));
297 quote::quote! { #expr }
298}
299
300#[derive(Debug, Clone, PartialEq, Eq, Hash)]
308pub struct CrateDep {
309 pub name: &'static str,
311 pub version: &'static str,
313 pub features: &'static [&'static str],
315}
316
317impl CrateDep {
318 pub const fn new(name: &'static str, version: &'static str) -> Self {
320 Self {
321 name,
322 version,
323 features: &[],
324 }
325 }
326
327 pub const fn with_features(
329 name: &'static str,
330 version: &'static str,
331 features: &'static [&'static str],
332 ) -> Self {
333 Self {
334 name,
335 version,
336 features,
337 }
338 }
339
340 pub fn to_toml_line(&self) -> String {
348 if self.features.is_empty() {
349 format!(r#"{} = "{}""#, self.name, self.version)
350 } else {
351 let feats = self
352 .features
353 .iter()
354 .map(|f| format!(r#""{}""#, f))
355 .collect::<Vec<_>>()
356 .join(", ");
357 format!(
358 r#"{} = {{ version = "{}", features = [{}] }}"#,
359 self.name, self.version, feats
360 )
361 }
362 }
363}
364
365macro_rules! impl_emit_totokens {
372 ($($T:ty),+ $(,)?) => {
373 $(
374 impl EmitCode for $T {
375 fn emit_code(&self) -> TokenStream {
376 let mut ts = TokenStream::new();
377 quote::ToTokens::to_tokens(self, &mut ts);
378 ts
379 }
380 }
381 )+
382 };
383}
384
385impl_emit_totokens!(
386 bool, i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, usize, isize, f32, f64, char, String,
387);
388
389pub struct BinaryScaffold {
405 steps: Vec<Box<dyn EmitCode>>,
406 with_tracing: bool,
407 workspace_root: Option<std::path::PathBuf>,
413}
414
415impl BinaryScaffold {
416 pub fn new(steps: Vec<Box<dyn EmitCode>>, with_tracing: bool) -> Self {
423 Self {
424 steps,
425 with_tracing,
426 workspace_root: None,
427 }
428 }
429
430 pub fn with_workspace_root(mut self, root: impl Into<std::path::PathBuf>) -> Self {
438 self.workspace_root = Some(root.into());
439 self
440 }
441
442 fn resolved_workspace_root(&self) -> Option<std::path::PathBuf> {
444 self.workspace_root
445 .clone()
446 .or_else(|| std::env::var("ELICIT_WORKSPACE_ROOT").ok().map(Into::into))
447 }
448
449 pub fn all_deps(&self) -> Vec<CrateDep> {
454 let mut seen = std::collections::HashSet::new();
455 let mut deps = Vec::new();
456
457 let scaffold_deps = [
459 CrateDep::with_features("tokio", "1", &["full"]),
460 CrateDep::new("tracing-subscriber", "0.3"),
461 CrateDep::new("tracing", "0.1"),
462 ];
463 for dep in scaffold_deps {
464 if seen.insert(dep.name) {
465 deps.push(dep);
466 }
467 }
468
469 for step in &self.steps {
470 for dep in step.crate_deps() {
471 if seen.insert(dep.name) {
472 deps.push(dep);
473 }
474 }
475 }
476 deps
477 }
478
479 pub fn render(&self) -> TokenStream {
481 let step_tokens: Vec<TokenStream> = self
482 .steps
483 .iter()
484 .map(|s| {
485 let code = s.emit_code();
486 if s.shared_scope() {
487 quote::quote! { #code ; }
491 } else {
492 quote::quote! { { #code } }
495 }
496 })
497 .collect();
498
499 let tracing_init = if self.with_tracing {
500 quote::quote! { tracing_subscriber::fmt::init(); }
501 } else {
502 TokenStream::new()
503 };
504
505 let mut use_stmts: Vec<TokenStream> = self
511 .all_deps()
512 .into_iter()
513 .filter(|d| d.name.starts_with("elicit"))
514 .map(|d| {
515 let krate: TokenStream = d.name.parse().expect("valid ident");
516 if d.name == "elicitation" {
517 quote::quote! { use #krate::contracts::*; }
518 } else {
519 quote::quote! { use #krate::*; }
520 }
521 })
522 .collect();
523
524 if self.all_deps().iter().any(|d| d.name == "reqwest") {
529 use_stmts.push(quote::quote! { use reqwest::header::HeaderMap; });
530 }
531
532 if self.all_deps().iter().any(|d| d.name == "elicit_reqwest") {
534 use_stmts.push(quote::quote! { use std::collections::HashMap; });
535 }
536
537 quote::quote! {
538 #( #use_stmts )*
539 #[tokio::main]
540 async fn main() -> Result<(), Box<dyn std::error::Error>> {
541 #tracing_init
542 #( #step_tokens )*
543 Ok(())
544 }
545 }
546 }
547
548 pub fn to_source(&self) -> Result<String, syn::Error> {
553 let tokens = self.render();
554 let file: syn::File = syn::parse2(tokens)?;
555 Ok(prettyplease::unparse(&file))
556 }
557
558 pub fn to_cargo_toml(&self, package_name: &str) -> String {
565 let ws_root = self.resolved_workspace_root();
566 let deps = self.all_deps();
567 let dep_lines: String = deps
568 .iter()
569 .map(|d| {
570 let line = if let Some(ref root) = ws_root {
571 if d.name == "elicitation"
572 || d.name.starts_with("elicit_")
573 || d.name.starts_with("elicitation_")
574 {
575 let path = root.join("crates").join(d.name);
576 let path_str = path.to_string_lossy().replace('\\', "/");
579 format!(r#"{} = {{ path = "{}" }}"#, d.name, path_str)
580 } else {
581 d.to_toml_line()
582 }
583 } else {
584 d.to_toml_line()
585 };
586 format!("{line}\n")
587 })
588 .collect();
589
590 format!(
591 r#"[package]
592name = "{}"
593version = "0.1.0"
594edition = "2021"
595
596# Prevent cargo from treating this as a member of any parent workspace.
597[workspace]
598
599[dependencies]
600{}
601"#,
602 package_name, dep_lines
603 )
604 }
605
606 pub fn emit_to_disk(
611 &self,
612 output_dir: &std::path::Path,
613 package_name: &str,
614 ) -> Result<std::path::PathBuf, EmitError> {
615 let src_dir = output_dir.join("src");
616 std::fs::create_dir_all(&src_dir)?;
617
618 let source = self.to_source().map_err(EmitError::Syntax)?;
619 let main_rs = src_dir.join("main.rs");
620 std::fs::write(&main_rs, &source)?;
621
622 let cargo_toml = output_dir.join("Cargo.toml");
623 std::fs::write(&cargo_toml, self.to_cargo_toml(package_name))?;
624
625 Ok(main_rs)
626 }
627}
628
629pub fn compile(project_dir: &std::path::Path) -> Result<std::path::PathBuf, CompileError> {
636 let output = std::process::Command::new("cargo")
637 .args(["build", "--release"])
638 .current_dir(project_dir)
639 .output()
640 .map_err(|e| CompileError::Io(e.to_string()))?;
641
642 if output.status.success() {
643 let binary = project_dir.join("target/release").join(
645 project_dir
646 .file_name()
647 .unwrap_or(std::ffi::OsStr::new("generated_workflow")),
648 );
649 Ok(binary)
650 } else {
651 Err(CompileError::CargoFailed(
652 String::from_utf8_lossy(&output.stderr).into_owned(),
653 ))
654 }
655}
656
657#[derive(Debug, derive_more::Display, derive_more::Error)]
661pub enum EmitError {
662 #[display("Syntax error in emitted code: {}", _0)]
664 Syntax(#[error(not(source))] syn::Error),
665 #[display("IO error: {}", _0)]
667 Io(#[error(not(source))] std::io::Error),
668}
669
670impl From<std::io::Error> for EmitError {
671 fn from(e: std::io::Error) -> Self {
672 EmitError::Io(e)
673 }
674}
675
676#[derive(Debug, derive_more::Display, derive_more::Error)]
678pub enum CompileError {
679 #[display("Compilation failed:\n{}", _0)]
681 CargoFailed(#[error(not(source))] String),
682 #[display("Could not launch cargo: {}", _0)]
684 Io(#[error(not(source))] String),
685}
686
687impl<T: EmitCode> EmitCode for Vec<T> {
691 fn emit_code(&self) -> TokenStream {
692 let elems: Vec<TokenStream> = self.iter().map(|e| e.emit_code()).collect();
693 quote::quote! { vec![ #( #elems ),* ] }
694 }
695}
696
697impl<T: EmitCode> EmitCode for Option<T> {
699 fn emit_code(&self) -> TokenStream {
700 match self {
701 Some(inner) => {
702 let inner_ts = inner.emit_code();
703 quote::quote! { Some(#inner_ts) }
704 }
705 None => quote::quote! { None },
706 }
707 }
708}
709
710impl EmitCode for std::path::PathBuf {
712 fn emit_code(&self) -> TokenStream {
713 let s = self.to_string_lossy();
714 let s = s.as_ref();
715 quote::quote! { std::path::PathBuf::from(#s) }
716 }
717}
718
719impl EmitCode for std::time::Duration {
721 fn emit_code(&self) -> TokenStream {
722 let nanos = self.as_nanos() as u64;
723 quote::quote! { std::time::Duration::from_nanos(#nanos) }
724 }
725}
726
727macro_rules! impl_emit_tuple {
729 ( $( $T:ident ),+ ; $( $idx:tt ),+ ) => {
730 impl< $( $T: EmitCode ),+ > EmitCode for ( $( $T, )+ ) {
731 fn emit_code(&self) -> TokenStream {
732 paste::paste! {
733 $( let [<$T:lower _val>] = self.$idx.emit_code(); )+
734 quote::quote! { ( $( #[<$T:lower _val>] ),+ ) }
735 }
736 }
737 }
738 };
739}
740
741impl_emit_tuple!(A, B; 0, 1);
742impl_emit_tuple!(A, B, C; 0, 1, 2);
743impl_emit_tuple!(A, B, C, D; 0, 1, 2, 3);
744
745#[cfg(feature = "serde_json")]
749impl EmitCode for serde_json::Value {
750 fn emit_code(&self) -> TokenStream {
751 let s = self.to_string();
752 quote::quote! {
753 serde_json::from_str(#s).expect("valid json literal")
754 }
755 }
756}
757
758#[cfg(feature = "url")]
760impl EmitCode for url::Url {
761 fn emit_code(&self) -> TokenStream {
762 let s = self.as_str();
763 quote::quote! { url::Url::parse(#s).expect("valid URL") }
764 }
765}
766
767#[cfg(feature = "uuid")]
769impl EmitCode for uuid::Uuid {
770 fn emit_code(&self) -> TokenStream {
771 let s = self.to_string();
772 quote::quote! { uuid::Uuid::parse_str(#s).expect("valid UUID") }
773 }
774}
775
776impl EmitCode for std::net::IpAddr {
778 fn emit_code(&self) -> TokenStream {
779 let s = self.to_string();
780 quote::quote! { #s.parse::<std::net::IpAddr>().expect("valid IP") }
781 }
782}
783
784impl EmitCode for std::net::Ipv4Addr {
786 fn emit_code(&self) -> TokenStream {
787 let s = self.to_string();
788 quote::quote! { #s.parse::<std::net::Ipv4Addr>().expect("valid IPv4") }
789 }
790}
791
792impl EmitCode for std::net::Ipv6Addr {
794 fn emit_code(&self) -> TokenStream {
795 let s = self.to_string();
796 quote::quote! { #s.parse::<std::net::Ipv6Addr>().expect("valid IPv6") }
797 }
798}
799
800#[cfg(feature = "chrono")]
802impl EmitCode for chrono::DateTime<chrono::Utc> {
803 fn emit_code(&self) -> TokenStream {
804 let s = self.to_rfc3339();
805 quote::quote! {
806 chrono::DateTime::parse_from_rfc3339(#s)
807 .expect("valid RFC3339 datetime")
808 .with_timezone(&chrono::Utc)
809 }
810 }
811}
812
813#[cfg(feature = "chrono")]
815impl EmitCode for chrono::NaiveDateTime {
816 fn emit_code(&self) -> TokenStream {
817 let s = self.format("%Y-%m-%dT%H:%M:%S%.f").to_string();
818 quote::quote! {
819 chrono::NaiveDateTime::parse_from_str(#s, "%Y-%m-%dT%H:%M:%S%.f")
820 .expect("valid NaiveDateTime")
821 }
822 }
823}
824
825#[cfg(feature = "time")]
827impl EmitCode for time::OffsetDateTime {
828 fn emit_code(&self) -> TokenStream {
829 let s = self
830 .format(&time::format_description::well_known::Rfc3339)
831 .unwrap_or_default();
832 quote::quote! {
833 time::OffsetDateTime::parse(#s, &time::format_description::well_known::Rfc3339)
834 .expect("valid OffsetDateTime")
835 }
836 }
837}
838
839#[cfg(feature = "time")]
841impl EmitCode for time::PrimitiveDateTime {
842 fn emit_code(&self) -> TokenStream {
843 const PRIM_FMT: time::format_description::well_known::Iso8601<
846 {
847 time::format_description::well_known::iso8601::Config::DEFAULT
848 .set_formatted_components(
849 time::format_description::well_known::iso8601::FormattedComponents::DateTime,
850 )
851 .encode()
852 },
853 > = time::format_description::well_known::Iso8601;
854 let s = self.format(&PRIM_FMT).unwrap_or_default();
855 quote::quote! {
856 {
857 const PRIM_FMT: time::format_description::well_known::Iso8601<{
858 time::format_description::well_known::iso8601::Config::DEFAULT
859 .set_formatted_components(
860 time::format_description::well_known::iso8601::FormattedComponents::DateTime,
861 )
862 .encode()
863 }> = time::format_description::well_known::Iso8601;
864 time::PrimitiveDateTime::parse(#s, &PRIM_FMT).expect("valid PrimitiveDateTime")
865 }
866 }
867 }
868}
869
870#[cfg(feature = "time")]
872impl EmitCode for time::Time {
873 fn emit_code(&self) -> TokenStream {
874 let h = self.hour();
875 let m = self.minute();
876 let s = self.second();
877 let ns = self.nanosecond();
878 quote::quote! {
879 time::Time::from_hms_nano(#h, #m, #s, #ns).expect("valid Time")
880 }
881 }
882}
883
884#[cfg(feature = "jiff")]
886impl EmitCode for jiff::Timestamp {
887 fn emit_code(&self) -> TokenStream {
888 let s = self.to_string();
889 quote::quote! {
890 #s.parse::<jiff::Timestamp>().expect("valid Timestamp")
891 }
892 }
893}
894
895#[cfg(feature = "jiff")]
897impl EmitCode for jiff::Zoned {
898 fn emit_code(&self) -> TokenStream {
899 let s = self.to_string();
900 quote::quote! {
901 #s.parse::<jiff::Zoned>().expect("valid Zoned")
902 }
903 }
904}
905
906#[cfg(feature = "jiff")]
908impl EmitCode for jiff::civil::DateTime {
909 fn emit_code(&self) -> TokenStream {
910 let s = self.to_string();
911 quote::quote! {
912 #s.parse::<jiff::civil::DateTime>().expect("valid civil DateTime")
913 }
914 }
915}
916
917#[cfg(feature = "reqwest")]
919impl EmitCode for reqwest::StatusCode {
920 fn emit_code(&self) -> TokenStream {
921 let n = self.as_u16();
922 quote::quote! {
923 reqwest::StatusCode::from_u16(#n).expect("valid status code")
924 }
925 }
926}
927
928macro_rules! impl_to_code_literal_totokens {
931 ($($T:ty),+ $(,)?) => {
932 $(
933 impl ToCodeLiteral for $T {
934 fn to_code_literal(&self) -> TokenStream {
935 let mut ts = TokenStream::new();
936 quote::ToTokens::to_tokens(self, &mut ts);
937 ts
938 }
939 fn type_tokens() -> TokenStream {
940 quote::quote! { $T }
941 }
942 }
943 )+
944 };
945}
946
947impl_to_code_literal_totokens!(
948 bool, i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, usize, isize, f32, f64, char,
949);
950
951impl ToCodeLiteral for String {
952 fn to_code_literal(&self) -> TokenStream {
953 let s = self.as_str();
954 quote::quote! { #s.to_string() }
955 }
956 fn type_tokens() -> TokenStream {
957 quote::quote! { String }
958 }
959}
960
961impl<T: ToCodeLiteral> ToCodeLiteral for Option<T> {
962 fn to_code_literal(&self) -> TokenStream {
963 match self {
964 Some(v) => {
965 let inner = v.to_code_literal();
966 quote::quote! { ::std::option::Option::Some(#inner) }
967 }
968 None => {
969 let t = <T as ToCodeLiteral>::type_tokens();
970 quote::quote! { None::<#t> }
971 }
972 }
973 }
974}
975
976impl<T: ToCodeLiteral> ToCodeLiteral for Vec<T> {
977 fn type_tokens() -> TokenStream {
978 let t = <T as ToCodeLiteral>::type_tokens();
979 quote::quote! { ::std::vec::Vec<#t> }
980 }
981
982 fn to_code_literal(&self) -> TokenStream {
983 let elems: Vec<_> = self.iter().map(|v| v.to_code_literal()).collect();
984 quote::quote! { ::std::vec![#(#elems),*] }
985 }
986}
987
988impl<T: ToCodeLiteral, const N: usize> ToCodeLiteral for [T; N] {
989 fn type_tokens() -> TokenStream {
990 let t = <T as ToCodeLiteral>::type_tokens();
991 let n = proc_macro2::Literal::usize_suffixed(N);
992 quote::quote! { [#t; #n] }
993 }
994
995 fn to_code_literal(&self) -> TokenStream {
996 let elements: Vec<_> = self.iter().map(|e| e.to_code_literal()).collect();
997 quote::quote! { [#(#elements),*] }
998 }
999}
1000
1001impl<V: ToCodeLiteral> ToCodeLiteral for std::collections::HashMap<String, V> {
1002 fn type_tokens() -> TokenStream {
1003 let v = <V as ToCodeLiteral>::type_tokens();
1004 quote::quote! { ::std::collections::HashMap<::std::string::String, #v> }
1005 }
1006
1007 fn to_code_literal(&self) -> TokenStream {
1008 let entries: Vec<_> = self
1009 .iter()
1010 .map(|(k, v)| {
1011 let v_ts = v.to_code_literal();
1012 quote::quote! { (#k.to_string(), #v_ts) }
1013 })
1014 .collect();
1015 quote::quote! {
1016 [#(#entries),*].into_iter().collect::<::std::collections::HashMap<_, _>>()
1017 }
1018 }
1019}
1020
1021impl<V: ToCodeLiteral> ToCodeLiteral for std::collections::BTreeMap<String, V> {
1022 fn type_tokens() -> TokenStream {
1023 let v = <V as ToCodeLiteral>::type_tokens();
1024 quote::quote! { ::std::collections::BTreeMap<::std::string::String, #v> }
1025 }
1026
1027 fn to_code_literal(&self) -> TokenStream {
1028 let entries: Vec<_> = self
1029 .iter()
1030 .map(|(k, v)| {
1031 let v_ts = v.to_code_literal();
1032 quote::quote! { (#k.to_string(), #v_ts) }
1033 })
1034 .collect();
1035 quote::quote! {
1036 [#(#entries),*].into_iter().collect::<::std::collections::BTreeMap<_, _>>()
1037 }
1038 }
1039}
1040
1041impl<T: ToCodeLiteral> ToCodeLiteral for Box<T> {
1042 fn type_tokens() -> TokenStream {
1043 let inner = <T as ToCodeLiteral>::type_tokens();
1044 quote::quote! { ::std::boxed::Box<#inner> }
1045 }
1046
1047 fn to_code_literal(&self) -> TokenStream {
1048 let inner = (**self).to_code_literal();
1049 quote::quote! { ::std::boxed::Box::new(#inner) }
1050 }
1051}
1052
1053impl<A: ToCodeLiteral, B: ToCodeLiteral> ToCodeLiteral for (A, B) {
1054 fn type_tokens() -> TokenStream {
1055 let a = <A as ToCodeLiteral>::type_tokens();
1056 let b = <B as ToCodeLiteral>::type_tokens();
1057 quote::quote! { (#a, #b) }
1058 }
1059
1060 fn to_code_literal(&self) -> TokenStream {
1061 let a = self.0.to_code_literal();
1062 let b = self.1.to_code_literal();
1063 quote::quote! { (#a, #b) }
1064 }
1065}
1066
1067impl<A: ToCodeLiteral, B: ToCodeLiteral, C: ToCodeLiteral> ToCodeLiteral for (A, B, C) {
1068 fn type_tokens() -> TokenStream {
1069 let a = <A as ToCodeLiteral>::type_tokens();
1070 let b = <B as ToCodeLiteral>::type_tokens();
1071 let c = <C as ToCodeLiteral>::type_tokens();
1072 quote::quote! { (#a, #b, #c) }
1073 }
1074
1075 fn to_code_literal(&self) -> TokenStream {
1076 let a = self.0.to_code_literal();
1077 let b = self.1.to_code_literal();
1078 let c = self.2.to_code_literal();
1079 quote::quote! { (#a, #b, #c) }
1080 }
1081}
1082
1083impl<A: ToCodeLiteral, B: ToCodeLiteral, C: ToCodeLiteral, D: ToCodeLiteral> ToCodeLiteral
1084 for (A, B, C, D)
1085{
1086 fn type_tokens() -> TokenStream {
1087 let a = <A as ToCodeLiteral>::type_tokens();
1088 let b = <B as ToCodeLiteral>::type_tokens();
1089 let c = <C as ToCodeLiteral>::type_tokens();
1090 let d = <D as ToCodeLiteral>::type_tokens();
1091 quote::quote! { (#a, #b, #c, #d) }
1092 }
1093
1094 fn to_code_literal(&self) -> TokenStream {
1095 let a = self.0.to_code_literal();
1096 let b = self.1.to_code_literal();
1097 let c = self.2.to_code_literal();
1098 let d = self.3.to_code_literal();
1099 quote::quote! { (#a, #b, #c, #d) }
1100 }
1101}
1102
1103impl ToCodeLiteral for std::net::IpAddr {
1106 fn to_code_literal(&self) -> TokenStream {
1107 EmitCode::emit_code(self)
1108 }
1109}
1110
1111impl ToCodeLiteral for std::net::Ipv4Addr {
1112 fn to_code_literal(&self) -> TokenStream {
1113 EmitCode::emit_code(self)
1114 }
1115}
1116
1117impl ToCodeLiteral for std::net::Ipv6Addr {
1118 fn to_code_literal(&self) -> TokenStream {
1119 EmitCode::emit_code(self)
1120 }
1121}
1122
1123impl ToCodeLiteral for std::path::PathBuf {
1124 fn to_code_literal(&self) -> TokenStream {
1125 EmitCode::emit_code(self)
1126 }
1127}
1128
1129impl ToCodeLiteral for std::time::Duration {
1130 fn to_code_literal(&self) -> TokenStream {
1131 EmitCode::emit_code(self)
1132 }
1133}
1134
1135#[cfg(feature = "serde_json")]
1136impl ToCodeLiteral for serde_json::Value {
1137 fn to_code_literal(&self) -> TokenStream {
1138 EmitCode::emit_code(self)
1139 }
1140}
1141
1142#[cfg(feature = "url")]
1143impl ToCodeLiteral for url::Url {
1144 fn to_code_literal(&self) -> TokenStream {
1145 EmitCode::emit_code(self)
1146 }
1147}
1148
1149#[cfg(feature = "url")]
1150impl ToCodeLiteral for url::SyntaxViolation {
1151 fn to_code_literal(&self) -> TokenStream {
1152 use url::SyntaxViolation::*;
1153 match self {
1154 Backslash => quote::quote! { url::SyntaxViolation::Backslash },
1155 C0SpaceIgnored => quote::quote! { url::SyntaxViolation::C0SpaceIgnored },
1156 EmbeddedCredentials => quote::quote! { url::SyntaxViolation::EmbeddedCredentials },
1157 ExpectedDoubleSlash => quote::quote! { url::SyntaxViolation::ExpectedDoubleSlash },
1158 ExpectedFileDoubleSlash => {
1159 quote::quote! { url::SyntaxViolation::ExpectedFileDoubleSlash }
1160 }
1161 FileWithHostAndWindowsDrive => {
1162 quote::quote! { url::SyntaxViolation::FileWithHostAndWindowsDrive }
1163 }
1164 NonUrlCodePoint => quote::quote! { url::SyntaxViolation::NonUrlCodePoint },
1165 NullInFragment => quote::quote! { url::SyntaxViolation::NullInFragment },
1166 PercentDecode => quote::quote! { url::SyntaxViolation::PercentDecode },
1167 TabOrNewlineIgnored => quote::quote! { url::SyntaxViolation::TabOrNewlineIgnored },
1168 UnencodedAtSign => quote::quote! { url::SyntaxViolation::UnencodedAtSign },
1169 _ => unreachable!("unknown SyntaxViolation variant"),
1170 }
1171 }
1172}
1173
1174#[cfg(feature = "uuid")]
1175impl ToCodeLiteral for uuid::Uuid {
1176 fn to_code_literal(&self) -> TokenStream {
1177 EmitCode::emit_code(self)
1178 }
1179}
1180
1181#[cfg(feature = "chrono")]
1182impl ToCodeLiteral for chrono::DateTime<chrono::Utc> {
1183 fn to_code_literal(&self) -> TokenStream {
1184 EmitCode::emit_code(self)
1185 }
1186}
1187
1188#[cfg(feature = "chrono")]
1189impl ToCodeLiteral for chrono::NaiveDateTime {
1190 fn to_code_literal(&self) -> TokenStream {
1191 EmitCode::emit_code(self)
1192 }
1193}
1194
1195#[cfg(feature = "chrono")]
1196impl ToCodeLiteral for chrono::DateTime<chrono::FixedOffset> {
1197 fn to_code_literal(&self) -> TokenStream {
1198 let s = self.to_rfc3339();
1199 quote::quote! {
1200 chrono::DateTime::parse_from_rfc3339(#s)
1201 .expect("valid RFC3339 datetime")
1202 }
1203 }
1204
1205 fn type_tokens() -> TokenStream {
1206 quote::quote! { chrono::DateTime<chrono::FixedOffset> }
1207 }
1208}
1209
1210#[cfg(feature = "chrono")]
1211impl ToCodeLiteral for chrono::TimeDelta {
1212 fn to_code_literal(&self) -> TokenStream {
1213 let secs = self.num_seconds();
1214 let sub_nanos = self.subsec_nanos();
1215 if sub_nanos == 0 {
1216 quote::quote! {
1217 chrono::TimeDelta::try_seconds(#secs).expect("valid seconds")
1218 }
1219 } else {
1220 quote::quote! {
1222 chrono::TimeDelta::try_seconds(#secs).expect("valid seconds")
1223 + chrono::TimeDelta::nanoseconds(#sub_nanos as i64)
1224 }
1225 }
1226 }
1227
1228 fn type_tokens() -> TokenStream {
1229 quote::quote! { chrono::TimeDelta }
1230 }
1231}
1232
1233#[cfg(feature = "time")]
1234impl ToCodeLiteral for time::OffsetDateTime {
1235 fn to_code_literal(&self) -> TokenStream {
1236 EmitCode::emit_code(self)
1237 }
1238}
1239
1240#[cfg(feature = "time")]
1241impl ToCodeLiteral for time::PrimitiveDateTime {
1242 fn to_code_literal(&self) -> TokenStream {
1243 EmitCode::emit_code(self)
1244 }
1245}
1246
1247#[cfg(feature = "time")]
1248impl ToCodeLiteral for time::Time {
1249 fn to_code_literal(&self) -> TokenStream {
1250 EmitCode::emit_code(self)
1251 }
1252}
1253
1254#[cfg(feature = "jiff")]
1255impl ToCodeLiteral for jiff::Timestamp {
1256 fn to_code_literal(&self) -> TokenStream {
1257 EmitCode::emit_code(self)
1258 }
1259}
1260
1261#[cfg(feature = "jiff")]
1262impl ToCodeLiteral for jiff::Zoned {
1263 fn to_code_literal(&self) -> TokenStream {
1264 EmitCode::emit_code(self)
1265 }
1266}
1267
1268#[cfg(feature = "jiff")]
1269impl ToCodeLiteral for jiff::civil::DateTime {
1270 fn to_code_literal(&self) -> TokenStream {
1271 EmitCode::emit_code(self)
1272 }
1273}
1274
1275#[cfg(feature = "reqwest")]
1276impl ToCodeLiteral for reqwest::StatusCode {
1277 fn to_code_literal(&self) -> TokenStream {
1278 EmitCode::emit_code(self)
1279 }
1280}
1281
1282macro_rules! impl_atomic_to_code_literal {
1287 ($($atomic:ident => $prim:ty),+ $(,)?) => {
1288 $(
1289 impl ToCodeLiteral for ::std::sync::atomic::$atomic {
1290 fn to_code_literal(&self) -> TokenStream {
1291 use ::std::sync::atomic::Ordering;
1292 let val = self.load(Ordering::SeqCst);
1293 let val_lit = <$prim as ToCodeLiteral>::to_code_literal(&val);
1294 let ty: TokenStream =
1295 concat!("::std::sync::atomic::", stringify!($atomic))
1296 .parse()
1297 .expect("valid atomic type path");
1298 quote::quote! { #ty::new(#val_lit) }
1299 }
1300
1301 fn type_tokens() -> TokenStream {
1302 concat!("::std::sync::atomic::", stringify!($atomic))
1303 .parse()
1304 .expect("valid atomic type path")
1305 }
1306 }
1307 )+
1308 };
1309}
1310
1311impl_atomic_to_code_literal!(
1312 AtomicBool => bool,
1313 AtomicI8 => i8,
1314 AtomicI16 => i16,
1315 AtomicI32 => i32,
1316 AtomicI64 => i64,
1317 AtomicIsize => isize,
1318 AtomicU8 => u8,
1319 AtomicU16 => u16,
1320 AtomicU32 => u32,
1321 AtomicU64 => u64,
1322 AtomicUsize => usize,
1323);