use crate::parse::{Layer, OptionField, OptionsInput};
use crate::Result;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{PathArguments, Type};
pub fn generate_view(input: &OptionsInput) -> Result<TokenStream> {
let view_name = format_ident!("{}View", input.name);
let struct_name = &input.name;
let vis = &input.vis;
let layers = &input.layers;
let last_layer_idx = layers.len() - 1;
let mut struct_fields = Vec::new();
let mut new_params = Vec::new();
struct_fields.push(quote! { env: Option<::std::sync::Arc<#struct_name>> });
new_params.push(quote! { env: Option<::std::sync::Arc<#struct_name>> });
for (i, layer) in layers.iter().enumerate() {
let field_name = layer.ident();
if i == last_layer_idx {
struct_fields.push(quote! { #field_name: Option<&'a #struct_name> });
new_params.push(quote! { #field_name: Option<&'a #struct_name> });
} else {
struct_fields.push(quote! { #field_name: Option<::std::sync::Arc<#struct_name>> });
new_params.push(quote! { #field_name: Option<::std::sync::Arc<#struct_name>> });
}
}
let accessors = input
.fields
.iter()
.map(|field| generate_accessor(field, layers))
.collect::<Result<Vec<_>>>()?;
let mut new_body_fields = Vec::new();
new_body_fields.push(quote! { env });
for layer in layers {
let field_name = layer.ident();
new_body_fields.push(quote! { #field_name });
}
Ok(quote! {
#[automatically_derived]
#vis struct #view_name<'a> {
#(#struct_fields),*
}
#[automatically_derived]
impl<'a> #view_name<'a> {
#vis fn new(#(#new_params),*) -> Self {
Self {
#(#new_body_fields),*
}
}
#(#accessors)*
}
})
}
fn generate_accessor(field: &OptionField, layers: &[Layer]) -> Result<TokenStream> {
if field.nested {
return generate_nested_accessor(field, layers);
}
if field.merge.is_some() {
return generate_merge_accessor(field, layers);
}
generate_shadow_accessor(field, layers)
}
fn generate_shadow_accessor(field: &OptionField, layers: &[Layer]) -> Result<TokenStream> {
let field_name = &field.ident;
let inner_type = &field.inner_type;
let last_layer_idx = layers.len() - 1;
let mut chain_parts = Vec::new();
for (i, layer) in layers.iter().enumerate().rev() {
let layer_name = layer.ident();
if i == last_layer_idx {
chain_parts.push(quote! {
self.#layer_name.and_then(|l| l.#field_name.as_ref())
});
} else {
chain_parts.push(quote! {
self.#layer_name.as_ref().and_then(|l| l.#field_name.as_ref())
});
}
}
chain_parts.push(quote! {
self.env.as_ref().and_then(|l| l.#field_name.as_ref())
});
let first = &chain_parts[0];
let rest = &chain_parts[1..];
let chain = rest
.iter()
.fold(first.clone(), |acc, part| quote! { #acc.or(#part) });
Ok(quote! {
pub fn #field_name(&self) -> Option<&#inner_type> {
#chain
}
})
}
fn generate_merge_accessor(field: &OptionField, layers: &[Layer]) -> Result<TokenStream> {
let field_name = &field.ident;
let inner_type = &field.inner_type;
let last_layer_idx = layers.len() - 1;
let mut extend_stmts = Vec::new();
extend_stmts.push(quote! {
if let Some(ref env) = self.env {
if let Some(ref v) = env.#field_name {
merged.extend(v.clone());
}
}
});
for (i, layer) in layers.iter().enumerate() {
let layer_name = layer.ident();
if i == last_layer_idx {
extend_stmts.push(quote! {
if let Some(#layer_name) = self.#layer_name {
if let Some(ref v) = #layer_name.#field_name {
merged.extend(v.clone());
}
}
});
} else {
extend_stmts.push(quote! {
if let Some(ref #layer_name) = self.#layer_name {
if let Some(ref v) = #layer_name.#field_name {
merged.extend(v.clone());
}
}
});
}
}
Ok(quote! {
pub fn #field_name(&self) -> #inner_type {
let mut merged = <#inner_type>::default();
#(#extend_stmts)*
merged
}
})
}
fn generate_nested_accessor(field: &OptionField, layers: &[Layer]) -> Result<TokenStream> {
let field_name = &field.ident;
let inner_type = &field.inner_type;
let view_path = nested_view_path(inner_type)?;
let last_layer_idx = layers.len() - 1;
let mut new_args = Vec::new();
new_args.push(quote! {
self.env.as_ref()
.and_then(|l| l.#field_name.as_ref())
.map(|v| ::std::sync::Arc::new(v.clone()))
});
for (i, layer) in layers.iter().enumerate() {
let layer_name = layer.ident();
if i == last_layer_idx {
new_args.push(quote! {
self.#layer_name.and_then(|l| l.#field_name.as_ref())
});
} else {
new_args.push(quote! {
self.#layer_name.as_ref()
.and_then(|l| l.#field_name.as_ref())
.map(|v| ::std::sync::Arc::new(v.clone()))
});
}
}
Ok(quote! {
pub fn #field_name(&self) -> #view_path<'_> {
#view_path::new(#(#new_args),*)
}
})
}
fn nested_view_path(inner_type: &Type) -> Result<syn::Path> {
match inner_type {
Type::Path(type_path) if type_path.qself.is_none() => {
let mut path = type_path.path.clone();
let last_seg = path.segments.last_mut().ok_or_else(|| {
syn::Error::new_spanned(
inner_type,
"nested type must have at least one path segment",
)
})?;
last_seg.ident = format_ident!("{}View", last_seg.ident);
last_seg.arguments = PathArguments::None;
Ok(path)
}
_ => Err(syn::Error::new_spanned(
inner_type,
"nested option type must be a simple path type",
)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parse::OptionsInput;
use quote::quote;
#[test]
fn view_generated_for_three_layers() {
let input: syn::DeriveInput = syn::parse_quote! {
#[options(layers(runtime, account, operation))]
pub struct RequestOptions {
pub consistency_level: Option<String>,
pub throughput_bucket: Option<usize>,
}
};
let parsed = OptionsInput::from_derive_input(&input).unwrap();
let tokens = generate_view(&parsed).unwrap();
let expected = quote! {
#[automatically_derived]
pub struct RequestOptionsView<'a> {
env: Option<::std::sync::Arc<RequestOptions>>,
runtime: Option<::std::sync::Arc<RequestOptions>>,
account: Option<::std::sync::Arc<RequestOptions>>,
operation: Option<&'a RequestOptions>
}
#[automatically_derived]
impl<'a> RequestOptionsView<'a> {
pub fn new(
env: Option<::std::sync::Arc<RequestOptions>>,
runtime: Option<::std::sync::Arc<RequestOptions>>,
account: Option<::std::sync::Arc<RequestOptions>>,
operation: Option<&'a RequestOptions>
) -> Self {
Self { env, runtime, account, operation }
}
pub fn consistency_level(&self) -> Option<&String> {
self.operation.and_then(|l| l.consistency_level.as_ref())
.or(self.account.as_ref().and_then(|l| l.consistency_level.as_ref()))
.or(self.runtime.as_ref().and_then(|l| l.consistency_level.as_ref()))
.or(self.env.as_ref().and_then(|l| l.consistency_level.as_ref()))
}
pub fn throughput_bucket(&self) -> Option<&usize> {
self.operation.and_then(|l| l.throughput_bucket.as_ref())
.or(self.account.as_ref().and_then(|l| l.throughput_bucket.as_ref()))
.or(self.runtime.as_ref().and_then(|l| l.throughput_bucket.as_ref()))
.or(self.env.as_ref().and_then(|l| l.throughput_bucket.as_ref()))
}
}
};
assert_eq!(expected.to_string(), tokens.to_string());
}
#[test]
fn view_generated_for_two_layers() {
let input: syn::DeriveInput = syn::parse_quote! {
#[options(layers(runtime, account))]
pub struct ConnectionOptions {
pub request_timeout: Option<u64>,
}
};
let parsed = OptionsInput::from_derive_input(&input).unwrap();
let tokens = generate_view(&parsed).unwrap();
let expected = quote! {
#[automatically_derived]
pub struct ConnectionOptionsView<'a> {
env: Option<::std::sync::Arc<ConnectionOptions>>,
runtime: Option<::std::sync::Arc<ConnectionOptions>>,
account: Option<&'a ConnectionOptions>
}
#[automatically_derived]
impl<'a> ConnectionOptionsView<'a> {
pub fn new(
env: Option<::std::sync::Arc<ConnectionOptions>>,
runtime: Option<::std::sync::Arc<ConnectionOptions>>,
account: Option<&'a ConnectionOptions>
) -> Self {
Self { env, runtime, account }
}
pub fn request_timeout(&self) -> Option<&u64> {
self.account.and_then(|l| l.request_timeout.as_ref())
.or(self.runtime.as_ref().and_then(|l| l.request_timeout.as_ref()))
.or(self.env.as_ref().and_then(|l| l.request_timeout.as_ref()))
}
}
};
assert_eq!(expected.to_string(), tokens.to_string());
}
#[test]
fn view_includes_env_field_when_env_attr_present() {
let input: syn::DeriveInput = syn::parse_quote! {
#[options(layers(runtime, account))]
pub struct TestOptions {
#[option(env = "MY_VAR")]
pub my_field: Option<String>,
}
};
let parsed = OptionsInput::from_derive_input(&input).unwrap();
let tokens = generate_view(&parsed).unwrap();
let expected = quote! {
#[automatically_derived]
pub struct TestOptionsView<'a> {
env: Option<::std::sync::Arc<TestOptions>>,
runtime: Option<::std::sync::Arc<TestOptions>>,
account: Option<&'a TestOptions>
}
#[automatically_derived]
impl<'a> TestOptionsView<'a> {
pub fn new(
env: Option<::std::sync::Arc<TestOptions>>,
runtime: Option<::std::sync::Arc<TestOptions>>,
account: Option<&'a TestOptions>
) -> Self {
Self { env, runtime, account }
}
pub fn my_field(&self) -> Option<&String> {
self.account.and_then(|l| l.my_field.as_ref())
.or(self.runtime.as_ref().and_then(|l| l.my_field.as_ref()))
.or(self.env.as_ref().and_then(|l| l.my_field.as_ref()))
}
}
};
assert_eq!(expected.to_string(), tokens.to_string());
}
#[test]
fn view_merge_accessor_generated() {
let input: syn::DeriveInput = syn::parse_quote! {
#[options(layers(runtime, account))]
pub struct TestOptions {
#[option(merge = "extend")]
pub headers: Option<Vec<String>>,
}
};
let parsed = OptionsInput::from_derive_input(&input).unwrap();
let tokens = generate_view(&parsed).unwrap();
let inner_type: syn::Type = syn::parse_quote!(Vec<String>);
let expected = quote! {
#[automatically_derived]
pub struct TestOptionsView<'a> {
env: Option<::std::sync::Arc<TestOptions>>,
runtime: Option<::std::sync::Arc<TestOptions>>,
account: Option<&'a TestOptions>
}
#[automatically_derived]
impl<'a> TestOptionsView<'a> {
pub fn new(
env: Option<::std::sync::Arc<TestOptions>>,
runtime: Option<::std::sync::Arc<TestOptions>>,
account: Option<&'a TestOptions>
) -> Self {
Self { env, runtime, account }
}
pub fn headers(&self) -> Vec<String> {
let mut merged = <#inner_type>::default();
if let Some(ref env) = self.env {
if let Some(ref v) = env.headers {
merged.extend(v.clone());
}
}
if let Some(ref runtime) = self.runtime {
if let Some(ref v) = runtime.headers {
merged.extend(v.clone());
}
}
if let Some(account) = self.account {
if let Some(ref v) = account.headers {
merged.extend(v.clone());
}
}
merged
}
}
};
assert_eq!(expected.to_string(), tokens.to_string());
}
#[test]
fn view_nested_accessor_with_qualified_path() {
let input: syn::DeriveInput = syn::parse_quote! {
#[options(layers(runtime, account))]
pub struct TestOptions {
#[option(nested)]
pub child: Option<inner::ChildOptions>,
}
};
let parsed = OptionsInput::from_derive_input(&input).unwrap();
let tokens = generate_view(&parsed).unwrap();
let expected = quote! {
#[automatically_derived]
pub struct TestOptionsView<'a> {
env: Option<::std::sync::Arc<TestOptions>>,
runtime: Option<::std::sync::Arc<TestOptions>>,
account: Option<&'a TestOptions>
}
#[automatically_derived]
impl<'a> TestOptionsView<'a> {
pub fn new(
env: Option<::std::sync::Arc<TestOptions>>,
runtime: Option<::std::sync::Arc<TestOptions>>,
account: Option<&'a TestOptions>
) -> Self {
Self { env, runtime, account }
}
pub fn child(&self) -> inner::ChildOptionsView<'_> {
inner::ChildOptionsView::new(
self.env.as_ref()
.and_then(|l| l.child.as_ref())
.map(|v| ::std::sync::Arc::new(v.clone())),
self.runtime.as_ref()
.and_then(|l| l.child.as_ref())
.map(|v| ::std::sync::Arc::new(v.clone())),
self.account.and_then(|l| l.child.as_ref())
)
}
}
};
assert_eq!(expected.to_string(), tokens.to_string());
}
#[test]
fn view_nested_accessor_with_simple_path() {
let input: syn::DeriveInput = syn::parse_quote! {
#[options(layers(runtime, account))]
pub struct TestOptions {
#[option(nested)]
pub child: Option<ChildOptions>,
}
};
let parsed = OptionsInput::from_derive_input(&input).unwrap();
let tokens = generate_view(&parsed).unwrap();
let expected = quote! {
#[automatically_derived]
pub struct TestOptionsView<'a> {
env: Option<::std::sync::Arc<TestOptions>>,
runtime: Option<::std::sync::Arc<TestOptions>>,
account: Option<&'a TestOptions>
}
#[automatically_derived]
impl<'a> TestOptionsView<'a> {
pub fn new(
env: Option<::std::sync::Arc<TestOptions>>,
runtime: Option<::std::sync::Arc<TestOptions>>,
account: Option<&'a TestOptions>
) -> Self {
Self { env, runtime, account }
}
pub fn child(&self) -> ChildOptionsView<'_> {
ChildOptionsView::new(
self.env.as_ref()
.and_then(|l| l.child.as_ref())
.map(|v| ::std::sync::Arc::new(v.clone())),
self.runtime.as_ref()
.and_then(|l| l.child.as_ref())
.map(|v| ::std::sync::Arc::new(v.clone())),
self.account.and_then(|l| l.child.as_ref())
)
}
}
};
assert_eq!(expected.to_string(), tokens.to_string());
}
}