From d3a89f5c69ad9b7cb056b67baeab59efba1a7524 Mon Sep 17 00:00:00 2001 From: benev0 Date: Wed, 3 Sep 2025 18:37:05 -0400 Subject: [PATCH 1/4] add metadata.config parsing --- src/lib.rs | 1 + src/metatdata.rs | 634 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 635 insertions(+) create mode 100644 src/metatdata.rs diff --git a/src/lib.rs b/src/lib.rs index 3d95d18..d86f909 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ mod proto; mod tests; pub mod version; +pub mod metatdata; use crate::proto::{signed::Signed, versions::Versions}; use bytes::buf::Buf; diff --git a/src/metatdata.rs b/src/metatdata.rs new file mode 100644 index 0000000..6baace6 --- /dev/null +++ b/src/metatdata.rs @@ -0,0 +1,634 @@ +use std::fmt::{self, Display}; +use serde::de::{Deserialize, Visitor}; +use serde::{de, ser}; +use serde::de::{DeserializeSeed, MapAccess, SeqAccess}; + +// Errors --------------------------------------------------------------------- +pub type Result = std::result::Result; + +#[derive(Debug)] +pub enum Error { + Message(String), + Eof, + Syntax, + ExpectedBoolean, + ExpectedString, + ExpectedStringTerminal, + ExpectedArray, + ExpectedArrayEnd, + TrailingCharacters, + ExpectedArrayComma, + ExpectedTuple, + ExpectedTupleEnd, + ExpectedMap, + ExpectedMapEnd, + ExpectedMapComma, +} + +impl ser::Error for Error { + fn custom(msg: T) -> Self { + Error::Message(msg.to_string()) + } +} + +impl de::Error for Error { + fn custom(msg: T) -> Self { + Error::Message(msg.to_string()) + } +} + +impl Display for Error { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::Message(msg) => formatter.write_str(msg), + Error::Eof => formatter.write_str("unexpected end of input"), + Error::Syntax => formatter.write_str("invalid syntax"), + Error::ExpectedBoolean => formatter.write_str("expected boolean (true|false)"), + Error::ExpectedString => formatter.write_str("expected string: (<<\"string\">>|<<\"string\"/uft-8>>)"), + Error::ExpectedStringTerminal => formatter.write_str("expected end of string (\">>|\"/uft-8>>)"), + Error::ExpectedArray => formatter.write_str("expected array"), + Error::ExpectedArrayEnd => formatter.write_str("expected end of array (])"), + Error::TrailingCharacters => formatter.write_str("parse finished with content in the buffer"), + Error::ExpectedArrayComma => formatter.write_str("expected array comma"), + Error::ExpectedTupleEnd => formatter.write_str("expected tuple to end"), + Error::ExpectedTuple => formatter.write_str("expected tuple"), + Error::ExpectedMapComma => formatter.write_str("expected map comma"), + Error::ExpectedMap => formatter.write_str("expected map"), + Error::ExpectedMapEnd => formatter.write_str("expected map end (})"), + } + } +} + +impl std::error::Error for Error {} + + +// Deserialization ------------------------------------------------------------ + +pub struct Deserializer<'de> { + input: &'de str, +} + +impl<'de> Deserializer<'de> { + pub fn from_str(input: &'de str) -> Self { + Self { input: input } + } +} + +pub fn from_str<'a, T>(s: &'a str) -> Result +where + T: Deserialize<'a>, +{ + let mut deserializer = Deserializer::from_str(s); + let t = T::deserialize(&mut deserializer)?; + + if deserializer.input.is_empty() { + Ok(t) + } else { + Err(Error::TrailingCharacters) + } +} + +impl<'de> Deserializer<'de> { + fn peek_char(&mut self) -> Result { + self.input.chars().next().ok_or(Error::Eof) + } + + fn next_char(& mut self) -> Result{ + let ch = self.peek_char()?; + self.input = &self.input[ch.len_utf8()..]; + Ok(ch) + } + + fn parse_bool(&mut self) -> Result { + if self.input.starts_with("true") { + self.input = &self.input["true".len()..]; + Ok(true) + } else if self.input.starts_with("false") { + self.input = &self.input["false".len()..]; + Ok(false) + } else { + Err(Error::ExpectedBoolean) + } + } + + fn starts_with_read(&mut self, other: &str) -> bool { + let starts = self.input.starts_with(other); + if starts { + self.input = &self.input[other.len()..] + } + starts + } + + fn clear_whitespace(&mut self) -> Result { + let mut ch = self.peek_char()?; + while ch.is_whitespace() { + self.next_char()?; + ch = self.peek_char()?; + } + Ok(ch) + } + + fn parse_string(&mut self) -> Result<&'de str> { + if !self.starts_with_read("<<\"") { + return Err(Error::ExpectedString); + } + + match self.input.find('"') { + Some(len) => { + let s = &self.input[..len]; + let save = self.input; + self.input = &self.input[len..]; + if self.starts_with_read("\">>") { + Ok(s) + } else if self.starts_with_read("\"/utf-8>>") { + Ok(s) + } else { + self.input = save; + Err(Error::ExpectedStringTerminal) + } + } + None => Err(Error::Eof), + } + } +} + + +impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { + type Error = Error; + + fn deserialize_any(self, visitor: V) -> Result + where + V: Visitor<'de> + { + match self.peek_char()? { + 't' | 'f' => self.deserialize_bool(visitor), + '<' => self.deserialize_str(visitor), + '{' => self.deserialize_tuple(2, visitor), + _ => Err(Error::Syntax), + } + } + + fn deserialize_bool(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_bool(self.parse_bool()?) + } + + fn deserialize_string(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_str(visitor) + } + + fn deserialize_str(self, visitor: V) -> Result + where + V: Visitor<'de> { + visitor.visit_borrowed_str(self.parse_string()?) + } + + fn deserialize_i8(self, _visitor: V) -> Result + where + V: Visitor<'de> { + unimplemented!() + } + + fn deserialize_i16(self, _visitor: V) -> Result + where + V: Visitor<'de> { + unimplemented!() + } + + fn deserialize_i32(self, _visitor: V) -> Result + where + V: Visitor<'de> { + unimplemented!() + } + + fn deserialize_i64(self, _visitor: V) -> Result + where + V: Visitor<'de> { + unimplemented!() + } + + fn deserialize_u8(self, _visitor: V) -> Result + where + V: Visitor<'de> { + unimplemented!() + } + + fn deserialize_u16(self, _visitor: V) -> Result + where + V: Visitor<'de> { + unimplemented!() + } + + fn deserialize_u32(self, _visitor: V) -> Result + where + V: Visitor<'de> { + unimplemented!() + } + + fn deserialize_u64(self, _visitor: V) -> Result + where + V: Visitor<'de> { + unimplemented!() + } + + fn deserialize_f32(self, _visitor: V) -> Result + where + V: Visitor<'de> { + unimplemented!() + } + + fn deserialize_f64(self, _visitor: V) -> Result + where + V: Visitor<'de> { + unimplemented!() + } + + fn deserialize_char(self, _visitor: V) -> Result + where + V: Visitor<'de> { + unimplemented!() + } + + fn deserialize_bytes(self, _visitor: V) -> Result + where + V: Visitor<'de> { + unimplemented!() + } + + fn deserialize_byte_buf(self, _visitor: V) -> Result + where + V: Visitor<'de> { + unimplemented!() + } + + fn deserialize_option(self, _visitor: V) -> Result + where + V: Visitor<'de> { + unimplemented!() + } + + fn deserialize_unit(self, _visitor: V) -> Result + where + V: Visitor<'de> { + unimplemented!() + } + + fn deserialize_unit_struct( + self, + _name: &'static str, + _visitor: V, + ) -> Result + where + V: Visitor<'de> { + unimplemented!() + } + + fn deserialize_newtype_struct( + self, + _name: &'static str, + visitor: V, + ) -> Result + where + V: Visitor<'de> { + visitor.visit_newtype_struct(self) + } + + fn deserialize_seq(self, visitor: V) -> Result + where + V: Visitor<'de> { + self.clear_whitespace()?; + + if self.next_char()? == '[' { + self.clear_whitespace()?; + + let value = visitor.visit_seq(CommaSeparated::new(self, ']'))?; + if self.next_char()? == ']' { + Ok(value) + } else { + Err(Error::ExpectedArrayEnd) + } + } else { + Err(Error::ExpectedArray) + } + } + + fn deserialize_tuple(self, len: usize, visitor: V) -> Result + where + V: Visitor<'de> { + if self.next_char()? == '{' { + let value = visitor.visit_seq(CommaSeparatedFixed::new(self, len))?; + + self.clear_whitespace()?; + + if self.next_char()? == '}' { + Ok(value) + } + else { + Err(Error::ExpectedTupleEnd) + } + } else { + Err(Error::ExpectedTuple) + } + } + + fn deserialize_tuple_struct( + self, + _name: &'static str, + len: usize, + visitor: V, + ) -> Result + where + V: Visitor<'de> { + self.deserialize_tuple(len, visitor) + } + + fn deserialize_map(self, visitor: V) -> Result + where + V: Visitor<'de> { + if self.next_char()? == '[' { + let value = visitor.visit_map(CommaSeparated::new(self, ']'))?; + if self.next_char()? == ']' { + Ok(value) + } else { + Err(Error::ExpectedMapEnd) + } + } else { + Err(Error::ExpectedMap) + } + } + + fn deserialize_struct( + self, + _name: &'static str, + _fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de> { + self.deserialize_map(visitor) + } + + fn deserialize_enum( + self, + _name: &'static str, + _variants: &'static [&'static str], + _visitor: V, + ) -> Result + where + V: Visitor<'de> { + unimplemented!() + } + + fn deserialize_identifier(self, visitor: V) -> Result + where + V: Visitor<'de> { + self.deserialize_str(visitor) + } + + fn deserialize_ignored_any(self, visitor: V) -> Result + where + V: Visitor<'de> { + self.deserialize_any(visitor) + } +} + +// sequences ------------------------------------------------------------------ + +struct CommaSeparated<'a, 'de: 'a> { + de: &'a mut Deserializer<'de>, + first: bool, + end: char, +} + +impl<'a, 'de> CommaSeparated<'a, 'de> { + fn new(de: &'a mut Deserializer<'de>, end: char) -> Self { + CommaSeparated { + de, + first: true, + end: end + } + } +} + +impl<'de, 'a> SeqAccess<'de> for CommaSeparated<'a, 'de> { + type Error = Error; + + fn next_element_seed(&mut self, seed: T) -> Result> + where + T: DeserializeSeed<'de>, + { + if self.de.clear_whitespace()? == self.end { + return Ok(None); + } + + if !self.first && self.de.next_char()? != ',' { + return Err(Error::ExpectedArrayComma); + } + + self.de.clear_whitespace()?; + + self.first = false; + seed.deserialize(&mut *self.de).map(Some) + } +} + +// tuples --------------------------------------------------------------------- + +struct CommaSeparatedFixed<'a, 'de: 'a> { + de: &'a mut Deserializer<'de>, + first: bool, + size: usize, + seen: usize, +} + +impl<'a, 'de> CommaSeparatedFixed<'a, 'de> { + fn new(de: &'a mut Deserializer<'de>, size: usize) -> Self { + CommaSeparatedFixed { + de, + first: true, + size: size, + seen: 0, + } + } +} + +impl<'de, 'a> SeqAccess<'de> for CommaSeparatedFixed<'a, 'de> { + type Error = Error; + + fn next_element_seed(&mut self, seed: T) -> Result> + where + T: DeserializeSeed<'de>, + { + let ch = self.de.clear_whitespace()?; + + if self.seen == self.size && ch == '}' { + return Ok(None); + } else if self.seen == self.size { + return Err(Error::ExpectedTupleEnd); + } else if !self.first && self.de.next_char()? != ',' { + return Err(Error::ExpectedArrayComma); + } + + self.de.clear_whitespace()?; + + self.first = false; + self.seen += 1; + seed.deserialize(&mut *self.de).map(Some) + } +} + +// maps ----------------------------------------------------------------------- + +impl<'de, 'a> MapAccess<'de> for CommaSeparated<'a, 'de> { + type Error = Error; + + fn next_key_seed(&mut self, seed: T) -> Result> + where + T: DeserializeSeed<'de>, + { + if self.de.clear_whitespace()? == ']' { + return Ok(None); + } + + if !self.first && self.de.next_char()? != ',' { + return Err(Error::ExpectedArrayComma); + } + + self.de.clear_whitespace()?; + + if self.de.next_char()? != '{' { + return Err(Error::ExpectedTuple); + } + + self.de.clear_whitespace()?; + + self.first = false; + seed.deserialize(&mut *self.de).map(Some) + } + + fn next_value_seed(&mut self, seed: V) -> Result + where + V: DeserializeSeed<'de>, + { + self.de.clear_whitespace()?; + + if self.de.next_char()? != ',' { + return Err(Error::ExpectedMapComma); + } + + self.de.clear_whitespace()?; + + let val = seed.deserialize(&mut *self.de); + + self.de.clear_whitespace()?; + + if self.de.next_char()? != '}' { + return Err(Error::ExpectedTupleEnd) + } + + val + } +} + +// tests ---------------------------------------------------------------------- + +#[cfg(test)] +mod tests { + use crate::metatdata::from_str; + + #[derive(serde::Deserialize, PartialEq, Debug)] + struct Requirement<'a> { + app: &'a str, + optional: bool, + requirement: &'a str, + } + + #[test] + fn test_true() { + assert_eq!(true, from_str::("true").unwrap()) + } + + #[test] + fn test_false() { + assert_eq!(false, from_str::("false").unwrap()) + } + + #[test] + fn test_string() { + let s = "<<\"basic string\">>"; + let expected = "basic string"; + assert_eq!(expected, from_str::<&str>(s).unwrap()) + } + + #[test] + fn test_string_utf() { + let s = "<<\"basic string\"/utf-8>>"; + let expected = "basic string"; + assert_eq!(expected, from_str::<&str>(s).unwrap()) + } + + #[test] + fn test_pair_no_white() { + let s = r#"{<<"name">>,<<"gleam_erlang">>}"#; + let expected = ("name", "gleam_erlang"); + assert_eq!(expected, from_str::<(&str, &str)>(s).unwrap()) + } + + #[test] + fn test_pair_normal_white() { + let s = r#"{<<"name">>, <<"gleam_erlang">>}"#; + let expected = ("name", "gleam_erlang"); + assert_eq!(expected, from_str::<(&str, &str)>(s).unwrap()) + } + + #[test] + + fn test_pair_excess_white() { + let s = r#"{ <<"name">> , <<"gleam_erlang">> }"#; + let expected = ("name", "gleam_erlang"); + assert_eq!(expected, from_str::<(&str, &str)>(s).unwrap()) + } + + #[test] + fn test_requirement() { + let s = + r#"[ + {<<"app">>, <<"gleam_stdlib">>}, + {<<"optional">>, false}, + {<<"requirement">>, <<">= 0.33.0 and < 2.0.0">>} + ]"#; + let expected = Requirement { + app: "gleam_stdlib", + optional: false, + requirement: ">= 0.33.0 and < 2.0.0", + }; + + assert_eq!(expected, from_str(s).unwrap()); + } + + #[test] + fn test_seq() { + let s = r#"[true,false,true]"#; + let expected = vec![true, false, true]; + assert_eq!(expected, from_str::>(s).unwrap()); + } + + #[test] + fn test_seq_normal_white() { + let s = r#"[true, false, true]"#; + let expected = vec![true, false, true]; + assert_eq!(expected, from_str::>(s).unwrap()); + } + + #[test] + fn test_seq_excess_white() { + let s = r#"[ true , false , true ]"#; + let expected = vec![true, false, true]; + assert_eq!(expected, from_str::>(s).unwrap()); + } +} \ No newline at end of file From 6eb67ce60151f2acb05d785b2d20a5988aa7c626 Mon Sep 17 00:00:00 2001 From: benev0 Date: Wed, 3 Sep 2025 18:38:59 -0400 Subject: [PATCH 2/4] eol to eof --- src/metatdata.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metatdata.rs b/src/metatdata.rs index 6baace6..252e5e5 100644 --- a/src/metatdata.rs +++ b/src/metatdata.rs @@ -631,4 +631,4 @@ mod tests { let expected = vec![true, false, true]; assert_eq!(expected, from_str::>(s).unwrap()); } -} \ No newline at end of file +} From bc0f4b1f7b32282c0a57e1f06453ced30ee0faac Mon Sep 17 00:00:00 2001 From: benev0 Date: Thu, 4 Sep 2025 13:48:41 -0400 Subject: [PATCH 3/4] add optinal fields --- src/metatdata.rs | 78 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 18 deletions(-) diff --git a/src/metatdata.rs b/src/metatdata.rs index 252e5e5..c19da0e 100644 --- a/src/metatdata.rs +++ b/src/metatdata.rs @@ -1,7 +1,8 @@ use std::fmt::{self, Display}; -use serde::de::{Deserialize, Visitor}; -use serde::{de, ser}; -use serde::de::{DeserializeSeed, MapAccess, SeqAccess}; +use serde::de::{self, Deserialize, Visitor, DeserializeSeed, MapAccess, SeqAccess}; + + +// SERDE ---------------------------------------------------------------------- // Errors --------------------------------------------------------------------- pub type Result = std::result::Result; @@ -25,12 +26,6 @@ pub enum Error { ExpectedMapComma, } -impl ser::Error for Error { - fn custom(msg: T) -> Self { - Error::Message(msg.to_string()) - } -} - impl de::Error for Error { fn custom(msg: T) -> Self { Error::Message(msg.to_string()) @@ -266,10 +261,10 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { unimplemented!() } - fn deserialize_option(self, _visitor: V) -> Result + fn deserialize_option(self, visitor: V) -> Result where V: Visitor<'de> { - unimplemented!() + visitor.visit_some(self) } fn deserialize_unit(self, _visitor: V) -> Result @@ -539,14 +534,9 @@ impl<'de, 'a> MapAccess<'de> for CommaSeparated<'a, 'de> { #[cfg(test)] mod tests { - use crate::metatdata::from_str; + use std::collections::HashMap; - #[derive(serde::Deserialize, PartialEq, Debug)] - struct Requirement<'a> { - app: &'a str, - optional: bool, - requirement: &'a str, - } + use crate::metatdata::from_str; #[test] fn test_true() { @@ -594,6 +584,13 @@ mod tests { assert_eq!(expected, from_str::<(&str, &str)>(s).unwrap()) } + #[derive(serde::Deserialize, PartialEq, Debug)] + struct Requirement<'a> { + app: &'a str, + optional: bool, + requirement: &'a str, + } + #[test] fn test_requirement() { let s = @@ -611,6 +608,27 @@ mod tests { assert_eq!(expected, from_str(s).unwrap()); } + #[derive(serde::Deserialize, PartialEq, Debug)] + struct Perhaps<'a> { + q: Option<&'a str> + } + + #[test] + fn test_optional_member_present() { + let s = r#"[{<<"q">>, <<"present">>}]"#; + let expected = Perhaps { q: Some("present") }; + + assert_eq!(expected, from_str(s).unwrap()); + } + + #[test] + fn test_optional_member_not_present() { + let s = r#"[]"#; + let expected = Perhaps { q: None }; + + assert_eq!(expected, from_str(s).unwrap()); + } + #[test] fn test_seq() { let s = r#"[true,false,true]"#; @@ -631,4 +649,28 @@ mod tests { let expected = vec![true, false, true]; assert_eq!(expected, from_str::>(s).unwrap()); } + + #[test] + fn test_seq_empty() { + let s = r#"[]"#; + let expected: Vec = vec![]; + assert_eq!(expected, from_str::>(s).unwrap()); + } + + #[test] + fn test_map_empty() { + let s = r#"[]"#; + let expected: HashMap<&str, bool> = HashMap::new(); + assert_eq!(expected, from_str::>(s).unwrap()); + } + + #[test] + fn test_map() { + let s = r#"[{<<"hello">>, true}, {<<"goodbye">>, false}]"#; + let mut expected: HashMap<&str, bool> = HashMap::new(); + expected.insert("hello", true); + expected.insert("goodbye", false); + + assert_eq!(expected, from_str::>(s).unwrap()); + } } From d4d0d87ed0089448612e0e33f56db6fdb2f3a245 Mon Sep 17 00:00:00 2001 From: benev0 Date: Fri, 5 Sep 2025 18:18:29 -0400 Subject: [PATCH 4/4] add (unverified) archive read methods --- Cargo.toml | 6 ++ src/metatdata.rs | 180 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 186 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index f7a79f9..450082b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,8 @@ regex = "1.3" bytes = "1" # gzip (de)compression flate2 = "1.0" +# tar archive reading +tar = "0" # RSA signature and SHA256 checksum verification ring = "0.17" # PEM -> DER conversion @@ -40,6 +42,10 @@ http-auth-basic = "0.3" base16 = { version = "0.2", features = ["alloc"] } # Protobuf runtime prost = "0.13.5" +# utf-8 only paths +camino = "1" +# computation of archive hashes +sha2 = "0.10.9" [dev-dependencies] # HTTP client diff --git a/src/metatdata.rs b/src/metatdata.rs index c19da0e..e1c29b2 100644 --- a/src/metatdata.rs +++ b/src/metatdata.rs @@ -1,7 +1,134 @@ +use std::collections::HashMap; use std::fmt::{self, Display}; +use std::io::{Read, Seek}; +use std::marker::PhantomData; use serde::de::{self, Deserialize, Visitor, DeserializeSeed, MapAccess, SeqAccess}; +use tar::Archive; +use camino::Utf8Path; +use sha2::{Sha256, Digest}; +use crate::version::{Range, Version}; +use crate::{ Dependency, Release}; + + +// API ------------------------------------------------------------------------ + +#[derive(Debug)] +pub enum FileAPIError { + Silent, + SerdeError(Error), +} + +impl Display for FileAPIError { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + match self { + FileAPIError::Silent => formatter.write_str("error"), + FileAPIError::SerdeError(err) => err.fmt(formatter), + } + } +} + +impl std::error::Error for FileAPIError {} + + +#[derive(serde::Deserialize)] +struct RawDependency { + app: Option, + optional: bool, + requirement: String, + repository: Option, +} + +pub fn read_archive_release(mut archive: Archive) -> std::result::Result, FileAPIError> + where R: Read + Seek, { + let meta_path = Utf8Path::new("metadata.config"); + let mut deps = Vec::new(); + + for archive_entry in archive.entries().or(Err(FileAPIError::Silent))? { + let mut file = archive_entry.or(Err(FileAPIError::Silent))?; + let path = file.header().path().or(Err(FileAPIError::Silent))?; + let path = path.as_ref(); + + if path == meta_path { + let _ = file.read_to_end(&mut deps); + } + } + + if deps.is_empty() { + return Err(FileAPIError::Silent); + } + + let raw = read_metadata(std::str::from_utf8(&deps).or(Err(FileAPIError::Silent))?)?; + let checksum = compute_outer_checksum(archive)?; + + Ok(Release { + version: Version::parse(&raw.version).or(Err(FileAPIError::Silent))?, + requirements: raw.requirements, + retirement_status: None, + outer_checksum: checksum, + meta: (), + }) +} + +pub fn compute_outer_checksum(archive: Archive) -> std::result::Result, FileAPIError> + where R: Read + Seek, { + let mut buf = Vec::new(); + let mut reader = archive.into_inner(); + reader.rewind().or(Err(FileAPIError::Silent))?; + reader.read_to_end(&mut buf).or(Err(FileAPIError::Silent))?; + let mut hasher = Sha256::new(); + hasher.update(buf); + Ok(hasher.finalize().to_vec()) +} + + +pub struct RawRelease { + pub version: String, + pub requirements: HashMap, +} + +fn read_metadata(file_content: &str) -> std::result::Result { + let mut rest = file_content; + let mut version = None; + let mut requirements = None; + + while let Some((data, remainder)) = rest.split_once('.') { + rest = remainder; + let mut de = Deserializer::from_str(data); + let key: &str = KeyOnly::new().deserialize(&mut de).unwrap(); + + match key { + "version" => { + version = Some(from_str::<(&str, String)>(data).or(Err(FileAPIError::Silent))?); + }, + "requirements" => { + requirements = Some(from_str::<(&str, Vec<(String, RawDependency)>)>(data).or(Err(FileAPIError::Silent))?); + }, + _ => (), + } + } + + Ok(RawRelease { + version: version.ok_or(FileAPIError::Silent)?.1, + requirements: + requirements.ok_or(FileAPIError::Silent)? + .1 + .into_iter() + .map(|(dep_name, dep)| -> std::result::Result<(String, Dependency), FileAPIError> { + Ok(( + dep_name, + Dependency { + requirement: Range::new(dep.requirement).or(Err(FileAPIError::Silent))?, + optional: dep.optional, + app: dep.app, + repository: dep.repository, + } + )) + }).collect::, FileAPIError>>()?, + }) +} + // SERDE ---------------------------------------------------------------------- // Errors --------------------------------------------------------------------- @@ -530,6 +657,59 @@ impl<'de, 'a> MapAccess<'de> for CommaSeparated<'a, 'de> { } } +// key only ------------------------------------------------------------------- + +pub struct KeyOnly { + marker: PhantomData, +} + +impl KeyOnly { + pub fn new() -> Self { + Self { marker: PhantomData, } + } +} + +impl<'de, T> Visitor<'de> for KeyOnly +where + T: Deserialize<'de>, +{ + type Value = T; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "expected a sequence with at least one element") + } + + fn visit_seq(self, mut seq: A) -> std::result::Result + where + A: SeqAccess<'de>, { + + let first = match seq.next_element()? { + Some(first) => first, + None => { + return Err(de::Error::invalid_length(0, &self)); + }, + }; + + while let Some(_) = seq.next_element::()? {} + + Ok(first) + } +} + +impl<'de, T> DeserializeSeed<'de> for KeyOnly +where + T: Deserialize<'de>, +{ + type Value = T; + + fn deserialize(self, deserializer: D) -> std::result::Result + where + D: de::Deserializer<'de>, + { + deserializer.deserialize_seq(self) + } +} + // tests ---------------------------------------------------------------------- #[cfg(test)]