code-moniker-core 0.2.0

Core symbol-graph types and per-language extractors for code-moniker (pure Rust, no pgrx). Consumed by the CLI and the PostgreSQL extension.
Documentation
use super::encoding::{HEADER_FIXED_LEN, VERSION, write_u16};
use super::{Moniker, MonikerView};

const MAX_COMPONENT_LEN: usize = u16::MAX as usize;

#[derive(Default, Debug)]
pub struct MonikerBuilder {
	project: Vec<u8>,
	segments: Vec<(Vec<u8>, Vec<u8>)>,
}

impl MonikerBuilder {
	pub fn new() -> Self {
		Self::default()
	}

	pub fn from_view(view: MonikerView<'_>) -> Self {
		let mut b = Self::new();
		b.project(view.project());
		for seg in view.segments() {
			b.segment(seg.kind, seg.name);
		}
		b
	}

	pub fn truncate(&mut self, len: usize) {
		self.segments.truncate(len);
	}

	pub fn project(&mut self, project: &[u8]) -> &mut Self {
		self.project.clear();
		self.project.extend_from_slice(project);
		self
	}

	pub fn segment(&mut self, kind: &[u8], name: &[u8]) -> &mut Self {
		self.segments.push((kind.to_vec(), name.to_vec()));
		self
	}

	pub fn build(&self) -> Moniker {
		assert!(
			self.project.len() <= MAX_COMPONENT_LEN,
			"moniker project longer than u16::MAX bytes ({})",
			self.project.len()
		);
		let mut buf = Vec::with_capacity(self.estimated_size());
		buf.push(VERSION);
		write_u16(&mut buf, self.project.len() as u16);
		buf.extend_from_slice(&self.project);
		for (kind, name) in &self.segments {
			assert!(
				kind.len() <= MAX_COMPONENT_LEN,
				"moniker segment kind longer than u16::MAX bytes ({})",
				kind.len()
			);
			assert!(
				name.len() <= MAX_COMPONENT_LEN,
				"moniker segment name longer than u16::MAX bytes ({})",
				name.len()
			);
			write_u16(&mut buf, kind.len() as u16);
			buf.extend_from_slice(kind);
			write_u16(&mut buf, name.len() as u16);
			buf.extend_from_slice(name);
		}
		Moniker::from_canonical_bytes(buf)
	}

	fn estimated_size(&self) -> usize {
		HEADER_FIXED_LEN
			+ self.project.len()
			+ self
				.segments
				.iter()
				.map(|(k, n)| 2 + k.len() + 2 + n.len())
				.sum::<usize>()
	}
}

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

	#[test]
	fn builder_empty() {
		let m = MonikerBuilder::new().build();
		let v = m.as_view();
		assert_eq!(v.project(), b"");
		assert_eq!(v.segment_count(), 0);
		assert_eq!(v.segments().count(), 0);
	}

	#[test]
	fn builder_with_project_no_segments() {
		let m = MonikerBuilder::new().project(b"my-app").build();
		let v = m.as_view();
		assert_eq!(v.project(), b"my-app");
		assert_eq!(v.segment_count(), 0);
	}

	#[test]
	fn builder_with_segments() {
		let m = MonikerBuilder::new()
			.project(b"my-app")
			.segment(b"module", b"main")
			.segment(b"package", b"com")
			.segment(b"package", b"acme")
			.segment(b"class", b"Foo")
			.build();
		let v = m.as_view();
		assert_eq!(v.segment_count(), 4);
		let segs: Vec<_> = v.segments().collect();
		assert_eq!(
			segs[0],
			Segment {
				kind: b"module",
				name: b"main"
			}
		);
		assert_eq!(
			segs[3],
			Segment {
				kind: b"class",
				name: b"Foo"
			}
		);
	}

	#[test]
	fn builder_method_with_arity_in_name() {
		let m = MonikerBuilder::new()
			.project(b"app")
			.segment(b"class", b"Foo")
			.segment(b"method", b"bar()")
			.segment(b"method", b"bar(2)")
			.build();
		let segs: Vec<_> = m.as_view().segments().collect();
		assert_eq!(segs[1].name, b"bar()");
		assert_eq!(segs[2].name, b"bar(2)");
	}

	#[test]
	fn builder_accepts_max_length_component() {
		let big = vec![b'a'; MAX_COMPONENT_LEN];
		let m = MonikerBuilder::new()
			.project(&big)
			.segment(b"path", &big)
			.build();
		let v = m.as_view();
		assert_eq!(v.project().len(), MAX_COMPONENT_LEN);
		let seg = v.segments().next().unwrap();
		assert_eq!(seg.name.len(), MAX_COMPONENT_LEN);
	}

	#[test]
	#[should_panic(expected = "moniker project longer than u16::MAX bytes")]
	fn builder_panics_on_oversized_project() {
		let oversize = vec![b'a'; MAX_COMPONENT_LEN + 1];
		MonikerBuilder::new().project(&oversize).build();
	}

	#[test]
	#[should_panic(expected = "moniker segment kind longer than u16::MAX bytes")]
	fn builder_panics_on_oversized_segment_kind() {
		let oversize = vec![b'a'; MAX_COMPONENT_LEN + 1];
		MonikerBuilder::new()
			.project(b"app")
			.segment(&oversize, b"x")
			.build();
	}

	#[test]
	#[should_panic(expected = "moniker segment name longer than u16::MAX bytes")]
	fn builder_panics_on_oversized_segment_name() {
		let oversize = vec![b'a'; MAX_COMPONENT_LEN + 1];
		MonikerBuilder::new()
			.project(b"app")
			.segment(b"path", &oversize)
			.build();
	}
}