135 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			135 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| extern crate proc_macro;
 | |
| 
 | |
| use proc_macro::TokenStream;
 | |
| use quote::quote;
 | |
| use serde_yaml::Value;
 | |
| use syn::LitStr;
 | |
| use syn::parse_macro_input;
 | |
| 
 | |
| #[proc_macro]
 | |
| pub fn ip(input: TokenStream) -> TokenStream {
 | |
|     let input = parse_macro_input!(input as LitStr);
 | |
|     let ip_str = input.value();
 | |
| 
 | |
|     if let Ok(_) = ip_str.parse::<std::net::Ipv4Addr>() {
 | |
|         let expanded =
 | |
|             quote! { std::net::IpAddr::V4(#ip_str.parse::<std::net::Ipv4Addr>().unwrap()) };
 | |
|         return TokenStream::from(expanded);
 | |
|     }
 | |
| 
 | |
|     if let Ok(_) = ip_str.parse::<std::net::Ipv6Addr>() {
 | |
|         let expanded =
 | |
|             quote! { std::net::IpAddr::V6(#ip_str.parse::<std::net::Ipv6Addr>().unwrap()) };
 | |
|         return TokenStream::from(expanded);
 | |
|     }
 | |
| 
 | |
|     panic!("Invalid IP address: {}", ip_str);
 | |
| }
 | |
| 
 | |
| #[proc_macro]
 | |
| pub fn ipv4(input: TokenStream) -> TokenStream {
 | |
|     let input = parse_macro_input!(input as LitStr);
 | |
|     let ip_str = input.value();
 | |
| 
 | |
|     if let Ok(_) = ip_str.parse::<std::net::Ipv4Addr>() {
 | |
|         let expanded = quote! { #ip_str.parse::<std::net::Ipv4Addr>().unwrap() };
 | |
|         return TokenStream::from(expanded);
 | |
|     }
 | |
| 
 | |
|     panic!("Invalid IPv4 address: {}", ip_str);
 | |
| }
 | |
| 
 | |
| #[proc_macro]
 | |
| pub fn mac_address(input: TokenStream) -> TokenStream {
 | |
|     let input = parse_macro_input!(input as LitStr);
 | |
|     let mac_str = input.value();
 | |
| 
 | |
|     match parse_mac_address(&mac_str) {
 | |
|         Ok(bytes) => {
 | |
|             let b0 = bytes[0];
 | |
|             let b1 = bytes[1];
 | |
|             let b2 = bytes[2];
 | |
|             let b3 = bytes[3];
 | |
|             let b4 = bytes[4];
 | |
|             let b5 = bytes[5];
 | |
| 
 | |
|             quote! {
 | |
|             harmony_types::net::MacAddress( [#b0, #b1, #b2, #b3, #b4, #b5] )
 | |
|             }
 | |
|             .into()
 | |
|         }
 | |
|         Err(err) => syn::Error::new(input.span(), err).to_compile_error().into(),
 | |
|     }
 | |
| }
 | |
| 
 | |
| fn parse_mac_address(mac: &str) -> Result<[u8; 6], String> {
 | |
|     let parts: Vec<&str> = mac.split(':').collect();
 | |
|     if parts.len() != 6 {
 | |
|         return Err("MAC address must contain exactly six octets separated by colons".to_string());
 | |
|     }
 | |
| 
 | |
|     let mut bytes = [0u8; 6];
 | |
|     for (i, part) in parts.iter().enumerate() {
 | |
|         match u8::from_str_radix(part, 16) {
 | |
|             Ok(byte) => bytes[i] = byte,
 | |
|             Err(_) => return Err(format!("Invalid MAC address octet: {}", part)),
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     Ok(bytes)
 | |
| }
 | |
| 
 | |
| /// Verify that input is valid yaml by trying to deserialize it using
 | |
| /// serde_yaml::from_str::<serde_yaml::value::Value>(input)
 | |
| ///
 | |
| /// panics: If yaml is not valid
 | |
| #[proc_macro]
 | |
| pub fn yaml(input: TokenStream) -> TokenStream {
 | |
|     // TODO, accept a second argument that is the type to be deserialized to and validate at
 | |
|     // compile-time that deserialization is possible
 | |
|     //
 | |
|     // It does not seem to be doable with the way macros are designed : we may pass an ident to the
 | |
|     // macro, but the macro only has access to the ident, not the type itself.
 | |
|     //
 | |
|     // I also tried to create a restricted version of this macro, but this time serde got in the
 | |
|     // way : my use case is to make sure that the yaml I am given can deserialize strictly (with
 | |
|     // deny_unknown_attributes option on) to k8s-openapi types. But the k8s-openapi types are not
 | |
|     // annotated with deny_unknown_attributes and there does not seem to be a way to tell serde to
 | |
|     // deserialize in "strict mode" when calling the deserialization function itself. I tried
 | |
|     // wrapping the types in something like :
 | |
|     //
 | |
|     // ```rust
 | |
|     // #[derive(Deserialize, Debug)]
 | |
|     // #[serde(deny_unknown_fields)]
 | |
|     // struct Strict<T>(pub T);
 | |
|     // ```
 | |
|     //
 | |
|     // But it still does actually deserialize T strictly. I gave up for now at this point. Will
 | |
|     // find a solution some day!
 | |
| 
 | |
|     let yaml = parse_macro_input!(input as LitStr);
 | |
| 
 | |
|     serde_yaml::from_str::<Value>(yaml.value().as_str()).expect("Should be valid yaml");
 | |
| 
 | |
|     quote! {
 | |
|         serde_yaml::from_str(#yaml)
 | |
|     }
 | |
|     .into()
 | |
| }
 | |
| 
 | |
| /// Verify that a string is a valid(ish) ingress path
 | |
| /// Panics if path does not start with `/`
 | |
| #[proc_macro]
 | |
| pub fn ingress_path(input: TokenStream) -> TokenStream {
 | |
|     let input = parse_macro_input!(input as LitStr);
 | |
|     let path_str = input.value();
 | |
| 
 | |
|     match path_str.starts_with("/") {
 | |
|         true => {
 | |
|             let expanded = quote! {(#path_str.to_string()) };
 | |
|             return TokenStream::from(expanded);
 | |
|         }
 | |
|         false => panic!("Invalid ingress path"),
 | |
|     }
 | |
| }
 |