ld-core-derive 0.4.0

Derive macros for the `ld-core` Linked-Data serialization traits
Documentation
use proc_macro2::{Ident, TokenStream};
use quote::quote;

use super::{Error, generate_fields};
use crate::generate::{
	InterpretationBounds, TypeAttributes, extend_generics, read_variant_attributes,
};

pub fn generate(
	attrs: &TypeAttributes,
	ident: Ident,
	generics: syn::Generics,
	e: syn::DataEnum,
) -> Result<TokenStream, Error> {
	let mut interpretation_bounds = InterpretationBounds {
		reverse: true,
		..Default::default()
	};

	let mut bounds = Vec::new();

	let mut deserialize_variants = Vec::with_capacity(e.variants.len());
	for v in e.variants {
		let v_ident = &v.ident;
		let v_attrs = read_variant_attributes(v.attrs)?;

		let deserialize_variant = match v_attrs.iri {
			Some(compact_iri) => {
				let iri = compact_iri.expand(&attrs.prefixes)?.to_string();

				match variant_shape(&v.fields) {
					VariantShape::Simple(ty) => {
						bounds.push(
							syn::parse2(
								quote!(#ty: ::ld_core::LinkedDataDeserializePredicateObjects<I_>),
							)
							.unwrap(),
						);

						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);
										let result = ::ld_core::LinkedDataDeserializePredicateObjects::deserialize_objects_in(
											interpretation_,
											dataset_,
											graph_,
											::ld_core::rdfx::dataset::PatternMatchingDataset::quad_objects(dataset_, graph_, resource_, &predicate),
											context_
										);

										match result {
											Ok(value) => return Ok(Self::#v_ident(value)),
											Err(e) => error = Some(e)
										}
									}
									None => {
										error = Some(::ld_core::FromLinkedDataError::MissingRequiredValue(
											context_.into_iris(interpretation_)
										))
									}
								}
							}
						}
					}
					VariantShape::Compound => {
						let fields_de = generate_fields(attrs, v.fields)?;
						interpretation_bounds.add(fields_de.interpretation_bounds);
						bounds.extend(fields_de.bounds);

						let deserialize_fields = fields_de.deserialize_fields;
						let constructor = fields_de.constructor;

						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 mut objects = ::ld_core::rdfx::dataset::PatternMatchingDataset::quad_objects(dataset_, graph_, resource_, &predicate);

										let result = match objects.next() {
											Some(resource_) => {
												(|| {
													#(#deserialize_fields)*

													if objects.next().is_some() {
														Err(::ld_core::FromLinkedDataError::TooManyValues(
															context_.into_iris(interpretation_)
														))
													} else {
														Ok(Self::#v_ident #constructor)
													}
												})()
											}
											None => Err(::ld_core::FromLinkedDataError::MissingRequiredValue(
												context_.into_iris(interpretation_)
											))
										};

										match result {
											Ok(value) => return Ok(value),
											Err(e) => error = Some(e)
										}
									}
									None => {
										error = Some(::ld_core::FromLinkedDataError::MissingRequiredValue(
											context_.into_iris(interpretation_)
										))
									}
								}
							}
						}
					}
					VariantShape::Unit => {
						interpretation_bounds.reverse = true;
						quote! {
							{
								for i in ::ld_core::rdfx::interpretation::ReverseInterpretation::iris_of(interpretation_, resource_) {
									if i.as_str() == #iri {
										return Ok(Self::#v_ident)
									}
								}
							}
						}
					}
				}
			}
			None => match variant_shape(&v.fields) {
				VariantShape::Simple(ty) => {
					bounds.push(
						syn::parse2(quote!(#ty: ::ld_core::LinkedDataDeserializeSubject<I_>))
							.unwrap(),
					);

					quote! {
						let result = ::ld_core::LinkedDataDeserializeSubject::deserialize_subject_in(
							interpretation_,
							dataset_,
							graph_,
							resource_,
							context_
						);

						match result {
							Ok(value) => return Ok(Self::#v_ident(value)),
							Err(e) => error = Some(e)
						}
					}
				}
				VariantShape::Compound => {
					let variant_de = generate_fields(attrs, v.fields)?;
					interpretation_bounds.add(variant_de.interpretation_bounds);
					bounds.extend(variant_de.bounds);

					let deserialize_fields = variant_de.deserialize_fields;
					let constructor = variant_de.constructor;

					quote! {
						let result = (|| {
							#(#deserialize_fields)*
							Ok(Self::#v_ident #constructor)
						})();

						match result {
							Ok(value) => return Ok(value),
							Err(e) => error = Some(e)
						}
					}
				}
				VariantShape::Unit => {
					panic!()
				}
			},
		};

		deserialize_variants.push(deserialize_variant)
	}

	let ld_generics = extend_generics(&generics, interpretation_bounds, bounds);
	let (_, ty_generics, _) = generics.split_for_impl();
	let (impl_generics, _, where_clause) = ld_generics.split_for_impl();

	Ok(quote! {
		impl #impl_generics ::ld_core::LinkedDataDeserializeSubject<I_> for #ident #ty_generics #where_clause {
			fn deserialize_subject_in<D_>(
				interpretation_: &I_,
				dataset_: &D_,
				graph_: Option<&I_::Resource>,
				resource_: &I_::Resource,
				outer_context_: ::ld_core::Context<I_>
			) -> Result<Self, ::ld_core::FromLinkedDataError>
			where
				D_: ::ld_core::rdfx::dataset::PatternMatchingDataset<Subject = I_::Resource>
			{
				let context_ = outer_context_.with_subject(resource_);
				let mut error = None;

				#(#deserialize_variants)*

				Err(error.unwrap_or_else(|| {
					::ld_core::FromLinkedDataError::InvalidSubject {
						context: outer_context_.into_iris(interpretation_),
						subject: ::ld_core::rdfx::interpretation::ReverseInterpretation::iris_of(interpretation_, resource_).next().map(|i| i.into_owned())
					}
				}))
			}
		}

		impl #impl_generics ::ld_core::LinkedDataDeserializePredicateObjects<I_> for #ident #ty_generics #where_clause {
			fn deserialize_objects_in<'de_, D_>(
				interpretation: &I_,
				dataset: &D_,
				graph: Option<&I_::Resource>,
				objects: impl IntoIterator<Item = &'de_ I_::Resource>,
				context: ::ld_core::Context<I_>
			) -> Result<Self, ::ld_core::FromLinkedDataError>
			where
				I_::Resource: 'de_,
				D_: ::ld_core::rdfx::dataset::PatternMatchingDataset<Subject = I_::Resource>
			{
				let mut objects = objects.into_iter();

				match objects.next() {
					Some(object) => {
						let value = <Self as ::ld_core::LinkedDataDeserializeSubject<I_>>::deserialize_subject_in(
							interpretation,
							dataset,
							graph,
							object,
							context
						)?;

						if objects.next().is_some() {
							Err(::ld_core::FromLinkedDataError::TooManyValues(
								context.into_iris(interpretation)
							))
						} else {
							Ok(value)
						}
					}
					None => {
						Err(::ld_core::FromLinkedDataError::MissingRequiredValue(
							context.into_iris(interpretation)
						))
					}
				}
			}
		}
	})
}

enum VariantShape<'a> {
	Simple(&'a syn::Type),
	Compound,
	Unit,
}

fn variant_shape(fields: &syn::Fields) -> VariantShape<'_> {
	match fields {
		syn::Fields::Named(_) => VariantShape::Compound,
		syn::Fields::Unnamed(unnamed_fields) => {
			let mut fields_iter = unnamed_fields.unnamed.iter();

			if let Some(field) = fields_iter.next() {
				if fields_iter.next().is_none()
					&& !field.attrs.iter().any(|attr| attr.path().is_ident("ld"))
				{
					return VariantShape::Simple(&field.ty);
				}
			}

			VariantShape::Compound
		}
		syn::Fields::Unit => VariantShape::Unit,
	}
}