json-ld-testing 0.21.4

Testing library for the `json-ld` library
Documentation
use super::Type;

pub struct UnknownType;

pub struct Parsed {
	pub ty: Type,
	pub required: bool,
	pub multiple: bool,
}

pub fn parse(ty: syn::Type) -> Result<Parsed, UnknownType> {
	match ty {
		syn::Type::Reference(r) => match reference_into_multiple(r) {
			Ok(ty) => Ok(Parsed {
				ty: parse_multiple(ty)?,
				required: true,
				multiple: true,
			}),
			Err(r) => Ok(Parsed {
				ty: parse_reference(r)?,
				required: true,
				multiple: false,
			}),
		},
		syn::Type::Path(p) => match path_into_option(p) {
			Ok(ty) => parse_optional(ty),
			Err(p) => Ok(Parsed {
				ty: parse_path(p)?,
				required: true,
				multiple: false,
			}),
		},
		_ => Err(UnknownType),
	}
}

fn parse_optional(ty: syn::Type) -> Result<Parsed, UnknownType> {
	match ty {
		syn::Type::Reference(r) => match reference_into_multiple(r) {
			Ok(ty) => Ok(Parsed {
				ty: parse_multiple(ty)?,
				required: false,
				multiple: true,
			}),
			Err(r) => Ok(Parsed {
				ty: parse_reference(r)?,
				required: false,
				multiple: false,
			}),
		},
		syn::Type::Path(p) => Ok(Parsed {
			ty: parse_path(p)?,
			required: false,
			multiple: false,
		}),
		_ => Err(UnknownType),
	}
}

fn parse_multiple(ty: syn::Type) -> Result<Type, UnknownType> {
	match ty {
		syn::Type::Reference(r) => parse_reference(r),
		syn::Type::Path(p) => parse_path(p),
		_ => Err(UnknownType),
	}
}

fn parse_reference(r: syn::TypeReference) -> Result<Type, UnknownType> {
	if r.mutability.is_none() {
		if let Some(lft) = r.lifetime {
			if lft.ident == "static" {
				if is_str(&r.elem) {
					return Ok(Type::String);
				}

				if is_iri(&r.elem) {
					return Ok(Type::Iri);
				}
			}
		}
	}

	Err(UnknownType)
}

fn reference_into_multiple(r: syn::TypeReference) -> Result<syn::Type, syn::TypeReference> {
	if r.mutability.is_none() {
		if let Some(lft) = &r.lifetime {
			if lft.ident == "static" && matches!(r.elem.as_ref(), syn::Type::Slice(_)) {
				match *r.elem {
					syn::Type::Slice(e) => return Ok(*e.elem),
					_ => unreachable!(),
				}
			}
		}
	}

	Err(r)
}

fn parse_path(p: syn::TypePath) -> Result<Type, UnknownType> {
	if is_bool_path(&p) {
		Ok(Type::Bool)
	} else if is_processing_mode_path(&p) {
		Ok(Type::ProcessingMode)
	} else if is_rdf_direction_path(&p) {
		Ok(Type::RdfDirection)
	} else if p.path.leading_colon.is_none()
		&& p.path.segments.len() == 1
		&& p.path.segments[0].arguments.is_empty()
	{
		Ok(Type::Ref(p.path.segments[0].ident.clone()))
	} else {
		Err(UnknownType)
	}
}

fn segment_is_ident(path: &syn::Path, i: usize, id: &str) -> bool {
	let segment = &path.segments[i];
	segment.ident == id
}

fn segment_is_empty_ident(path: &syn::Path, i: usize, id: &str) -> bool {
	let segment = &path.segments[i];
	segment.arguments.is_empty() && segment.ident == id
}

fn is_str(ty: &syn::Type) -> bool {
	match ty {
		syn::Type::Path(p) => is_str_path(p),
		_ => false,
	}
}

fn is_iri(ty: &syn::Type) -> bool {
	match ty {
		syn::Type::Path(p) => is_iri_path(p),
		_ => false,
	}
}

fn is_bool_path(p: &syn::TypePath) -> bool {
	p.qself.is_none()
		&& ((p.path.segments.len() == 3
			&& segment_is_empty_ident(&p.path, 0, "core")
			&& segment_is_empty_ident(&p.path, 1, "primitive")
			&& segment_is_empty_ident(&p.path, 2, "bool"))
			|| (p.path.leading_colon.is_none()
				&& p.path.segments.len() == 1
				&& segment_is_empty_ident(&p.path, 0, "bool")))
}

fn is_str_path(p: &syn::TypePath) -> bool {
	p.qself.is_none()
		&& ((p.path.segments.len() == 2
			&& (segment_is_empty_ident(&p.path, 0, "std")
				|| segment_is_empty_ident(&p.path, 0, "core")
				|| segment_is_empty_ident(&p.path, 0, "alloc"))
			&& segment_is_empty_ident(&p.path, 1, "str"))
			|| (p.path.leading_colon.is_none()
				&& p.path.segments.len() == 1
				&& segment_is_empty_ident(&p.path, 0, "str")))
}

fn is_iri_path(p: &syn::TypePath) -> bool {
	p.qself.is_none()
		&& ((p.path.segments.len() == 2
			&& segment_is_empty_ident(&p.path, 0, "iref")
			&& segment_is_ident(&p.path, 1, "Iri"))
			|| (p.path.leading_colon.is_none()
				&& p.path.segments.len() == 1
				&& segment_is_ident(&p.path, 0, "Iri")))
}

fn is_processing_mode_path(p: &syn::TypePath) -> bool {
	p.qself.is_none()
		&& ((p.path.segments.len() == 2
			&& segment_is_empty_ident(&p.path, 0, "json_ld")
			&& segment_is_empty_ident(&p.path, 1, "ProcessingMode"))
			|| (p.path.leading_colon.is_none()
				&& p.path.segments.len() == 1
				&& segment_is_empty_ident(&p.path, 0, "ProcessingMode")))
}

fn is_rdf_direction_path(p: &syn::TypePath) -> bool {
	p.qself.is_none()
		&& ((p.path.segments.len() == 2
			&& segment_is_empty_ident(&p.path, 0, "json_ld")
			&& segment_is_empty_ident(&p.path, 1, "RdfDirection"))
			|| (p.path.leading_colon.is_none()
				&& p.path.segments.len() == 1
				&& segment_is_empty_ident(&p.path, 0, "RdfDirection")))
}

fn is_option_path(p: &syn::TypePath) -> bool {
	p.qself.is_none()
		&& ((p.path.segments.len() == 3
			&& (segment_is_empty_ident(&p.path, 0, "std")
				|| segment_is_empty_ident(&p.path, 0, "core"))
			&& segment_is_empty_ident(&p.path, 1, "option")
			&& segment_is_ident(&p.path, 2, "Option"))
			|| (p.path.leading_colon.is_none()
				&& p.path.segments.len() == 1
				&& segment_is_ident(&p.path, 0, "Option")))
		&& is_option_arguments(&p.path.segments.last().unwrap().arguments)
}

fn is_option_arguments(args: &syn::PathArguments) -> bool {
	match args {
		syn::PathArguments::AngleBracketed(args) => {
			args.args.len() == 1 && matches!(&args.args[0], syn::GenericArgument::Type(_))
		}
		_ => false,
	}
}

fn path_into_option(p: syn::TypePath) -> Result<syn::Type, syn::TypePath> {
	if is_option_path(&p) {
		match p.path.segments.into_iter().next_back().unwrap().arguments {
			syn::PathArguments::AngleBracketed(args) => {
				match args.args.into_iter().next_back().unwrap() {
					syn::GenericArgument::Type(ty) => Ok(ty),
					_ => unreachable!(),
				}
			}
			_ => unreachable!(),
		}
	} else {
		Err(p)
	}
}