ld-core-derive 0.4.0

Derive macros for the `ld-core` Linked-Data serialization traits
Documentation
use std::collections::HashMap;

use proc_macro2::TokenStream;

use quote::{format_ident, quote};
use syn::{DeriveInput, spanned::Spanned};

use super::{
	Error, InterpretationBounds, TypeAttributes, read_field_attributes, read_type_attributes,
};

mod r#enum;
mod r#struct;

pub fn subject(input: DeriveInput) -> Result<TokenStream, Error> {
	let attrs = read_type_attributes(input.attrs)?;
	match input.data {
		syn::Data::Struct(s) => r#struct::generate(&attrs, input.ident, input.generics, s),
		syn::Data::Enum(e) => r#enum::generate(&attrs, input.ident, input.generics, e),
		syn::Data::Union(u) => Err(Error::UnionType(u.union_token.span())),
	}
}

struct FieldsDeserialization {
	deserialize_fields: Vec<TokenStream>,
	constructor: TokenStream,
	interpretation_bounds: InterpretationBounds,
	bounds: Vec<syn::WherePredicate>,
}

fn generate_fields(
	attrs: &TypeAttributes,
	fields: syn::Fields,
) -> Result<FieldsDeserialization, Error> {
	let mut interpretation_bounds = InterpretationBounds::default();
	let mut bounds = Vec::new();
	let mut deserialize_fields = Vec::with_capacity(fields.len());
	let constructor = match fields {
		syn::Fields::Unit => quote!(),
		syn::Fields::Unnamed(fields) => {
			let mut fields_constructors = Vec::with_capacity(fields.unnamed.len());
			for (i, f) in fields.unnamed.into_iter().enumerate() {
				let ident = format_ident!("a{i}");
				match generate_field(
					&attrs.prefixes,
					f.attrs,
					&f.ty,
					&mut interpretation_bounds,
					&mut bounds,
				)? {
					Some(deserialize_field) => {
						deserialize_fields.push(quote! {
							let #ident = #deserialize_field ;
						});

						fields_constructors.push(quote!(#ident));
					}
					None => {
						let ty = &f.ty;
						fields_constructors
							.push(quote!(<#ty as ::std::default::Default>::default()));
					}
				}
			}

			quote!(( #(#fields_constructors),* ))
		}
		syn::Fields::Named(fields) => {
			let mut fields_constructors = Vec::with_capacity(fields.named.len());
			for f in fields.named {
				let ident = f.ident;

				match generate_field(
					&attrs.prefixes,
					f.attrs,
					&f.ty,
					&mut interpretation_bounds,
					&mut bounds,
				)? {
					Some(deserialize_field) => {
						deserialize_fields.push(quote! {
							let #ident = #deserialize_field ;
						});

						fields_constructors.push(quote!(#ident));
					}
					None => {
						let ty = &f.ty;
						fields_constructors
							.push(quote!(#ident: <#ty as ::std::default::Default>::default()));
					}
				}
			}

			quote!({ #(#fields_constructors),* })
		}
	};

	Ok(FieldsDeserialization {
		deserialize_fields,
		constructor,
		interpretation_bounds,
		bounds,
	})
}

fn generate_field(
	prefixes: &HashMap<String, String>,
	attrs: Vec<syn::Attribute>,
	ty: &syn::Type,
	interpretation_bounds: &mut InterpretationBounds,
	bounds: &mut Vec<syn::WherePredicate>,
) -> Result<Option<TokenStream>, Error> {
	let attrs = read_field_attributes(attrs)?;

	match attrs.iri {
		Some(compact_iri) => {
			let iri = compact_iri.expand(prefixes)?.to_string();
			interpretation_bounds.reverse = true;

			bounds.push(
				syn::parse2(quote!(#ty: ::ld_core::LinkedDataDeserializePredicateObjects<I_>))
					.unwrap(),
			);

			Ok(Some(quote! {
				{
					let _iri_buf = ::ld_core::iri_rs::IriBuf::new(#iri.to_owned()).unwrap();
					match ::ld_core::rdfx::Interpretation::iri(interpretation_, _iri_buf.as_ref()) {
						Some(predicate_) => {
							let context_ = context_.with_predicate(&predicate_);
							::ld_core::LinkedDataDeserializePredicateObjects::deserialize_objects_in(
								interpretation_,
								dataset_,
								graph_,
								::ld_core::rdfx::dataset::PatternMatchingDataset::quad_objects(dataset_, graph_, resource_, &predicate_),
								context_
							)?
						}
						None => {
							let context_ = context_.with_predicate_iri(_iri_buf.as_ref());
							::ld_core::LinkedDataDeserializePredicateObjects::deserialize_objects_in(
								interpretation_,
								dataset_,
								graph_,
								[],
								context_
							)?
						}
					}
				}
			}))
		}
		None => {
			if attrs.is_id || attrs.flatten {
				bounds.push(
					syn::parse2(quote!(#ty: ::ld_core::LinkedDataDeserializeSubject<I_>)).unwrap(),
				);

				Ok(Some(quote! {
					::ld_core::LinkedDataDeserializeSubject::deserialize_subject_in(
						interpretation_,
						dataset_,
						graph_,
						resource_,
						context_
					)?
				}))
			} else if attrs.ignore {
				Ok(None)
			} else {
				panic!()
			}
		}
	}
}