proc-macro-workshop:builder-4

godme發表於2022-06-29
// Generate a `build` method to go from builder to original struct.
//
// This method should require that every one of the fields has been explicitly
// set; it should return an error if a field is missing. The precise error type
// is not important. Consider using Box<dyn Error>, which you can construct
// using the impl From<String> for Box<dyn Error>.
//
//     impl CommandBuilder {
//         pub fn build(&mut self) -> Result<Command, Box<dyn Error>> {
//             ...
//         }
//     }

use derive_builder::Builder;

#[derive(Builder)]
pub struct Command {
    executable: String,
    args: Vec<String>,
    env: Vec<String>,
    current_dir: String,
}

fn main() {
    let mut builder = Command::builder();
    builder.executable("cargo".to_owned());
    builder.args(vec!["build".to_owned(), "--release".to_owned()]);
    builder.env(vec![]);
    builder.current_dir("..".to_owned());

    let command = builder.build().unwrap();
    assert_eq!(command.executable, "cargo");
}

如提示所言,我們這一關主要的是實現build方法,同時注意返回結果型別為Result
原來的資料我們設定在了XBuilder中,這一關就需要從XBuilder中反向注入引數了。

這裡需要擴充套件的是,一般操作過程錯誤處理需要返回Result
並且對於一個Command而言,可以有一些預設引數。
在這裡,我們將檢查全部欄位,如果沒有設定,返回異常。
雖然這裡沒有對異常進行檢查,但是後續的問題中,對異常的檢查會十分嚴格。

pub(super) fn solution(
    fields: &crate::common::FieldsType,
    builder_ident: &syn::Ident,
    origin_ident: &syn::Ident,
) -> proc_macro2::TokenStream {
    let idents: Vec<_> = fields.iter().map(|f| &f.ident).collect();
    quote::quote! {
        impl #builder_ident {
            pub fn build(&self) -> std::result::Result<#origin_ident, std::boxed::Box<dyn std::error::Error>> {
                // 異常檢查展開
                #(
                    if self.#idents.is_none() {
                        let err = std::format!("field {} missing", std::stringify!(#idents));
                        return std::result::Result::Err(err.into());
                    }
                )*
                // 數值回傳
                let res = #origin_ident {
                    #(
                        #idents: self.#idents.clone().unwrap()
                    ),*
                };
                std::result::Result::Ok(res)
            }
        }
    }
}

fn solution1(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let ast = syn::parse_macro_input!(input as syn::DeriveInput);
    let fields = {
        match common::parse_fields(&ast) {
            Ok(f) => f,
            Err(_e) => std::panic!(std::stringify!(_e)),
        }
    };

    let origin_ident = &ast.ident;
    let builder_ident = &quote::format_ident!("{}Builder", origin_ident);
    let mut token_stream = proc_macro2::TokenStream::new();

    // solution2
    let solution2_stream = solution2::solution(fields, origin_ident, builder_ident);
    token_stream.extend(solution2_stream);

    // solution35
    let solution3_stream = solution35::soultion(fields, builder_ident);
    token_stream.extend(solution3_stream);

    // solution4
    let solution45_stream = solution4::solution(fields, builder_ident, origin_ident);
    token_stream.extend(solution45_stream);

    proc_macro::TokenStream::from(token_stream)
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結