use super::super::ast::{
CSharpArgumentList, CSharpClassName, CSharpComment, CSharpExpression, CSharpIdentity,
CSharpMethodName, CSharpParamName, CSharpType,
};
use super::{CSharpCallablePlan, CSharpFieldPlan, CSharpMethodPlan};
#[derive(Debug, Clone)]
pub struct CSharpRecordPlan {
pub summary_doc: Option<CSharpComment>,
pub class_name: CSharpClassName,
pub fields: Vec<CSharpFieldPlan>,
pub is_blittable: bool,
pub methods: Vec<CSharpMethodPlan>,
pub is_error: bool,
}
impl CSharpRecordPlan {
pub fn is_empty(&self) -> bool {
self.fields.is_empty()
}
pub fn has_string_fields(&self) -> bool {
self.fields.iter().any(|f| f.csharp_type.contains_string())
}
pub fn has_pinned_params(&self) -> bool {
self.methods.iter().any(CSharpMethodPlan::has_pinned_params)
}
pub fn needs_system_text(&self) -> bool {
self.has_string_fields()
|| self
.methods
.iter()
.any(|m| m.params.iter().any(|p| p.csharp_type.contains_string()))
}
pub fn has_throwing_methods(&self) -> bool {
self.methods.iter().any(|m| m.return_kind.is_result())
}
pub fn has_async_methods(&self) -> bool {
self.methods.iter().any(CSharpMethodPlan::is_async)
}
pub(crate) fn exception_message_expr(&self) -> CSharpExpression {
let error = error_param_expr();
match self
.fields
.iter()
.find(|f| f.name.as_str() == "Message" && matches!(f.csharp_type, CSharpType::String))
{
Some(field) => CSharpExpression::MemberAccess {
receiver: Box::new(error),
name: field.name.clone(),
},
None => to_string_call(error),
}
}
}
pub(crate) fn error_param_expr() -> CSharpExpression {
CSharpExpression::Identity(CSharpIdentity::Param(CSharpParamName::new("error")))
}
pub(crate) fn to_string_call(receiver: CSharpExpression) -> CSharpExpression {
CSharpExpression::MethodCall {
receiver: Box::new(receiver),
method: CSharpMethodName::new("ToString"),
type_args: vec![],
args: CSharpArgumentList::default(),
}
}
#[cfg(test)]
mod tests {
use super::super::super::ast::{
CSharpExpression, CSharpIdentity, CSharpLiteral, CSharpLocalName, CSharpMethodName,
CSharpPropertyName, CSharpStatement, CSharpType,
};
use super::super::{
CFunctionName, CSharpFieldPlan, CSharpMethodPlan, CSharpReceiver, CSharpReturnKind,
};
use super::*;
fn primitive_field(name: &str, csharp_type: CSharpType) -> CSharpFieldPlan {
CSharpFieldPlan {
summary_doc: None,
name: CSharpPropertyName::from_source(name),
csharp_type,
wire_decode_expr: CSharpExpression::Literal(CSharpLiteral::Int(0)),
wire_size_expr: CSharpExpression::Literal(CSharpLiteral::Int(0)),
wire_encode_stmts: vec![CSharpStatement::Expression(CSharpExpression::Literal(
CSharpLiteral::Int(0),
))],
}
}
fn empty_record(class_name: &str, fields: Vec<CSharpFieldPlan>) -> CSharpRecordPlan {
CSharpRecordPlan {
summary_doc: None,
class_name: CSharpClassName::from_source(class_name),
is_blittable: false,
fields,
methods: vec![],
is_error: false,
}
}
fn dummy_throw_expr() -> CSharpExpression {
CSharpExpression::Identity(CSharpIdentity::Local(CSharpLocalName::new("placeholder")))
}
fn method_with_return_kind(return_kind: CSharpReturnKind) -> CSharpMethodPlan {
CSharpMethodPlan {
summary_doc: None,
name: CSharpMethodName::from_source("test"),
native_method_name: CSharpMethodName::from_source("OwnerTest"),
ffi_name: CFunctionName::new("boltffi_test".to_string()),
async_call: None,
receiver: CSharpReceiver::InstanceNative,
params: vec![],
return_type: CSharpType::Void,
return_kind,
wire_writers: vec![],
owner_is_blittable: false,
}
}
#[test]
fn exception_message_expr_forwards_string_message_field() {
let record = empty_record(
"app_error",
vec![
primitive_field("Code", CSharpType::Int),
primitive_field("Message", CSharpType::String),
],
);
assert_eq!(record.exception_message_expr().to_string(), "error.Message");
}
#[test]
fn exception_message_expr_falls_back_to_to_string_when_no_message_field() {
let record = empty_record(
"boundary",
vec![
primitive_field("X", CSharpType::Double),
primitive_field("Y", CSharpType::Double),
],
);
assert_eq!(
record.exception_message_expr().to_string(),
"error.ToString()",
);
}
#[test]
fn exception_message_expr_falls_back_when_message_field_is_not_string() {
let record = empty_record("status", vec![primitive_field("Message", CSharpType::Int)]);
assert_eq!(
record.exception_message_expr().to_string(),
"error.ToString()",
);
}
#[test]
fn has_throwing_methods_is_false_for_record_without_methods() {
let record = empty_record("point", vec![]);
assert!(!record.has_throwing_methods());
}
#[test]
fn has_throwing_methods_is_true_when_a_record_method_is_a_result() {
let mut record = empty_record("dataset", vec![]);
record.methods.push(method_with_return_kind(
CSharpReturnKind::WireDecodeResult {
ok_decode_expr: None,
err_throw_expr: dummy_throw_expr(),
},
));
assert!(record.has_throwing_methods());
}
#[test]
fn has_throwing_methods_is_false_when_no_record_method_is_a_result() {
let mut record = empty_record("dataset", vec![]);
record
.methods
.push(method_with_return_kind(CSharpReturnKind::Direct));
record
.methods
.push(method_with_return_kind(CSharpReturnKind::WireDecodeString));
assert!(!record.has_throwing_methods());
}
}