macro: add serialize_error attribute

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2023-05-02 09:38:39 +02:00
parent f381c740b4
commit 2aa0c70d0d
5 changed files with 89 additions and 30 deletions

View File

@ -128,6 +128,7 @@ pub struct FunctionAttrs {
pub raw_return: bool,
pub cv_variable: Option<Ident>,
pub prototype: Option<String>,
pub serialize_error: bool,
}
impl TryFrom<AttributeArgs> for FunctionAttrs {
@ -157,6 +158,8 @@ impl TryFrom<AttributeArgs> for FunctionAttrs {
syn::NestedMeta::Meta(syn::Meta::Path(path)) => {
if path.is_ident("raw_return") {
attrs.raw_return = true;
} else if path.is_ident("serialize_error") {
attrs.serialize_error = true;
} else {
error!(path => "unknown attribute");
}

View File

@ -52,15 +52,20 @@ impl ArgumentAttrs {
}
}
enum Return {
struct Return {
result: bool,
value: ReturnValue,
}
enum ReturnValue {
/// Return nothing. (This is different from returning an implicit undef!)
None(bool),
None,
/// Return a single element.
Single(bool),
Single,
/// We support tuple return types. They act like "list" return types in perl.
Tuple(bool, usize),
Tuple(usize),
}
pub fn handle_function(
@ -203,11 +208,23 @@ pub fn handle_function(
}
let has_return_value = match &func.sig.output {
syn::ReturnType::Default => Return::None(false),
syn::ReturnType::Default => Return {
result: false,
value: ReturnValue::None,
},
syn::ReturnType::Type(_arrow, ty) => match get_result_type(ty) {
(syn::Type::Tuple(tuple), result) if tuple.elems.is_empty() => Return::None(result),
(syn::Type::Tuple(tuple), result) => Return::Tuple(result, tuple.elems.len()),
(_, result) => Return::Single(result),
(syn::Type::Tuple(tuple), result) if tuple.elems.is_empty() => Return {
result,
value: ReturnValue::None,
},
(syn::Type::Tuple(tuple), result) => Return {
result,
value: ReturnValue::Tuple(tuple.elems.len()),
},
(_, result) => Return {
result,
value: ReturnValue::Single,
},
},
};
@ -328,24 +345,43 @@ fn handle_return_kind(
(quote! { _cv }, TokenStream::new())
};
let return_error = if ret.result {
if attr.serialize_error {
quote! {
match ::perlmod::to_value(&err) {
Ok(err) => return Err(err.into_mortal().into_raw()),
Err(err) => {
return Err(::perlmod::Value::new_string(&format!("{}\n", err))
.into_mortal()
.into_raw());
}
}
}
} else {
quote! {
return Err(::perlmod::Value::new_string(&format!("{}\n", err))
.into_mortal()
.into_raw());
}
}
} else {
TokenStream::new()
};
let pthx = crate::pthx_param();
match ret {
Return::None(result) => {
match ret.value {
ReturnValue::None => {
return_type = quote! { () };
if attr.raw_return {
bail!(&attr.raw_return => "raw_return attribute is illegal without a return value");
}
if result {
if ret.result {
handle_return = quote! {
match #name(#passed_arguments) {
Ok(()) => (),
Err(err) => {
return Err(::perlmod::Value::new_string(&format!("{}\n", err))
.into_mortal()
.into_raw());
}
Err(err) => { #return_error }
}
Ok(())
@ -370,18 +406,14 @@ fn handle_return_kind(
}
};
}
Return::Single(result) => {
ReturnValue::Single => {
return_type = quote! { *mut ::perlmod::ffi::SV };
if result {
if ret.result {
handle_return = quote! {
let result = match #name(#passed_arguments) {
Ok(output) => output,
Err(err) => {
return Err(::perlmod::Value::new_string(&format!("{}\n", err))
.into_mortal()
.into_raw());
}
Err(err) => { #return_error }
};
};
} else {
@ -417,7 +449,7 @@ fn handle_return_kind(
}
};
}
Return::Tuple(result, count) => {
ReturnValue::Tuple(count) => {
return_type = {
let mut rt = TokenStream::new();
for _ in 0..count {
@ -426,15 +458,11 @@ fn handle_return_kind(
quote! { (#rt) }
};
if result {
if ret.result {
handle_return = quote! {
let result = match #name(#passed_arguments) {
Ok(output) => output,
Err(err) => {
return Err(::perlmod::Value::new_string(&format!("{}\n", err))
.into_mortal()
.into_raw());
}
Err(err) => { #return_error }
};
};
} else {

View File

@ -85,6 +85,24 @@ mod export {
fn test_substr_return(#[raw] value: Value) -> Result<Value, Error> {
Ok(value.substr(3..6)?)
}
#[derive(serde::Serialize)]
struct MyError {
a: String,
b: String,
}
#[export(serialize_error)]
fn test_deserialized_error(fail: bool) -> Result<&'static str, MyError> {
if fail {
Err(MyError {
a: "first".to_string(),
b: "second".to_string(),
})
} else {
Ok("worked")
}
}
}
#[perlmod::package(name = "RSPM::EnvVarLibrary", lib = "x-${CARGO_PKG_NAME}-y")]

View File

@ -120,3 +120,12 @@ print("Substring test\n");
my $orig = "OneTwoThree";
my $sub = RSPM::Foo142::test_substr_return($orig);
print("[$orig] [$sub]\n");
my $ok = RSPM::Foo142::test_deserialized_error(0);
die "test_deserialized_error failed to return a value\n" if $ok ne 'worked';
$ok = eval { RSPM::Foo142::test_deserialized_error(1) };
die "test_deserialized_error error case returned a value\n" if defined $ok;
my $err = $@;
die "test_deserialized_error error is not a hash\n" if ref($err) ne 'HASH';
die "structured error has invalid fields\n" if join(',', sort(keys(%$err))) ne 'a,b';
print('error type: { a: ', $err->{a}, ', b: ', $err->{b}, " }\n");

View File

@ -45,3 +45,4 @@ Testing optional parameters
3, None
Substring test
[OneTwoThree] [Two]
error type: { a: first, b: second }