Lo que un ámbito no comprueba
Un ámbito valida la forma del argumento y nada más. Las cinco cosas que quedan fuera, por qué la validación de contenido es siempre tuya, y el patrón que la hace imposible de olvidar.
Las cinco
Forma frente a intención
La distinción que ordena las cinco, y es la misma del nivel 17.5 sobre serde:
- La forma la comprueban el ámbito y la deserialización: que el argumento sea una ruta, que caiga dentro de la lista, que el tipo encaje.
- La intención no la comprueba nadie salvo tú: que esa ruta sea un proyecto y no una foto, que el proyecto exista, que no esté bloqueado, que el usuario esté en un estado donde eso tenga sentido.
Todo lo que un ámbito hace es de la primera clase. Y por eso un comando bien diseñado —nivel 14.4— no depende del ámbito para su corrección: el ámbito es una red, no la lógica.
El patrón que lo hace imposible de olvidar
La validación en el cuerpo del comando se olvida, porque es una línea que se puede no escribir y nada la echa de menos. La forma de que no se pueda olvidar es la del nivel 14.4 llevada al final: que el tipo del argumento no pueda representar un valor inválido.
// Un identificador que solo se construye validandopub struct ProyectoId(String);impl ProyectoId { pub fn nuevo(s: &str) -> Result<Self, ProyectoError> { // solo letras, números y guiones; longitud acotada if s.is_empty() || s.len() > 64 || !s.chars().all(|c| c.is_ascii_alphanumeric() || c == '-') { return Err(ProyectoError::IdInvalido); } Ok(ProyectoId(s.to_string())) } pub fn como_fichero(&self) -> String { format!("{}.json", self.0) }}// Y la deserialización pasa por ahí, así que un valor inválido// NO LLEGA AL COMANDO: falla en el tramo 6 del recorridoimpl<'de> serde::Deserialize<'de> for ProyectoId { … }Con eso, tres cosas se vuelven ciertas a la vez:
- Un identificador con puntos o barras no existe como valor, así que la travesía del nivel 30.2 no se puede ni expresar.
- La validación ocurre en la frontera, no en el cuerpo, así que ningún comando puede olvidarla.
- Y el compilador la exige: cualquier función que reciba un identificador tiene la garantía puesta en el tipo.
Es más trabajo que un if y es la diferencia entre una comprobación que está y una que no puede faltar.
Los dos casos que hay que comprobar siempre
De las cinco, dos merecen ser reflejo en cualquier comando que toque ficheros:
El tamaño, antes de leer a memoria. Dos líneas, y evitan que el frontend agote la memoria del proceso con un fichero grande que está perfectamente dentro del ámbito.
let meta = std::fs::metadata(&ruta)?;if meta.len() > MAXIMO { return Err(ProyectoError::DemasiadoGrande.into()); }Que sea un fichero regular. Un directorio, un enlace, un dispositivo o una tubería con nombre pueden estar dentro del ámbito y comportarse de formas que tu código no espera — leer de una tubería puede bloquearse para siempre.
if !meta.is_file() { return Err(ProyectoError::NoEsUnFichero.into()); }Las dos juntas son cuatro líneas y cierran la mayor parte de lo que el ámbito deja pasar.