mirror of
https://codeberg.org/icewind/vdf-reader.git
synced 2026-06-03 10:04:08 +02:00
add support for deserializing a map with int keys as a sequence
This commit is contained in:
parent
1a37f7e866
commit
b928f85df6
14 changed files with 226 additions and 9 deletions
37
examples/steam_folders.rs
Normal file
37
examples/steam_folders.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
use miette::{Context, IntoDiagnostic, Result};
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use std::env::args;
|
||||
use std::fs::read_to_string;
|
||||
use vdf_reader::from_str;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
struct LibraryFolders {
|
||||
#[serde(rename = "libraryfolders")]
|
||||
folders: Vec<LibraryFolder>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
struct LibraryFolder {
|
||||
path: String,
|
||||
label: String,
|
||||
#[serde(rename = "contentid")]
|
||||
content_id: i64,
|
||||
#[serde(rename = "totalsize")]
|
||||
total_size: u64,
|
||||
update_clean_bytes_tally: u64,
|
||||
time_last_update_verified: u64,
|
||||
apps: HashMap<u64, u64>,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let path = args().nth(1).expect("no path provided");
|
||||
let raw = read_to_string(path)
|
||||
.into_diagnostic()
|
||||
.wrap_err("failed to read input")?;
|
||||
let folders: LibraryFolders = from_str(&raw)?;
|
||||
dbg!(folders);
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
use super::Entry;
|
||||
use super::{Entry, Table};
|
||||
use crate::entry::Value;
|
||||
use crate::error::{ParseStringError, UnknownError};
|
||||
use crate::VdfError;
|
||||
use serde::de::{DeserializeSeed, SeqAccess};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -80,3 +81,53 @@ impl<'de> SeqAccess<'de> for ArraySeq {
|
|||
seed.deserialize(next).map(Some)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct TableArraySeq {
|
||||
iter: std::vec::IntoIter<(String, Entry)>,
|
||||
last_key: Option<u64>,
|
||||
}
|
||||
|
||||
impl TableArraySeq {
|
||||
pub(crate) fn new(table: Table) -> Self {
|
||||
// since the tables map doesn't have a stable order, we need to re-sort them to have the keys in order
|
||||
let mut items: Vec<_> = table.into_iter().collect();
|
||||
items.sort_by(|(key_a, _), (key_b, _)| key_a.cmp(key_b));
|
||||
TableArraySeq {
|
||||
iter: items.into_iter(),
|
||||
last_key: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> SeqAccess<'de> for TableArraySeq {
|
||||
type Error = VdfError;
|
||||
|
||||
fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>, Self::Error>
|
||||
where
|
||||
T: DeserializeSeed<'de>,
|
||||
{
|
||||
let (key, next) = match self.iter.next() {
|
||||
Some(next) => next,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let key: u64 = key.parse().map_err(|_| {
|
||||
VdfError::ParseString(ParseStringError {
|
||||
ty: "u64",
|
||||
value: key,
|
||||
})
|
||||
})?;
|
||||
|
||||
if let Some(last_key) = self.last_key {
|
||||
let expected_key = last_key + 1;
|
||||
if expected_key != key {
|
||||
return Err(VdfError::Other(UnknownError::from(format!(
|
||||
"Invalid array key {key}, expected {expected_key}"
|
||||
))));
|
||||
}
|
||||
}
|
||||
self.last_key = Some(key);
|
||||
|
||||
seed.deserialize(next).map(Some)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@ macro_rules! from_str {
|
|||
);
|
||||
}
|
||||
|
||||
use crate::entry::array::ArraySeq;
|
||||
use crate::entry::array::{ArraySeq, TableArraySeq};
|
||||
use crate::entry::table::TableSeq;
|
||||
use serde::de::{DeserializeSeed, EnumAccess, Error, MapAccess, SeqAccess, VariantAccess, Visitor};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
|
|
@ -614,7 +614,8 @@ impl<'de> Deserializer<'de> for Entry {
|
|||
{
|
||||
match self {
|
||||
Entry::Array(arr) => visitor.visit_seq(ArraySeq::new(arr)),
|
||||
_ => Err(UnknownError::from("array2").into()),
|
||||
Entry::Table(table) => visitor.visit_seq(TableArraySeq::new(table)),
|
||||
_ => Err(UnknownError::from("array").into()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use std::ops::{Deref, DerefMut};
|
|||
/// A table of entries.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize, Default)]
|
||||
#[serde(transparent)]
|
||||
pub struct Table(#[serde(serialize_with = "ordered_map")] HashMap<String, Entry>);
|
||||
pub struct Table(#[serde(serialize_with = "ordered_map")] HashMap<String, Entry>); // todo: switch to a map that maintains item order
|
||||
|
||||
impl From<HashMap<String, Entry>> for Table {
|
||||
fn from(value: HashMap<String, Entry>) -> Self {
|
||||
|
|
@ -118,6 +118,15 @@ impl Table {
|
|||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for Table {
|
||||
type Item = (String, Entry);
|
||||
type IntoIter = hash_map::IntoIter<String, Entry>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Table> for Entry {
|
||||
fn from(table: Table) -> Self {
|
||||
Entry::Table(table)
|
||||
|
|
|
|||
10
src/error.rs
10
src/error.rs
|
|
@ -181,6 +181,16 @@ impl From<&str> for UnknownError {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<String> for UnknownError {
|
||||
fn from(value: String) -> Self {
|
||||
UnknownError {
|
||||
error: value,
|
||||
err_span: (0..0).into(),
|
||||
src: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A token that wasn't expected was found while parsing
|
||||
#[derive(Debug, Clone, Diagnostic)]
|
||||
#[diagnostic(code(vmt_reader::unexpected_token))]
|
||||
|
|
|
|||
54
src/serde.rs
54
src/serde.rs
|
|
@ -1,5 +1,5 @@
|
|||
use crate::entry::{string_is_array, Entry, ParseItem};
|
||||
use crate::error::{ExpectToken, NoValidTokenError, ResultExt, SerdeParseError};
|
||||
use crate::error::{ExpectToken, NoValidTokenError, ResultExt, SerdeParseError, UnknownError};
|
||||
use crate::tokenizer::{SpannedToken, Tokenizer};
|
||||
use crate::{Token, VdfError};
|
||||
use logos::Span;
|
||||
|
|
@ -348,7 +348,17 @@ impl<'de> de::Deserializer<'de> for &'_ mut Deserializer<'de> {
|
|||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
let token = self.peek().expect_token(STRING_ITEMS, self.source())?;
|
||||
let token = self.peek().expect_token(SEQ_START_ITEMS, self.source())?;
|
||||
|
||||
if token.token == Token::GroupStart {
|
||||
// we allow deserializing a map of consecutive int keys as a seq
|
||||
let _ = self.next();
|
||||
return visitor.visit_seq(MapSeqWalker {
|
||||
table: TableWalker::new(self, false),
|
||||
last_key: None,
|
||||
});
|
||||
}
|
||||
|
||||
let value_str = &self.source()[token.span.clone()];
|
||||
if (value_str.starts_with("\"[") && value_str.ends_with("]\""))
|
||||
|| (value_str.starts_with("\"{") && value_str.ends_with("}\""))
|
||||
|
|
@ -555,6 +565,15 @@ impl<'de> MapAccess<'de> for TableWalker<'de, '_> {
|
|||
}
|
||||
}
|
||||
|
||||
/// STRING_ITEMS and GroupStart
|
||||
const SEQ_START_ITEMS: &[Token] = &[
|
||||
Token::Item,
|
||||
Token::QuotedItem,
|
||||
Token::Statement,
|
||||
Token::QuotedStatement,
|
||||
Token::GroupStart,
|
||||
];
|
||||
|
||||
struct SeqWalker<'source, 'a> {
|
||||
table: TableWalker<'source, 'a>,
|
||||
key: Cow<'source, str>,
|
||||
|
|
@ -674,6 +693,37 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
struct MapSeqWalker<'source, 'a> {
|
||||
table: TableWalker<'source, 'a>,
|
||||
last_key: Option<u64>,
|
||||
}
|
||||
|
||||
impl<'de> SeqAccess<'de> for MapSeqWalker<'de, '_> {
|
||||
type Error = VdfError;
|
||||
|
||||
fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>, Self::Error>
|
||||
where
|
||||
T: DeserializeSeed<'de>,
|
||||
{
|
||||
let Some(key) = self.table.next_key()? else {
|
||||
return Ok(None);
|
||||
};
|
||||
if let Some(last_key) = self.last_key {
|
||||
let expected_key = last_key + 1;
|
||||
if expected_key != key {
|
||||
return Err(VdfError::Other(UnknownError::from(format!(
|
||||
"Invalid array key {key}, expected {expected_key}"
|
||||
)))
|
||||
.with_source_span(self.table.de.last_span.clone(), self.table.de.source()));
|
||||
}
|
||||
}
|
||||
|
||||
self.last_key = Some(key);
|
||||
|
||||
Ok(Some(self.table.next_value_seed(seed)?))
|
||||
}
|
||||
}
|
||||
|
||||
struct Enum<'a, 'de: 'a> {
|
||||
de: &'a mut Deserializer<'de>,
|
||||
enclosed: bool,
|
||||
|
|
|
|||
6
tests/data/maparray.vdf
Normal file
6
tests/data/maparray.vdf
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
"MapArray"
|
||||
{
|
||||
"0" "foo"
|
||||
"1" "bar"
|
||||
"2" "asd"
|
||||
}
|
||||
6
tests/errors/invalidmaparraykeys.vdf
Normal file
6
tests/errors/invalidmaparraykeys.vdf
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
"MapArray"
|
||||
{
|
||||
"0" "foo"
|
||||
"1" "bar"
|
||||
"3" "asd"
|
||||
}
|
||||
|
|
@ -69,6 +69,7 @@ enum Expected {
|
|||
EnumInMap {
|
||||
foo: EnumInMap,
|
||||
},
|
||||
MapArray(Vec<String>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
|
|
@ -211,6 +212,8 @@ struct GameList {
|
|||
#[test_case("tests/errors/concrete.vmt")]
|
||||
#[test_case("tests/errors/novalue.vdf")]
|
||||
#[test_case("tests/errors/serde_array_type.vdf")]
|
||||
#[test_case("tests/data/maparray.vdf")]
|
||||
#[test_case("tests/errors/invalidmaparraykeys.vdf")]
|
||||
fn test_serde(path: &str) {
|
||||
let raw = read_to_string(path).unwrap();
|
||||
match from_str::<Expected>(&raw) {
|
||||
|
|
@ -228,6 +231,7 @@ fn test_serde(path: &str) {
|
|||
#[test_case("tests/data/concrete.vmt")]
|
||||
#[test_case("tests/data/DialogConfigOverlay_1280x720.vdf")]
|
||||
#[test_case("tests/data/serde_array_type.vdf")]
|
||||
#[test_case("tests/data/maparray.vdf")]
|
||||
fn test_serde_table(path: &str) {
|
||||
let raw = read_to_string(path).unwrap();
|
||||
match from_str::<Table>(&raw) {
|
||||
|
|
@ -247,6 +251,7 @@ fn test_serde_table(path: &str) {
|
|||
#[test_case("tests/data/messy.vdf")]
|
||||
#[test_case("tests/data/DialogConfigOverlay_1280x720.vdf")]
|
||||
#[test_case("tests/data/serde_array_type.vdf")]
|
||||
#[test_case("tests/data/maparray.vdf")]
|
||||
fn test_serde_from_table(path: &str) {
|
||||
let raw = read_to_string(path).unwrap();
|
||||
let result = Table::load_from_str(&raw).unwrap();
|
||||
|
|
|
|||
11
tests/snapshots/serde__table__tests__data__maparray.vdf.snap
Normal file
11
tests/snapshots/serde__table__tests__data__maparray.vdf.snap
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
source: tests/serde.rs
|
||||
expression: result
|
||||
---
|
||||
{
|
||||
"MapArray": {
|
||||
"0": "foo",
|
||||
"1": "bar",
|
||||
"2": "asd",
|
||||
},
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
source: tests/serde.rs
|
||||
expression: material
|
||||
---
|
||||
MapArray([
|
||||
"foo",
|
||||
"bar",
|
||||
"asd",
|
||||
])
|
||||
9
tests/snapshots/serde__tests__data__maparray.vdf.snap
Normal file
9
tests/snapshots/serde__tests__data__maparray.vdf.snap
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
source: tests/serde.rs
|
||||
expression: result
|
||||
---
|
||||
MapArray([
|
||||
"foo",
|
||||
"bar",
|
||||
"asd",
|
||||
])
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
source: tests/serde.rs
|
||||
expression: out
|
||||
---
|
||||
vmt_reader::unexpected_token
|
||||
|
||||
× Invalid array key 3, expected 2
|
||||
╭─[5:3]
|
||||
4 │ "1" "bar"
|
||||
5 │ "3" "asd"
|
||||
· ─┬─
|
||||
· ╰── Invalid array key 3, expected 2
|
||||
6 │ }
|
||||
╰────
|
||||
|
|
@ -4,11 +4,10 @@ expression: out
|
|||
---
|
||||
vmt_parser::unknown_variant
|
||||
|
||||
× Unknown variant "Resource" expected on of expected on of Types, LightmappedGeneric, Resource/specificPanel.res, UserConfigData, Sprite, EnumInMap
|
||||
× Unknown variant "Resource" expected on of expected on of Types, LightmappedGeneric, Resource/specificPanel.res, UserConfigData, Sprite, EnumInMap, MapArray
|
||||
╭─[1:1]
|
||||
1 │ "Resource"
|
||||
· ─────┬────
|
||||
· ╰── expected on of Types, LightmappedGeneric, Resource/specificPanel.res, UserConfigData, Sprite, EnumInMap
|
||||
· ╰── expected on of Types, LightmappedGeneric, Resource/specificPanel.res, UserConfigData, Sprite, EnumInMap, MapArray
|
||||
2 │ {
|
||||
╰────
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue