1#![allow(unused_comparisons)]
2mod bucket;
25mod bucket_name;
26mod file_util;
27mod iam_validation;
28mod location_uri;
29mod object_sizes;
30mod strings;
31mod timeouts;
32mod transition_in_days;
33
34use crate::file_util::get_absolute_file_path;
35use crate::iam_validation::{PermissionValidator, ValidationResponse};
36use crate::location_uri::LocationUri;
37use crate::object_sizes::ObjectSizes;
38use crate::strings::{check_string_requirements, StringRequirements};
39use crate::timeouts::Timeouts;
40use crate::transition_in_days::TransitionInfo;
41use proc_macro::TokenStream;
42use quote::__private::Span;
43use quote::quote;
44use std::env;
45use syn::spanned::Spanned;
46use syn::{parse_macro_input, Error, LitInt, LitStr};
47
48#[proc_macro]
55pub fn string_with_only_alphanumerics_and_underscores(input: TokenStream) -> TokenStream {
56 let output: LitStr = syn::parse(input).unwrap();
57 let value = output.value();
58
59 let requirements = StringRequirements::not_empty_allowed_chars(vec!['_']);
60
61 match check_string_requirements(&value, output.span(), requirements) {
62 None => quote!(
63 StringWithOnlyAlphaNumericsAndUnderscores(#value.to_string())
64 )
65 .into(),
66 Some(e) => e.into_compile_error().into(),
67 }
68}
69
70#[proc_macro]
77pub fn string_with_only_alphanumerics_underscores_and_hyphens(input: TokenStream) -> TokenStream {
78 let output: LitStr = syn::parse(input).unwrap();
79 let value = output.value();
80
81 let requirements = StringRequirements::not_empty_allowed_chars(vec!['_', '-']);
82
83 match check_string_requirements(&value, output.span(), requirements) {
84 None => quote!(
85 StringWithOnlyAlphaNumericsUnderscoresAndHyphens(#value.to_string())
86 )
87 .into(),
88 Some(e) => e.into_compile_error().into(),
89 }
90}
91
92#[proc_macro]
103pub fn string_with_only_alphanumerics_and_hyphens(input: TokenStream) -> TokenStream {
104 let output: LitStr = syn::parse(input).unwrap();
105 let value = output.value();
106
107 let requirements = StringRequirements::not_empty_allowed_chars(vec!['-']);
108
109 match check_string_requirements(&value, output.span(), requirements) {
110 None => quote!(
111 StringWithOnlyAlphaNumericsAndHyphens(#value.to_string())
112 )
113 .into(),
114 Some(e) => e.into_compile_error().into(),
115 }
116}
117
118#[proc_macro]
129pub fn app_sync_api_name(input: TokenStream) -> TokenStream {
130 let output: LitStr = syn::parse(input).unwrap();
131 let value = output.value();
132
133 if value.len() > 50 {
134 return Error::new(output.span(), "name cannot be longer than 50 characters".to_string())
135 .into_compile_error()
136 .into();
137 }
138
139 let requirements = StringRequirements::not_empty_allowed_chars(vec!['-', '_', ' ']);
140
141 match check_string_requirements(&value, output.span(), requirements) {
142 None => quote!(
143 AppSyncApiName(#value.to_string())
144 )
145 .into(),
146 Some(e) => e.into_compile_error().into(),
147 }
148}
149
150#[proc_macro]
161pub fn channel_namespace_name(input: TokenStream) -> TokenStream {
162 let output: LitStr = syn::parse(input).unwrap();
163 let value = output.value();
164
165 if value.len() > 50 {
166 return Error::new(output.span(), "name cannot be longer than 50 characters".to_string())
167 .into_compile_error()
168 .into();
169 }
170
171 let requirements = StringRequirements::not_empty_allowed_chars(vec!['-']);
172
173 match check_string_requirements(&value, output.span(), requirements) {
174 None => quote!(
175 ChannelNamespaceName(#value.to_string())
176 )
177 .into(),
178 Some(e) => e.into_compile_error().into(),
179 }
180}
181
182#[proc_macro]
192pub fn string_for_secret(input: TokenStream) -> TokenStream {
193 let output: LitStr = syn::parse(input).unwrap();
194 let value = output.value();
195
196 let requirements = StringRequirements::not_empty_allowed_chars(vec!['/', '_', '+', '=', '.', '@', '-']);
197
198 match check_string_requirements(&value, output.span(), requirements) {
199 None => quote!(
200 StringForSecret(#value.to_string())
201 )
202 .into(),
203 Some(e) => e.into_compile_error().into(),
204 }
205}
206
207#[proc_macro]
215pub fn env_var_key(input: TokenStream) -> TokenStream {
216 let output: LitStr = syn::parse(input).unwrap();
217 let value = output.value();
218
219 if value.len() < 2 {
220 return Error::new(output.span(), "env var key should be at least two characters long".to_string())
221 .into_compile_error()
222 .into();
223 }
224
225 if value.get(0..1).expect("just checked that length is at least 2") == "_" {
226 return Error::new(output.span(), "env var key should not start with an underscore".to_string())
227 .into_compile_error()
228 .into();
229 }
230
231 if value.chars().any(|c| !c.is_alphanumeric() && c != '_') {
232 return Error::new(
233 output.span(),
234 "env var key should only contain alphanumeric characters and underscores".to_string(),
235 )
236 .into_compile_error()
237 .into();
238 }
239
240 quote!(
241 EnvVarKey(#value.to_string())
242 )
243 .into()
244}
245
246#[proc_macro]
259pub fn zip_file(input: TokenStream) -> TokenStream {
260 let output: syn::Result<LitStr> = syn::parse(input);
261
262 let output = match output {
263 Ok(output) => output,
264 Err(_) => {
265 return Error::new(Span::call_site(), "zip_file macro should contain value".to_string())
266 .into_compile_error()
267 .into();
268 }
269 };
270
271 let value = output.value();
272
273 if !value.ends_with(".zip") {
274 return Error::new(output.span(), format!("zip should end with `.zip` (found `{value}`)"))
275 .into_compile_error()
276 .into();
277 }
278
279 let value = match get_absolute_file_path(&value) {
280 Ok(v) => v,
281 Err(e) => {
282 return Error::new(output.span(), e).into_compile_error().into();
283 }
284 };
285
286 quote!(
287 ZipFile(#value.to_string())
288 )
289 .into()
290}
291
292#[proc_macro]
303pub fn toml_file(input: TokenStream) -> TokenStream {
304 let output: syn::Result<LitStr> = syn::parse(input);
305
306 let output = match output {
307 Ok(output) => output,
308 Err(_) => {
309 return Error::new(Span::call_site(), "toml_file macro should contain value".to_string())
310 .into_compile_error()
311 .into();
312 }
313 };
314
315 let value = output.value();
316
317 if !value.ends_with(".toml") {
318 return Error::new(output.span(), format!("toml file should end with `.toml` (found `{value}`)"))
319 .into_compile_error()
320 .into();
321 }
322
323 let value = match get_absolute_file_path(&value) {
324 Ok(v) => v,
325 Err(e) => {
326 return Error::new(output.span(), e).into_compile_error().into();
327 }
328 };
329
330 quote!(
331 TomlFile(#value.to_string())
332 )
333 .into()
334}
335
336#[proc_macro]
338pub fn non_zero_number(input: TokenStream) -> TokenStream {
339 let output = match syn::parse::<LitInt>(input) {
340 Ok(v) => v,
341 Err(_) => {
342 return Error::new(Span::call_site(), "value is not a valid number".to_string())
343 .into_compile_error()
344 .into();
345 }
346 };
347
348 let as_number: syn::Result<u32> = output.base10_parse();
349
350 let num = if let Ok(num) = as_number {
351 if num == 0 {
352 return Error::new(output.span(), "value should not be null".to_string())
353 .into_compile_error()
354 .into();
355 }
356 num
357 } else {
358 return Error::new(output.span(), "value is not a valid u32 number".to_string())
359 .into_compile_error()
360 .into();
361 };
362
363 quote!(
364 NonZeroNumber(#num)
365 )
366 .into()
367}
368
369macro_rules! number_check {
370 ($name:ident,$min:literal,$max:literal,$output:ident,$type:ty) => {
371 #[doc = "Checks whether the value that will be wrapped in the "]
372 #[doc = stringify!($output)]
373 #[doc = "struct is between "]
374 #[doc = stringify!($min)]
375 #[doc = "and "]
376 #[doc = stringify!($max)]
377 #[proc_macro]
378 pub fn $name(input: TokenStream) -> TokenStream {
379 let output: LitInt = syn::parse(input).unwrap();
380
381 let as_number: syn::Result<$type> = output.base10_parse();
382
383 if let Ok(num) = as_number {
384 if num < $min {
385 Error::new(output.span(), format!("value should be at least {}", $min)).into_compile_error().into()
386 } else if num > $max {
387 Error::new(output.span(), format!("value should be at most {}", $max)).into_compile_error().into()
388 } else {
389 quote!(
390 $output(#num)
391 ).into()
392 }
393 } else {
394 Error::new(output.span(), "value is not a valid number".to_string()).into_compile_error().into()
395 }
396 }
397 }
398}
399
400number_check!(memory, 128, 10240, Memory, u16);
401number_check!(timeout, 1, 900, Timeout, u16);
402number_check!(delay_seconds, 0, 900, DelaySeconds, u16);
403number_check!(maximum_message_size, 1024, 1048576, MaximumMessageSize, u32);
404number_check!(message_retention_period, 60, 1209600, MessageRetentionPeriod, u32);
405number_check!(visibility_timeout, 0, 43200, VisibilityTimeout, u32);
406number_check!(receive_message_wait_time, 0, 20, ReceiveMessageWaitTime, u8);
407number_check!(sqs_event_source_max_concurrency, 2, 1000, SqsEventSourceMaxConcurrency, u16);
408number_check!(connection_attempts, 1, 3, ConnectionAttempts, u8);
409number_check!(s3_origin_read_timeout, 1, 120, S3OriginReadTimeout, u8);
410number_check!(deployment_duration_in_minutes, 0, 1440, DeploymentDurationInMinutes, u16);
411number_check!(growth_factor, 0, 100, GrowthFactor, u8);
412
413const NO_REMOTE_OVERRIDE_ENV_VAR_NAME: &str = "RUSTY_CDK_NO_REMOTE";
414const RUSTY_CDK_RECHECK_ENV_VAR_NAME: &str = "RUSTY_CDK_RECHECK";
415
416#[proc_macro]
441pub fn bucket(input: TokenStream) -> TokenStream {
442 let input: LitStr = syn::parse(input).unwrap();
443 let value = input.value();
444
445 if value.starts_with("arn:") {
446 return Error::new(input.span(), "value is an arn, not a bucket name".to_string())
447 .into_compile_error()
448 .into();
449 }
450
451 if value.starts_with("s3:") {
452 return Error::new(input.span(), "value has s3 prefix, should be plain bucket name".to_string())
453 .into_compile_error()
454 .into();
455 }
456
457 let no_remote_check_wanted = env::var(NO_REMOTE_OVERRIDE_ENV_VAR_NAME)
458 .ok()
459 .and_then(|v| v.parse().ok())
460 .unwrap_or(false);
461
462 if no_remote_check_wanted {
463 return bucket::bucket_output(value);
464 }
465
466 let rechecked_wanted = env::var(RUSTY_CDK_RECHECK_ENV_VAR_NAME)
467 .ok()
468 .and_then(|v| v.parse().ok())
469 .unwrap_or(false);
470
471 if !rechecked_wanted {
472 match bucket::valid_bucket_according_to_file_storage(&value) {
473 bucket::FileStorageOutput::Valid => {
474 return bucket::bucket_output(value)
475 }
476 bucket::FileStorageOutput::Invalid => {
477 return Error::new(input.span(), format!("(cached) did not find bucket with name `{value}` in your account. You can rerun this check by adding setting the `{RUSTY_CDK_RECHECK_ENV_VAR_NAME}` env var to true")).into_compile_error().into()
478 }
479 bucket::FileStorageOutput::Unknown => {}
480 }
481 }
482
483 let rt = tokio::runtime::Runtime::new().unwrap();
484
485 match rt.block_on(bucket::find_bucket(input.clone())) {
486 Ok(_) => {
487 bucket::update_file_storage(bucket::FileStorageInput::Valid(&value));
488 bucket::bucket_output(value)
489 }
490 Err(e) => {
491 bucket::update_file_storage(bucket::FileStorageInput::Invalid(&value));
492 e.into_compile_error().into()
493 }
494 }
495}
496
497const ADDITIONAL_ALLOWED_FOR_BUCKET_NAME: [char; 2] = ['.', '-'];
498
499#[proc_macro]
525pub fn bucket_name(input: TokenStream) -> TokenStream {
526 let input: LitStr = syn::parse(input).unwrap();
527 let value = input.value();
528
529 if value.chars().any(|c| c.is_uppercase()) {
530 return Error::new(input.span(), "value contains uppercase letters".to_string())
531 .into_compile_error()
532 .into();
533 }
534
535 if value
536 .chars()
537 .any(|c| !c.is_alphanumeric() && !ADDITIONAL_ALLOWED_FOR_BUCKET_NAME.contains(&c))
538 {
539 return Error::new(
540 input.span(),
541 "value should contain only letters, numbers, periods and dashes".to_string(),
542 )
543 .into_compile_error()
544 .into();
545 }
546
547 let no_remote_check_wanted = env::var(NO_REMOTE_OVERRIDE_ENV_VAR_NAME)
548 .ok()
549 .and_then(|v| v.parse().ok())
550 .unwrap_or(false);
551
552 if no_remote_check_wanted {
553 return bucket_name::bucket_name_output(value);
554 }
555
556 let rechecked_wanted = env::var(RUSTY_CDK_RECHECK_ENV_VAR_NAME)
557 .ok()
558 .and_then(|v| v.parse().ok())
559 .unwrap_or(false);
560
561 if !rechecked_wanted {
562 match bucket_name::valid_bucket_name_according_to_file_storage(&value) {
563 bucket_name::FileStorageOutput::Valid => {
564 return bucket_name::bucket_name_output(value)
565 }
566 bucket_name::FileStorageOutput::Invalid => {
567 return Error::new(input.span(), format!("(cached) bucket name is already taken. You can rerun this check by adding setting the `{RUSTY_CDK_RECHECK_ENV_VAR_NAME}` env var to true")).into_compile_error().into()
568 }
569 bucket_name::FileStorageOutput::Unknown => {}
570 }
571 }
572
573 match bucket_name::check_bucket_name(input) {
574 Ok(_) => {
575 bucket_name::update_file_storage(bucket_name::FileStorageInput::Valid(&value));
576 bucket_name::bucket_name_output(value)
577 }
578 Err(e) => {
579 bucket_name::update_file_storage(bucket_name::FileStorageInput::Invalid(&value));
580 e.into_compile_error().into()
581 }
582 }
583}
584
585const POSSIBLE_LOG_RETENTION_VALUES: [u16; 22] = [
586 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1096, 1827, 2192, 2557, 2922, 3288, 3653,
587];
588
589#[proc_macro]
595pub fn log_retention(input: TokenStream) -> TokenStream {
596 let output = match syn::parse::<LitInt>(input) {
597 Ok(v) => v,
598 Err(_) => {
599 return Error::new(Span::call_site(), "value is not a valid number".to_string())
600 .into_compile_error()
601 .into();
602 }
603 };
604
605 let as_number: syn::Result<u16> = output.base10_parse();
606
607 if let Ok(num) = as_number {
608 if POSSIBLE_LOG_RETENTION_VALUES.contains(&num) {
609 quote! {
610 RetentionInDays(#num)
611 }
612 .into()
613 } else {
614 Error::new(output.span(), format!("value should be one of {:?}", POSSIBLE_LOG_RETENTION_VALUES))
615 .into_compile_error()
616 .into()
617 }
618 } else {
619 Error::new(output.span(), "value is not a valid u16 number".to_string())
620 .into_compile_error()
621 .into()
622 }
623}
624
625const ADDITIONAL_ALLOWED_FOR_LOG_GROUP: [char; 6] = ['.', '-', '_', '#', '/', '\\'];
626
627#[proc_macro]
635pub fn log_group_name(input: TokenStream) -> TokenStream {
636 let output: LitStr = syn::parse(input).unwrap();
637 let value = output.value();
638
639 if value.is_empty() {
640 return Error::new(output.span(), "value should not be blank".to_string())
641 .into_compile_error()
642 .into();
643 }
644
645 if value.len() > 512 {
646 return Error::new(output.span(), "value should not be longer than 512 chars".to_string())
647 .into_compile_error()
648 .into();
649 }
650
651 if value
652 .chars()
653 .any(|c| !c.is_alphanumeric() && !ADDITIONAL_ALLOWED_FOR_LOG_GROUP.contains(&c))
654 {
655 return Error::new(
656 output.span(),
657 format!(
658 "value should only contain alphanumeric characters and {:?}",
659 ADDITIONAL_ALLOWED_FOR_LOG_GROUP
660 ),
661 )
662 .into_compile_error()
663 .into();
664 }
665
666 quote!(
667 LogGroupName(#value.to_string())
668 )
669 .into()
670}
671
672#[proc_macro]
686pub fn iam_action(input: TokenStream) -> TokenStream {
687 let output: LitStr = syn::parse(input).unwrap();
688 let value = output.value();
689
690 if value.is_empty() {
691 return Error::new(output.span(), "value should not be blank".to_string())
692 .into_compile_error()
693 .into();
694 }
695
696 let validator = PermissionValidator::new();
697 match validator.is_valid_action(&value) {
698 ValidationResponse::Valid => quote!(
699 IamAction(#value.to_string())
700 )
701 .into(),
702 ValidationResponse::Invalid(message) => Error::new(output.span(), message).into_compile_error().into(),
703 }
704}
705
706#[proc_macro]
717pub fn lifecycle_object_sizes(input: TokenStream) -> TokenStream {
718 let ObjectSizes { first, second } = parse_macro_input!(input);
719
720 if first.is_some() && second.is_some() && first.unwrap() > second.unwrap() {
722 return Error::new(
723 Span::call_site(),
724 format!(
725 "first number ({}) in `lifecycle_object_sizes` should be smaller than second ({})",
726 first.unwrap(),
727 second.unwrap()
728 ),
729 )
730 .into_compile_error()
731 .into();
732 }
733
734 let first_output = if let Some(first) = first {
735 quote! {
736 Some(#first)
737 }
738 } else {
739 quote! { None }
740 };
741
742 let second_output = if let Some(second) = second {
743 quote! {
744 Some(#second)
745 }
746 } else {
747 quote! { None }
748 };
749
750 quote! {
751 S3LifecycleObjectSizes(#first_output, #second_output)
752 }
753 .into()
754}
755
756#[proc_macro]
766pub fn origin_path(input: TokenStream) -> TokenStream {
767 let output: LitStr = syn::parse(input).unwrap();
768 let value = output.value();
769
770 if !value.starts_with("/") || value.ends_with("/") {
771 return Error::new(
772 value.span(),
773 format!("origin path should start with a / and should not end with / (but got {})", value),
774 )
775 .into_compile_error()
776 .into();
777 }
778
779 quote! {
780 OriginPath(#value)
781 }
782 .into()
783}
784
785#[proc_macro]
795pub fn default_root_object(input: TokenStream) -> TokenStream {
796 let output: LitStr = syn::parse(input).unwrap();
797 let value = output.value();
798
799 if value.starts_with("/") || value.ends_with("/") {
800 return Error::new(value.span(), "default root object should not start with /".to_string())
801 .into_compile_error()
802 .into();
803 }
804
805 quote! {
806 DefaultRootObject(#value)
807 }
808 .into()
809}
810
811#[proc_macro]
819pub fn cf_connection_timeout(input: TokenStream) -> TokenStream {
820 let Timeouts { first, second } = parse_macro_input!(input);
821
822 if let Some(first) = first {
823 if first > 10 {
824 return Error::new(
825 Span::call_site(),
826 format!("connection timeout was {} but should be between 1 and 10", first),
827 )
828 .into_compile_error()
829 .into();
830 } else if let Some(second) = second && second < first {
831 return Error::new(
832 Span::call_site(),
833 format!(
834 "response completion timeout was {} but should be larger than connection timeout ({})",
835 second, first
836 ),
837 )
838 .into_compile_error()
839 .into();
840 }
841 }
842
843 let first_output = if let Some(first) = first {
844 quote! {
845 Some(#first)
846 }
847 } else {
848 quote! { None }
849 };
850
851 let second_output = if let Some(second) = second {
852 quote! {
853 Some(#second)
854 }
855 } else {
856 quote! { None }
857 };
858
859 quote! {
860 CfConnectionTimeout(#first_output, #second_output)
861 }
862 .into()
863}
864
865#[proc_macro]
876pub fn lambda_permission_action(input: TokenStream) -> TokenStream {
877 let output: LitStr = syn::parse(input).unwrap();
878 let value = output.value();
879
880 let requirements = StringRequirements::not_empty_prefix("lambda");
881
882 match check_string_requirements(&value, output.span(), requirements) {
883 None => quote!(
884 LambdaPermissionAction(#value.to_string())
885 )
886 .into(),
887 Some(e) => e.into_compile_error().into(),
888 }
889}
890
891#[proc_macro]
902pub fn app_config_name(input: TokenStream) -> TokenStream {
903 let output: LitStr = syn::parse(input).unwrap();
904 let value = output.value();
905
906 if value.is_empty() || value.len() > 64 {
907 return Error::new(
908 Span::call_site(),
909 "app config name should be between 1 and 64 chars in length".to_string(),
910 )
911 .into_compile_error()
912 .into();
913 }
914
915 quote! {
916 AppConfigName(#value.to_string())
917 }
918 .into()
919}
920
921const LIFECYCLE_STORAGE_TYPES: [&str; 6] = [
922 "IntelligentTiering",
923 "OneZoneIA",
924 "StandardIA",
925 "GlacierDeepArchive",
926 "Glacier",
927 "GlacierInstantRetrieval",
928];
929const LIFECYCLE_STORAGE_TYPES_MORE_THAN_THIRTY_DAYS: [&str; 2] = ["OneZoneIA", "StandardIA"];
930
931#[proc_macro]
939pub fn lifecycle_transition_in_days(input: TokenStream) -> TokenStream {
940 let TransitionInfo { days, service } = parse_macro_input!(input);
941 let service = service.trim();
942
943 if !LIFECYCLE_STORAGE_TYPES.contains(&service) {
944 return Error::new(
945 Span::call_site(),
946 format!("service should be one of {} (was {})", LIFECYCLE_STORAGE_TYPES.join(","), service),
947 )
948 .into_compile_error()
949 .into();
950 } else if LIFECYCLE_STORAGE_TYPES_MORE_THAN_THIRTY_DAYS.contains(&service) && days <= 30 {
951 return Error::new(
952 Span::call_site(),
953 format!(
954 "service of type {} cannot have transition under 30 days",
955 LIFECYCLE_STORAGE_TYPES_MORE_THAN_THIRTY_DAYS.join(" or ")
956 ),
957 )
958 .into_compile_error()
959 .into();
960 }
961
962 quote! {
963 LifecycleTransitionInDays(#days)
964 }
965 .into()
966}
967
968const LOCATION_URI_TYPES: [&str; 4] = ["hosted", "codepipeline", "secretsmanager", "s3"];
969const LOCATION_URI_CODEPIPELINE_START: &str = "codepipeline://";
970const LOCATION_URI_SECRETS_MANAGER_START: &str = "secretsmanager://";
971const LOCATION_URI_S3_START: &str = "s3://";
972
973
974#[proc_macro]
982pub fn location_uri(input: TokenStream) -> TokenStream {
983 let LocationUri {
984 location_uri_type,
985 content,
986 } = parse_macro_input!(input);
987 let location_uri_type = location_uri_type.trim();
988
989 #[allow(unused)] let mut error = None;
991
992 if !LOCATION_URI_TYPES.contains(&location_uri_type) {
993 error = Some(format!(
994 "unrecognized location uri {}, should be one of {}",
995 location_uri_type,
996 LOCATION_URI_TYPES.join(",")
997 ));
998 } else {
999 if location_uri_type == "hosted" {
1000 return quote! {
1001 LocationUri(#location_uri_type.to_string())
1002 }
1003 .into();
1004 } else if content.is_none() {
1005 error = Some(format!("location uri of type {}, should have content", location_uri_type));
1006 } else {
1007 let content = content.expect("just checked that this is present");
1008
1009 if location_uri_type == "codepipeline" && !content.starts_with(LOCATION_URI_CODEPIPELINE_START) {
1010 error = Some(format!(
1011 "content of type codepipeline should start with {}",
1012 LOCATION_URI_CODEPIPELINE_START
1013 ));
1014 } else if location_uri_type == "secretsmanager" && !content.starts_with(LOCATION_URI_SECRETS_MANAGER_START) {
1015 error = Some(format!(
1016 "content of type secretsmanager should start with {}",
1017 LOCATION_URI_SECRETS_MANAGER_START
1018 ));
1019 } else if location_uri_type == "s3" && !content.starts_with(LOCATION_URI_S3_START) {
1020 error = Some(format!("content of type s3 should start with {}", LOCATION_URI_S3_START));
1021 } else {
1022 return quote! {
1023 LocationUri(#content.to_string())
1024 }
1025 .into();
1026 }
1027 }
1028 }
1029
1030 Error::new(
1031 Span::call_site(),
1032 error.unwrap_or_else(|| "unknown error".to_string()),
1033 )
1034 .into_compile_error()
1035 .into()
1036}