use proc_macro2::TokenStream;
use quote::quote;
use syn::Ident;
use super::ViewAttributes;
pub fn mutable_attributes() -> (TokenStream, TokenStream) {
(quote! { #[cfg_attr(feature = "uniffi", derive(::uniffi::Object))] }, quote! {})
}
pub fn view_attributes() -> ViewAttributes {
ViewAttributes {
struct_attr: quote! { #[cfg_attr(feature = "uniffi", derive(::uniffi::Object))] },
impl_attr: quote! { #[cfg_attr(feature = "uniffi", ::uniffi::export)] },
id_method_attr: quote! {},
extra_impl: quote! {},
}
}
pub fn uniffi_view_edit_impl(view_name: &Ident, model_name: &Ident, mutable_name: &Ident) -> TokenStream {
quote! {
#[::uniffi::export]
impl #view_name {
#[uniffi::method(name = "edit")]
pub fn uniffi_edit(&self, trx: &::ankurah::transaction::Transaction) -> Result<#mutable_name, ::ankurah::core::error::MutationError> {
use ::ankurah::model::View;
match trx.edit::<#model_name>(&self.entity) {
Ok(mutable_borrow) => Ok(mutable_borrow.into_core()),
Err(e) => Err(::ankurah::core::error::MutationError::AccessDenied(e))
}
}
}
}
}
pub fn uniffi_mutable_field_methods(model: &crate::model::description::ModelDescription) -> TokenStream {
let mutable_name = model.mutable_name();
let model_name_str = model.name().to_string();
let methods: Vec<TokenStream> = model
.active_fields()
.iter()
.filter_map(|field| {
let field_name = field.ident.as_ref()?;
let uniffi_method_name = quote::format_ident!("uniffi_{}", field_name);
let field_name_str = field_name.to_string();
let backend_desc = model.backend_registry.resolve_active_type(field)?;
let wrapper_type_str = if backend_desc.is_provided_type() {
backend_desc.wrapper_type_path("local")
} else {
backend_desc.wrapper_type_name_for_model(&model_name_str)
};
let wrapper_type: syn::Type = syn::parse_str(&wrapper_type_str).expect("Failed to parse wrapper type path");
Some(quote! {
#[uniffi::method(name = #field_name_str)]
pub fn #uniffi_method_name(&self) -> #wrapper_type {
#wrapper_type(self.#field_name())
}
})
})
.collect();
if methods.is_empty() {
return quote! {};
}
quote! {
#[::uniffi::export]
impl #mutable_name {
#(#methods)*
}
}
}
pub fn uniffi_custom_wrappers(model: &crate::model::description::ModelDescription) -> TokenStream {
let model_name_str = model.name().to_string();
let mut seen = std::collections::HashSet::new();
let wrappers: Vec<TokenStream> = model
.active_fields()
.iter()
.filter_map(|field| {
let backend_desc = model.backend_registry.resolve_active_type(field)?;
if backend_desc.is_provided_type() {
return None;
}
if !seen.insert(backend_desc.wrapper_type_name_for_model(&model_name_str)) {
return None;
}
Some(backend_desc.uniffi_wrapper("external", Some(&model_name_str)))
})
.collect();
quote! {
#(#wrappers)*
}
}
fn is_ref_type(ty: &syn::Type) -> Option<Ident> {
if let syn::Type::Path(type_path) = ty {
let segment = type_path.path.segments.last()?;
if segment.ident == "Ref" {
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
if let Some(syn::GenericArgument::Type(syn::Type::Path(inner_path))) = args.args.first() {
return Some(inner_path.path.segments.last()?.ident.clone());
}
}
}
}
None
}
fn is_option_ref_type(ty: &syn::Type) -> Option<Ident> {
if let syn::Type::Path(type_path) = ty {
let segment = type_path.path.segments.last()?;
if segment.ident == "Option" {
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
if let Some(syn::GenericArgument::Type(inner_type)) = args.args.first() {
return is_ref_type(inner_type);
}
}
}
}
None
}
pub fn uniffi_impl(model: &crate::model::description::ModelDescription) -> TokenStream {
let name = model.name();
let view_name = model.view_name();
let mutable_name = model.mutable_name();
let ref_name = model.ref_name();
let ops_name = quote::format_ident!("{}Ops", name);
let input_name = quote::format_ident!("{}Input", name);
let resultset_name = model.resultset_name();
let changeset_name = model.changeset_name();
let livequery_name = model.livequery_name();
let view_edit_impl = uniffi_view_edit_impl(&view_name, &name, &mutable_name);
let mutable_field_methods = uniffi_mutable_field_methods(model);
let ref_wrapper = uniffi_ref_wrapper(&ref_name, &name, &view_name);
let input_record = uniffi_input_record(model, &input_name, &name);
let ops_wrapper = uniffi_ops_wrapper(&ops_name, &name, &view_name, &livequery_name, &input_name);
let resultset_wrapper = uniffi_resultset_wrapper(&resultset_name, &view_name);
let changeset_wrapper = uniffi_changeset_wrapper(&changeset_name, &view_name, &resultset_name);
let livequery_wrapper = uniffi_livequery_wrapper(&name, &livequery_name, &view_name, &resultset_name, &changeset_name);
let custom_wrappers = uniffi_custom_wrappers(model);
quote! {
#view_edit_impl
#mutable_field_methods
#ref_wrapper
#input_record
#ops_wrapper
#resultset_wrapper
#changeset_wrapper
#livequery_wrapper
#custom_wrappers
}
}
fn uniffi_ref_wrapper(ref_name: &Ident, model_name: &Ident, view_name: &Ident) -> TokenStream {
quote! {
#[derive(::uniffi::Object)]
pub struct #ref_name(::ankurah::property::Ref<#model_name>);
#[::uniffi::export]
impl #ref_name {
#[uniffi::constructor]
pub fn from_view(view: &#view_name) -> Self {
#ref_name(view.r())
}
pub fn id(&self) -> ::ankurah::proto::EntityId {
self.0.id()
}
pub async fn get(&self, ctx: &::ankurah::core::context::Context) -> Result<#view_name, ::ankurah::core::error::RetrievalError> {
self.0.get(ctx).await
}
}
impl From<::ankurah::property::Ref<#model_name>> for #ref_name {
fn from(r: ::ankurah::property::Ref<#model_name>) -> Self {
#ref_name(r)
}
}
impl From<#ref_name> for ::ankurah::property::Ref<#model_name> {
fn from(r: #ref_name) -> Self {
r.0
}
}
}
}
fn uniffi_input_record(model: &crate::model::description::ModelDescription, input_name: &Ident, model_name: &Ident) -> TokenStream {
let fields = model.active_fields();
let input_fields: Vec<_> = fields
.iter()
.map(|field| {
let field_name = field.ident.as_ref().unwrap();
let field_vis = &field.vis;
if is_option_ref_type(&field.ty).is_some() {
quote! { #field_vis #field_name: Option<String> }
} else if is_ref_type(&field.ty).is_some() {
quote! { #field_vis #field_name: String }
} else {
let field_ty = &field.ty;
quote! { #field_vis #field_name: #field_ty }
}
})
.collect();
let field_conversions: Vec<_> = fields
.iter()
.map(|field| {
let field_name = field.ident.as_ref().unwrap();
if is_option_ref_type(&field.ty).is_some() {
quote! {
#field_name: match input.#field_name {
Some(ref s) => Some(::ankurah::property::Ref::from(
::ankurah::proto::EntityId::from_base64(s)?
)),
None => None,
}
}
} else if is_ref_type(&field.ty).is_some() {
quote! {
#field_name: ::ankurah::property::Ref::from(
::ankurah::proto::EntityId::from_base64(&input.#field_name)?
)
}
} else {
quote! { #field_name: input.#field_name }
}
})
.collect();
quote! {
#[derive(::uniffi::Record)]
pub struct #input_name {
#(#input_fields),*
}
impl TryFrom<#input_name> for #model_name {
type Error = ::ankurah::proto::IdParseError;
fn try_from(input: #input_name) -> Result<Self, Self::Error> {
Ok(#model_name {
#(#field_conversions),*
})
}
}
}
}
fn uniffi_ops_wrapper(ops_name: &Ident, model_name: &Ident, view_name: &Ident, livequery_name: &Ident, input_name: &Ident) -> TokenStream {
quote! {
#[derive(::uniffi::Object)]
pub struct #ops_name;
#[::uniffi::export(async_runtime = "tokio")]
impl #ops_name {
#[uniffi::constructor]
pub fn new() -> Self {
Self
}
pub async fn get(
&self,
ctx: &::ankurah::core::context::Context,
id: &::ankurah::proto::EntityId,
) -> Result<#view_name, ::ankurah::core::error::RetrievalError> {
ctx.get::<#view_name>(id.clone()).await
}
pub async fn fetch(
&self,
ctx: &::ankurah::core::context::Context,
selection: String,
values: Vec<::ankurah::core::QueryValue>,
) -> Result<Vec<std::sync::Arc<#view_name>>, ::ankurah::core::error::RetrievalError> {
let mut selection = ::ankurah::ankql::parser::parse_selection(&selection)?;
selection.predicate = selection.predicate.populate(values)?;
let results = ctx.fetch::<#view_name>(selection).await?;
Ok(results.into_iter().map(std::sync::Arc::new).collect())
}
pub async fn query(
&self,
ctx: &::ankurah::core::context::Context,
selection: String,
values: Vec<::ankurah::core::QueryValue>,
) -> Result<#livequery_name, ::ankurah::core::error::RetrievalError> {
let mut selection = ::ankurah::ankql::parser::parse_selection(&selection)?;
selection.predicate = selection.predicate.populate(values)?;
let lq = ctx.query::<#view_name>(selection)?;
Ok(#livequery_name::from(lq))
}
pub async fn query_nocache(
&self,
ctx: &::ankurah::core::context::Context,
selection: String,
values: Vec<::ankurah::core::QueryValue>,
) -> Result<#livequery_name, ::ankurah::core::error::RetrievalError> {
let mut selection = ::ankurah::ankql::parser::parse_selection(&selection)?;
selection.predicate = selection.predicate.populate(values)?;
let args = ::ankurah::MatchArgs { selection, cached: false };
let lq = ctx.query::<#view_name>(args)?;
Ok(#livequery_name::from(lq))
}
pub async fn create(
&self,
trx: &::ankurah::transaction::Transaction,
input: #input_name,
) -> Result<#view_name, ::ankurah::core::error::MutationError> {
use ::ankurah::Mutable;
use std::convert::TryInto;
let model: #model_name = input.try_into()
.map_err(|e: ::ankurah::proto::IdParseError| ::ankurah::core::error::MutationError::General(Box::new(e)))?;
let mutable = trx.create(&model).await?;
Ok(mutable.read())
}
pub async fn create_one(
&self,
ctx: &::ankurah::core::context::Context,
input: #input_name,
) -> Result<#view_name, ::ankurah::core::error::MutationError> {
use ::ankurah::Mutable;
use std::convert::TryInto;
let model: #model_name = input.try_into()
.map_err(|e: ::ankurah::proto::IdParseError| ::ankurah::core::error::MutationError::General(Box::new(e)))?;
let tx = ctx.begin();
let mutable = tx.create(&model).await?;
let view = mutable.read();
tx.commit().await?;
Ok(view)
}
}
}
}
fn uniffi_resultset_wrapper(resultset_name: &Ident, view_name: &Ident) -> TokenStream {
quote! {
#[derive(::uniffi::Object)]
pub struct #resultset_name(::ankurah::core::resultset::ResultSet<#view_name>);
#[::uniffi::export]
impl #resultset_name {
pub fn items(&self) -> Vec<std::sync::Arc<#view_name>> {
use ::ankurah::signals::Get;
self.0.get().into_iter().map(std::sync::Arc::new).collect()
}
pub fn get(&self, index: u32) -> Option<std::sync::Arc<#view_name>> {
use ::ankurah::signals::Get;
self.0.get().into_iter().nth(index as usize).map(std::sync::Arc::new)
}
pub fn by_id(&self, id: &::ankurah::proto::EntityId) -> Option<std::sync::Arc<#view_name>> {
self.0.by_id(id).map(std::sync::Arc::new)
}
pub fn len(&self) -> u32 {
use ::ankurah::signals::Get;
self.0.get().len() as u32
}
pub fn is_loaded(&self) -> bool {
self.0.is_loaded()
}
}
impl From<::ankurah::core::resultset::ResultSet<#view_name>> for #resultset_name {
fn from(rs: ::ankurah::core::resultset::ResultSet<#view_name>) -> Self {
#resultset_name(rs)
}
}
}
}
fn uniffi_changeset_wrapper(changeset_name: &Ident, view_name: &Ident, resultset_name: &Ident) -> TokenStream {
quote! {
#[derive(::uniffi::Object)]
pub struct #changeset_name(::ankurah::core::changes::ChangeSet<#view_name>);
#[::uniffi::export]
impl #changeset_name {
pub fn resultset(&self) -> #resultset_name {
#resultset_name(self.0.resultset.wrap())
}
pub fn initial(&self) -> Vec<std::sync::Arc<#view_name>> {
self.0.initial().into_iter().map(std::sync::Arc::new).collect()
}
pub fn added(&self) -> Vec<std::sync::Arc<#view_name>> {
self.0.added().into_iter().map(std::sync::Arc::new).collect()
}
pub fn appeared(&self) -> Vec<std::sync::Arc<#view_name>> {
self.0.appeared().into_iter().map(std::sync::Arc::new).collect()
}
pub fn removed(&self) -> Vec<std::sync::Arc<#view_name>> {
self.0.removed().into_iter().map(std::sync::Arc::new).collect()
}
pub fn updated(&self) -> Vec<std::sync::Arc<#view_name>> {
self.0.updated().into_iter().map(std::sync::Arc::new).collect()
}
}
impl From<::ankurah::core::changes::ChangeSet<#view_name>> for #changeset_name {
fn from(cs: ::ankurah::core::changes::ChangeSet<#view_name>) -> Self {
#changeset_name(cs)
}
}
}
}
fn uniffi_livequery_wrapper(
model_name: &Ident,
livequery_name: &Ident,
view_name: &Ident,
resultset_name: &Ident,
changeset_name: &Ident,
) -> TokenStream {
let callback_name = quote::format_ident!("{}LiveQueryCallback", model_name);
quote! {
#[::uniffi::export(callback_interface)]
pub trait #callback_name: Send + Sync {
fn on_change(&self, changeset: std::sync::Arc<#changeset_name>);
}
#[derive(::uniffi::Object)]
pub struct #livequery_name(::ankurah::LiveQuery<#view_name>);
#[::uniffi::export]
impl #livequery_name {
pub fn items(&self) -> Vec<std::sync::Arc<#view_name>> {
use ::ankurah::signals::Get;
self.0.get().into_iter().map(std::sync::Arc::new).collect()
}
pub fn resultset(&self) -> #resultset_name {
#resultset_name(self.0.resultset())
}
pub fn is_loaded(&self) -> bool {
self.0.loaded()
}
pub fn len(&self) -> u32 {
use ::ankurah::signals::Get;
self.0.get().len() as u32
}
pub fn error(&self) -> Option<String> {
use ::ankurah::signals::Get;
self.0.error().map(|e| e.as_ref().map(|e| e.to_string())).get()
}
pub async fn update_selection(&self, new_selection: String, values: Vec<::ankurah::core::QueryValue>) -> Result<(), ::ankurah::core::error::RetrievalError> {
let mut selection = ::ankurah::ankql::parser::parse_selection(&new_selection)?;
selection.predicate = selection.predicate.populate(values)?;
self.0.update_selection_wait(selection).await?;
Ok(())
}
pub fn current_selection(&self) -> String {
use ::ankurah::signals::With;
self.0.selection().with(|(sel, _version)| sel.to_string())
}
pub fn subscribe(&self, callback: Box<dyn #callback_name>) -> std::sync::Arc<::ankurah::signals::SubscriptionGuard> {
use ::ankurah::signals::Subscribe;
let guard = self.0.subscribe(move |changeset| {
callback.on_change(std::sync::Arc::new(#changeset_name::from(changeset)));
});
std::sync::Arc::new(guard)
}
}
impl From<::ankurah::LiveQuery<#view_name>> for #livequery_name {
fn from(lq: ::ankurah::LiveQuery<#view_name>) -> Self {
#livequery_name(lq)
}
}
}
}