code-moniker-core 0.4.0

Core symbol-graph types and per-language extractors for code-moniker.
Documentation
mod builder;
pub mod encoding;
pub mod query;
mod view;

pub use builder::MonikerBuilder;
pub use encoding::EncodingError;
pub use view::{MonikerView, Segment, SegmentIter};

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct Moniker {
	bytes: Box<[u8]>,
}

impl Moniker {
	pub fn from_encoded(bytes: Vec<u8>) -> Result<Self, EncodingError> {
		MonikerView::from_encoded(&bytes)?;
		Ok(Self {
			bytes: bytes.into_boxed_slice(),
		})
	}

	pub(crate) fn from_encoded_unchecked(bytes: Vec<u8>) -> Self {
		Self {
			bytes: bytes.into_boxed_slice(),
		}
	}

	pub fn as_view(&self) -> MonikerView<'_> {
		unsafe { MonikerView::from_encoded_unchecked(&self.bytes) }
	}

	pub fn as_encoded(&self) -> &[u8] {
		&self.bytes
	}

	pub fn into_encoded(self) -> Vec<u8> {
		self.bytes.into_vec()
	}
}

#[cfg(feature = "serde")]
impl serde::Serialize for Moniker {
	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
	where
		S: serde::Serializer,
	{
		use serde::ser::SerializeStruct;

		let mut state = serializer.serialize_struct("Moniker", 1)?;
		state.serialize_field("bytes", &self.bytes)?;
		state.end()
	}
}

#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Moniker {
	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
	where
		D: serde::Deserializer<'de>,
	{
		#[derive(serde::Deserialize)]
		struct Repr {
			bytes: Vec<u8>,
		}

		let repr = Repr::deserialize(deserializer)?;
		Self::from_encoded(repr.bytes).map_err(serde::de::Error::custom)
	}
}

#[cfg(test)]
mod tests {
	use super::*;

	#[test]
	fn builder_rebuilds_same_encoding_from_view() {
		let m1 = MonikerBuilder::new()
			.project(b"my-app")
			.segment(b"module", b"main")
			.segment(b"class", b"Foo")
			.segment(b"method", b"bar(2)")
			.build();

		let v = m1.as_view();
		let mut b2 = MonikerBuilder::new();
		b2.project(v.project());
		for seg in v.segments() {
			b2.segment(seg.kind, seg.name);
		}
		let m2 = b2.build();

		assert_eq!(m1.as_encoded(), m2.as_encoded());
		assert_eq!(m1, m2);
	}

	#[test]
	fn eq_via_bytes() {
		let a = MonikerBuilder::new()
			.project(b"x")
			.segment(b"path", b"a")
			.build();
		let b = MonikerBuilder::new()
			.project(b"x")
			.segment(b"path", b"a")
			.build();
		let c = MonikerBuilder::new()
			.project(b"x")
			.segment(b"path", b"b")
			.build();
		assert_eq!(a, b);
		assert_ne!(a, c);
	}

	#[test]
	fn ord_places_parent_before_child() {
		let parent = MonikerBuilder::new()
			.project(b"app")
			.segment(b"module", b"main")
			.build();
		let child = MonikerBuilder::new()
			.project(b"app")
			.segment(b"module", b"main")
			.segment(b"class", b"Foo")
			.build();
		assert!(parent < child);
	}

	#[test]
	fn ord_separates_distinct_projects() {
		let a = MonikerBuilder::new().project(b"app1").build();
		let b = MonikerBuilder::new().project(b"app2").build();
		assert!(a < b);
	}

	#[test]
	fn from_encoded_roundtrip() {
		let m = MonikerBuilder::new()
			.project(b"pj")
			.segment(b"path", b"foo")
			.build();
		let bytes = m.clone().into_encoded();
		let m2 = Moniker::from_encoded(bytes).unwrap();
		assert_eq!(m, m2);
		assert!(Moniker::from_encoded(vec![99u8; 5]).is_err());
	}

	#[cfg(feature = "serde")]
	#[test]
	fn serde_rejects_invalid_encoded_moniker() {
		let err = serde_json::from_str::<Moniker>(r#"{"bytes":[2,0,0]}"#).unwrap_err();
		assert!(err.to_string().contains("project must not be empty"));
	}

	#[cfg(feature = "serde")]
	#[test]
	fn serde_roundtrips_valid_moniker() {
		let m = MonikerBuilder::new()
			.project(b"pj")
			.segment(b"path", b"foo")
			.build();
		let json = serde_json::to_string(&m).unwrap();
		let decoded: Moniker = serde_json::from_str(&json).unwrap();
		assert_eq!(decoded, m);
	}

	use proptest::prelude::*;

	proptest! {
		#![proptest_config(ProptestConfig {
			cases: 256,
			..ProptestConfig::default()
		})]

		#[test]
		fn moniker_from_encoded_never_panics(bytes in proptest::collection::vec(any::<u8>(), 0..4096)) {
			if let Ok(m) = Moniker::from_encoded(bytes.clone()) {
				prop_assert_eq!(m.as_encoded(), bytes.as_slice());
				let m2 = Moniker::from_encoded(m.as_encoded().to_vec())
					.expect("validated encoding must re-parse");
				prop_assert_eq!(m, m2);
			}
		}

		#[test]
		fn moniker_view_and_owned_agree(bytes in proptest::collection::vec(any::<u8>(), 0..4096)) {
			let owned = Moniker::from_encoded(bytes.clone()).is_ok();
			let view = MonikerView::from_encoded(&bytes).is_ok();
			prop_assert_eq!(owned, view);
		}
	}
}