saltine-caeser 0.3.0

Caeser cipher implementation for the saltine crate.
Documentation
use std::{convert::Infallible, fmt::Debug, marker::PhantomData};

use bon::Builder;
use saltine_core::{
	Cipher,
	CipherSolution,
	CipherSolver,
	alphabet::Alphabet,
	analyze::TextLike,
	character::Character,
	language::{English, Language},
};

#[derive(Builder)]
pub struct CaeserCipher<C: Character = char> {
	#[builder(name = with_alphabet)]
	alphabet: Alphabet<C>,
	#[builder(name = with_shift)]
	shift: usize,
}

impl CaeserCipher<char> {
	pub fn new(shift: usize) -> Self {
		CaeserCipher {
			alphabet: English::alphabet(),
			shift,
		}
	}
}

impl<C: Character> Cipher<C> for CaeserCipher<C> {
	type DecryptionError = Infallible;

	fn encrypt<T: TextLike<C> + ?Sized>(&self, plaintext: &T) -> impl TextLike<C> {
		plaintext.characters().map(|letter| self.alphabet.shift_char(letter, self.shift)).collect::<Vec<_>>()
	}

	fn decrypt<T: TextLike<C> + ?Sized>(&self, ciphertext: &T) -> Result<impl TextLike<C>, Self::DecryptionError> {
		Ok(ciphertext
			.characters()
			.map(|letter| self.alphabet.shift_char(letter, self.alphabet.len() - self.shift))
			.collect::<Vec<_>>())
	}
}

#[derive(Builder, Debug)]
pub struct CaeserCipherSolver<C: Character, L: Language<Character = C>> {
	#[builder(name = with_alphabet)]
	alphabet: Alphabet<C>,

	_phantom: PhantomData<L>,
}

impl CaeserCipherSolver<char, English> {
	pub fn new() -> CaeserCipherSolver<char, English> {
		CaeserCipherSolver::<char, English> {
			alphabet: English::alphabet(),
			_phantom: PhantomData,
		}
	}
}

impl<L: Language> CipherSolver<L> for CaeserCipherSolver<L::Character, L> {
	type Solution = CaeserCipherSolution<L::Character>;

	fn crack<T: TextLike<L::Character> + ?Sized>(&self, ciphertext: &T) -> CipherSolution<CaeserCipherSolution<L::Character>> {
		(0..self.alphabet.len())
			.into_iter()
			.map(|shift| {
				let solver = CaeserCipher::builder().with_alphabet(self.alphabet.clone()).with_shift(shift).build();
				let plaintext = solver.decrypt(ciphertext).unwrap();
				let score = plaintext.chance_of_being::<L>();
				CipherSolution::<L::Character>::new::<CaeserCipherSolution<L::Character>>(
					CaeserCipherSolution {
						plaintext: plaintext.to::<Vec<_>>(),
						shift,
					},
					score,
				)
			})
			.max_by(|s1, s2| s1.score().partial_cmp(&s2.score()).unwrap_or(std::cmp::Ordering::Equal))
			.unwrap()
	}
}

#[derive(Debug)]
pub struct CaeserCipherSolution<C> {
	plaintext: Vec<C>,
	shift: usize,
}

impl<C: Character> CaeserCipherSolution<C> {
	pub fn plaintext(&self) -> &impl TextLike<C> {
		&self.plaintext
	}

	pub fn shift(&self) -> usize {
		self.shift
	}
}