simplify with .into_iter()

This commit is contained in:
2025-07-22 01:33:12 +02:00
parent 66daaad6a7
commit 4a795819cc

View File

@@ -5,7 +5,7 @@ use chrono::{DateTime, FixedOffset};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt; use std::fmt;
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq)]
pub struct Id(pub String); pub struct Id(pub String);
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@@ -287,7 +287,7 @@ pub enum Response {
Entity, Entity,
} }
#[derive(Debug)] #[derive(Debug, PartialEq)]
pub enum DataType { pub enum DataType {
Manga, Manga,
Chapter, Chapter,
@@ -397,7 +397,7 @@ struct ChapterContent {
struct ChapterAttributesContent { struct ChapterAttributesContent {
volume: Option<String>, volume: Option<String>,
chapter: Option<String>, chapter: Option<String>,
title: String, title: Option<String>,
translated_language: String, translated_language: String,
external_url: Option<String>, external_url: Option<String>,
publish_at: String, publish_at: String,
@@ -420,7 +420,7 @@ pub struct Chapter {
pub struct ChapterAttributes { pub struct ChapterAttributes {
pub volume: Option<f32>, pub volume: Option<f32>,
pub chapter: Option<f32>, pub chapter: Option<f32>,
pub title: String, pub title: Option<String>,
pub translated_language: Language, pub translated_language: Language,
pub external_url: Option<String>, pub external_url: Option<String>,
pub published_at: DateTime<FixedOffset>, pub published_at: DateTime<FixedOffset>,
@@ -540,8 +540,8 @@ struct ContentRelationship {
#[derive(Debug)] #[derive(Debug)]
pub struct ChapterRelationship { pub struct ChapterRelationship {
id: Id, pub id: Id,
data_type: DataType, pub data_type: DataType,
} }
#[derive(Debug)] #[derive(Debug)]
@@ -941,19 +941,11 @@ impl TryFrom<SearchResponse> for SearchResult {
.try_into() .try_into()
.map_err(|_| ResponseConversionError::Result(search_response.result))?; .map_err(|_| ResponseConversionError::Result(search_response.result))?;
let mut data: Result<Vec<Manga>, ResponseConversionError> = let data: Result<Vec<Manga>, Self::Error> = search_response
Ok(Vec::with_capacity(search_response.data.len())); .data
for m in search_response.data { .into_iter()
if let Ok(ref mut d) = data { .map(|m| m.try_into())
match m.try_into() { .collect();
Ok(v) => d.push(v),
Err(e) => {
data = Err(e);
break;
}
}
}
}
Ok(SearchResult { Ok(SearchResult {
response, response,
result, result,
@@ -1235,7 +1227,7 @@ impl TryFrom<ContentAttributes> for MangaAttributes {
links: attributes.links, links: attributes.links,
original_language: (attributes.original_language.as_str()) original_language: (attributes.original_language.as_str())
.try_into() .try_into()
.map_err(|_| AttributeConversionError::Language(attributes.original_language))?, .map_err(|_| Self::Error::Language(attributes.original_language))?,
last_volume: match attributes.last_volume { last_volume: match attributes.last_volume {
Some(s) => match s.parse() { Some(s) => match s.parse() {
Ok(v) => Some(v), Ok(v) => Some(v),
@@ -1243,7 +1235,7 @@ impl TryFrom<ContentAttributes> for MangaAttributes {
if s.is_empty() { if s.is_empty() {
None None
} else { } else {
return Err(AttributeConversionError::LastVolume(s)); return Err(Self::Error::LastVolume(s));
} }
} }
}, },
@@ -1256,7 +1248,7 @@ impl TryFrom<ContentAttributes> for MangaAttributes {
if n.is_empty() { if n.is_empty() {
None None
} else { } else {
return Err(AttributeConversionError::LastVolume(n)); return Err(Self::Error::LastVolume(n));
} }
} }
}, },
@@ -1266,99 +1258,78 @@ impl TryFrom<ContentAttributes> for MangaAttributes {
Some(s) => Some( Some(s) => Some(
(s.as_str()) (s.as_str())
.try_into() .try_into()
.map_err(|_| AttributeConversionError::PublicationDemographic(s))?, .map_err(|_| Self::Error::PublicationDemographic(s))?,
), ),
None => None, None => None,
}, },
status: (attributes.status.as_str()) status: (attributes.status.as_str())
.try_into() .try_into()
.map_err(|_| AttributeConversionError::Status(attributes.status))?, .map_err(|_| Self::Error::Status(attributes.status))?,
year: attributes.year, year: attributes.year,
content_rating: attributes content_rating: attributes
.content_rating .content_rating
.as_str() .as_str()
.try_into() .try_into()
.map_err(|_| AttributeConversionError::ContentRating(attributes.content_rating))?, .map_err(|_| Self::Error::ContentRating(attributes.content_rating))?,
tags: { tags: attributes
let mut tags = Vec::with_capacity(attributes.tags.len()); .tags
for m in attributes.tags { .into_iter()
tags.push(({ .map(|m| {
|| { Ok(Tag {
Ok::<Tag, AttributeConversionError>(Tag { data_type: (m.type_name.as_str())
data_type: (m.type_name.as_str()) .try_into()
.try_into() .map_err(|_| Self::Error::DataType(m.type_name))?,
.map_err(|_| AttributeConversionError::DataType(m.type_name))?, id: Id(m.id),
id: Id(m.id), relationships: m
relationships: { .relationships
let mut relationships = .into_iter()
Vec::with_capacity(m.relationships.len()); .map(|r| {
for m in m.relationships { Ok::<Relationship, Self::Error>(Relationship {
relationships.push(({ id: Id(r.id),
|| { data_type: (r.type_name.as_str())
Ok::<Relationship, AttributeConversionError>( .try_into()
Relationship { .map_err(|_| Self::Error::DataType(r.type_name))?,
id: Id(m.id), // TODO: Do this
data_type: (m.type_name.as_str()) attributes: None,
.try_into() related: r.related,
.map_err(|_| { })
AttributeConversionError::DataType(
m.type_name,
)
})?,
// TODO: Do this
attributes: None,
related: m.related,
},
)
}
})(
)?);
}
relationships
},
attributes: TagAttributes {
name: m.attributes.name,
group: m.attributes.group,
version: m.attributes.version,
description: Description {
en: m.attributes.description.en,
ru: m.attributes.description.ru,
},
},
}) })
} .collect::<Result<Vec<Relationship>, Self::Error>>()?,
})()?); attributes: TagAttributes {
} name: m.attributes.name,
tags group: m.attributes.group,
}, version: m.attributes.version,
description: Description {
en: m.attributes.description.en,
ru: m.attributes.description.ru,
},
},
})
})
.collect::<Result<Vec<Tag>, Self::Error>>()?,
state: (attributes.state.as_str()) state: (attributes.state.as_str())
.try_into() .try_into()
.map_err(|_| AttributeConversionError::State(attributes.state))?, .map_err(|_| Self::Error::State(attributes.state))?,
chapter_numbers_reset_on_new_volume: attributes.chapter_numbers_reset_on_new_volume, chapter_numbers_reset_on_new_volume: attributes.chapter_numbers_reset_on_new_volume,
created_at: DateTime::parse_from_rfc3339(&attributes.created_at).map_err(|_| { created_at: DateTime::parse_from_rfc3339(&attributes.created_at)
AttributeConversionError::CreatedAtDateTime(attributes.created_at.clone()) .map_err(|_| Self::Error::CreatedAtDateTime(attributes.created_at.clone()))?,
})?,
updated_at: DateTime::parse_from_rfc3339(&attributes.updated_at) updated_at: DateTime::parse_from_rfc3339(&attributes.updated_at)
.map_err(|_| AttributeConversionError::UpdatedAtDateTime(attributes.created_at))?, .map_err(|_| Self::Error::UpdatedAtDateTime(attributes.created_at))?,
version: attributes.version, version: attributes.version,
available_translated_languages: { available_translated_languages: attributes
let mut av = Vec::with_capacity(attributes.available_translated_languages.len()); .available_translated_languages
for m in attributes.available_translated_languages { .into_iter()
av.push(({ .map(|m| {
|| { Ok(match m {
Ok::<Option<Language>, AttributeConversionError>(match m { Some(s) => Some(
Some(s) => Some( (s.as_str())
(s.as_str()) .try_into()
.try_into() .map_err(|_| Self::Error::Language(s))?,
.map_err(|_| AttributeConversionError::Language(s))?, ),
), None => None,
None => None, })
}) })
} .collect::<Result<Vec<Option<Language>>, Self::Error>>()?,
})()?);
}
av
},
latest_uploaded_chapter: attributes latest_uploaded_chapter: attributes
.latest_uploaded_chapter .latest_uploaded_chapter
.as_ref() .as_ref()
@@ -1368,6 +1339,7 @@ impl TryFrom<ContentAttributes> for MangaAttributes {
} }
pub fn deserialize_id_query(json: &str) -> Result<IdQueryResult, IdQueryResultError> { pub fn deserialize_id_query(json: &str) -> Result<IdQueryResult, IdQueryResultError> {
std::fs::write("id_query.json", json).unwrap();
let id_query_response: IdQueryResponse = serde_json::from_str(json) let id_query_response: IdQueryResponse = serde_json::from_str(json)
.map_err(|e| IdQueryResultError::Serde(JsonError::Message(e.to_string())))?; .map_err(|e| IdQueryResultError::Serde(JsonError::Message(e.to_string())))?;
id_query_response id_query_response
@@ -1383,16 +1355,13 @@ impl TryFrom<IdQueryResponse> for IdQueryResult {
.result .result
.as_str() .as_str()
.try_into() .try_into()
.map_err(|_| IdQueryResponseError::Result)?, .map_err(|_| Self::Error::Result)?,
response: response response: response
.response .response
.as_str() .as_str()
.try_into() .try_into()
.map_err(|_| IdQueryResponseError::Response)?, .map_err(|_| Self::Error::Response)?,
data: response data: response.data.try_into().map_err(Self::Error::Data)?,
.data
.try_into()
.map_err(IdQueryResponseError::Data)?,
}) })
} }
} }
@@ -1422,14 +1391,18 @@ impl TryFrom<ChapterFeedResponse> for ChapterFeed {
type Error = ChapterFeedConversionError; type Error = ChapterFeedConversionError;
fn try_from(feed: ChapterFeedResponse) -> Result<Self, Self::Error> { fn try_from(feed: ChapterFeedResponse) -> Result<Self, Self::Error> {
// Now this is a bit of an abomination. It uses closures such that you can use the ? syntax Ok(ChapterFeed {
// sugar to return an error. Also uses for loops instead of iterators since they do not take result: (feed.result.as_str())
// ownership. I think I should have just kept the iterators. .try_into()
let mut data: Vec<Chapter> = Vec::with_capacity(feed.data.len()); .map_err(|_| Self::Error::Result(feed.result))?,
for m in feed.data { response: (feed.response.as_str())
let chapter: Chapter = ({ .try_into()
|| { .map_err(|_| Self::Error::Result(feed.response))?,
Ok::<Chapter, ChapterConversionError>(Chapter { data: feed
.data
.into_iter()
.map(|m| {
Ok(Chapter {
data_type: (m.type_name.as_str()) data_type: (m.type_name.as_str())
.try_into() .try_into()
.map_err(|_| ChapterConversionError::DataType(m.type_name))?, .map_err(|_| ChapterConversionError::DataType(m.type_name))?,
@@ -1438,45 +1411,22 @@ impl TryFrom<ChapterFeedResponse> for ChapterFeed {
.attributes .attributes
.try_into() .try_into()
.map_err(ChapterConversionError::Attributes)?, .map_err(ChapterConversionError::Attributes)?,
relationships: { relationships: m
let mut relationships = Vec::with_capacity(m.relationships.len()); .relationships
for r in m.relationships { .into_iter()
relationships.push( .map(|r| {
({ Ok(ChapterRelationship {
|| { data_type: (r.type_name.as_str()).try_into().map_err(|_| {
Ok({ ChapterRelationshipError::TypeData(r.type_name)
ChapterRelationship { })?,
data_type: (r.type_name.as_str()) id: Id(r.id),
.try_into() })
.map_err(|_| { })
ChapterRelationshipError::TypeData( .collect::<Result<Vec<ChapterRelationship>, ChapterRelationshipError>>()
r.type_name, .map_err(ChapterConversionError::Relationship)?,
)
})?,
id: Id(r.id),
}
})
}
})()
.map_err(ChapterConversionError::Relationship)?,
);
}
relationships
},
}) })
} })
})() .collect::<Result<Vec<Chapter>, Self::Error>>()?,
.map_err(ChapterFeedConversionError::Chapter)?;
data.push(chapter);
}
Ok(ChapterFeed {
result: (feed.result.as_str())
.try_into()
.map_err(|_| ChapterFeedConversionError::Result(feed.result))?,
response: (feed.response.as_str())
.try_into()
.map_err(|_| ChapterFeedConversionError::Result(feed.response))?,
data,
limit: feed.limit, limit: feed.limit,
offset: feed.offset, offset: feed.offset,
total: feed.total, total: feed.total,
@@ -1491,33 +1441,29 @@ impl TryFrom<ChapterAttributesContent> for ChapterAttributes {
volume: match &attributes.volume { volume: match &attributes.volume {
Some(v) => match v.parse() { Some(v) => match v.parse() {
Ok(n) => Some(n), Ok(n) => Some(n),
Err(_) => return Err(ChapterAttributeConversionError::Volume(v.to_owned())), Err(_) => return Err(Self::Error::Volume(v.to_owned())),
}, },
None => None, None => None,
}, },
chapter: match &attributes.chapter { chapter: match &attributes.chapter {
Some(v) => match v.parse() { Some(v) => match v.parse() {
Ok(v) => Some(v), Ok(v) => Some(v),
Err(_) => return Err(ChapterAttributeConversionError::Chapter(v.to_owned())), Err(_) => return Err(Self::Error::Chapter(v.to_owned())),
}, },
None => None, None => None,
}, },
created_at: DateTime::parse_from_rfc3339(&attributes.created_at) created_at: DateTime::parse_from_rfc3339(&attributes.created_at)
.map_err(|_| ChapterAttributeConversionError::CreatedAt(attributes.created_at))?, .map_err(|_| Self::Error::CreatedAt(attributes.created_at))?,
published_at: DateTime::parse_from_rfc3339(&attributes.publish_at) published_at: DateTime::parse_from_rfc3339(&attributes.publish_at)
.map_err(|_| ChapterAttributeConversionError::CreatedAt(attributes.publish_at))?, .map_err(|_| Self::Error::CreatedAt(attributes.publish_at))?,
updated_at: DateTime::parse_from_rfc3339(&attributes.updated_at) updated_at: DateTime::parse_from_rfc3339(&attributes.updated_at)
.map_err(|_| ChapterAttributeConversionError::CreatedAt(attributes.updated_at))?, .map_err(|_| Self::Error::CreatedAt(attributes.updated_at))?,
external_url: attributes.external_url, external_url: attributes.external_url,
title: attributes.title, title: attributes.title,
pages: attributes.pages, pages: attributes.pages,
translated_language: (attributes.translated_language.as_str()) translated_language: (attributes.translated_language.as_str())
.try_into() .try_into()
.map_err(|_| { .map_err(|_| Self::Error::TranslatedLanguage(attributes.translated_language))?,
ChapterAttributeConversionError::TranslatedLanguage(
attributes.translated_language,
)
})?,
version: attributes.version, version: attributes.version,
}) })
} }
@@ -1529,7 +1475,7 @@ impl TryFrom<ChapterImagesContent> for ChapterImages {
Ok(ChapterImages { Ok(ChapterImages {
result: (data.result.as_str()) result: (data.result.as_str())
.try_into() .try_into()
.map_err(|_| ChapterImageError::Result(data.result))?, .map_err(|_| Self::Error::Result(data.result))?,
base_url: data.base_url, base_url: data.base_url,
chapter: ChapterImageData { chapter: ChapterImageData {
hash: data.chapter.hash, hash: data.chapter.hash,
@@ -1562,71 +1508,63 @@ impl TryFrom<ContentData> for Manga {
Ok(Manga { Ok(Manga {
id: Id(m.id), id: Id(m.id),
data_type: (m.type_name.as_str()).try_into().map_err(|_| { data_type: (m.type_name.as_str()).try_into().map_err(|_| {
ResponseConversionError::Attribute(AttributeConversionError::DataType(m.type_name)) Self::Error::Attribute(AttributeConversionError::DataType(m.type_name))
})?, })?,
attributes: m attributes: m.attributes.try_into().map_err(Self::Error::Attribute)?,
.attributes relationships: m
.try_into() .relationships
.map_err(ResponseConversionError::Attribute)?, .into_iter()
relationships: { .map(|m| {
let mut relationships = Vec::with_capacity(m.relationships.len()); Ok(Relationship {
for m in m.relationships { id: Id(m.id),
relationships.push( data_type: m
({ .type_name
|| { .as_str()
Ok::<Relationship, AttributeConversionError>(Relationship { .try_into()
id: Id(m.id), .map_err(|_| AttributeConversionError::DataType(m.type_name))?,
data_type: m.type_name.as_str().try_into().map_err(|_| { attributes: {
AttributeConversionError::DataType(m.type_name) if let Some(attributes) = m.attributes {
Some(CoverAttributes {
created_at: DateTime::parse_from_rfc3339(
&attributes.created_at,
)
.map_err(|_| {
AttributeConversionError::CreatedAtDateTime(
attributes.created_at.clone(),
)
})?, })?,
attributes: { updated_at: DateTime::parse_from_rfc3339(
if let Some(attributes) = m.attributes { &attributes.created_at,
Some(CoverAttributes { )
created_at: DateTime::parse_from_rfc3339( .map_err(|_| {
&attributes.created_at, AttributeConversionError::CreatedAtDateTime(
) attributes.created_at.clone(),
.map_err(|_| { )
AttributeConversionError::CreatedAtDateTime( })?,
attributes.created_at.clone(), // TODO: Something should probably be done here
) description: attributes.description,
})?, file_name: Id(attributes.file_name.clone()),
updated_at: DateTime::parse_from_rfc3339( locale: (attributes.locale.as_str()).try_into().map_err(
&attributes.created_at, |_| {
) AttributeConversionError::Locale(
.map_err(|_| { attributes.locale.clone(),
AttributeConversionError::CreatedAtDateTime( )
attributes.created_at.clone(), },
) )?,
})?, version: attributes.version,
// TODO: Something should probably be done here volume: match &attributes.volume {
description: attributes.description, Some(v) => v.parse().ok(),
file_name: Id(attributes.file_name.clone()), None => None,
locale: (attributes.locale.as_str())
.try_into()
.map_err(|_| {
AttributeConversionError::Locale(
attributes.locale.clone(),
)
})?,
version: attributes.version,
volume: match &attributes.volume {
Some(v) => v.parse().ok(),
None => None,
},
})
} else {
None
}
}, },
related: m.related.clone(),
}) })
} else {
None
} }
})() },
.map_err(ResponseConversionError::Attribute)?, related: m.related.clone(),
); })
} })
relationships .collect::<Result<Vec<Relationship>, AttributeConversionError>>()?,
},
}) })
} }
} }
@@ -1638,24 +1576,32 @@ mod tests {
#[test] #[test]
fn id_query() { fn id_query() {
let manga = std::fs::read_to_string("test_data/id_query_hunter_x_hunter.json").unwrap(); let manga = std::fs::read_to_string("test_data/id_query_hunter_x_hunter.json").unwrap();
assert_eq!(deserialize_id_query(&manga).unwrap().result , ResponseResult::Ok); assert_eq!(
deserialize_id_query(&manga).unwrap().result,
ResponseResult::Ok
);
} }
#[test] #[test]
fn search() { fn search() {
let search_result = std::fs::read_to_string("test_data/search_result.json").unwrap(); let search_result = std::fs::read_to_string("test_data/search_result.json").unwrap();
assert_eq!(deserialize_search(&search_result).unwrap().result , ResponseResult::Ok); assert_eq!(
deserialize_search(&search_result).unwrap().result,
ResponseResult::Ok
);
} }
#[test] #[test]
fn chapter_feed() { fn chapter_feed() {
let chapter_feed = std::fs::read_to_string("test_data/chapter_feed_hunter_x_hunter.json").unwrap(); let chapter_feed =
std::fs::read_to_string("test_data/chapter_feed_hunter_x_hunter.json").unwrap();
deserialize_chapter_feed(&chapter_feed).unwrap(); deserialize_chapter_feed(&chapter_feed).unwrap();
} }
#[test] #[test]
fn chapter_images() { fn chapter_images() {
let chapter_images = std::fs::read_to_string("test_data/chapter_images_hunter_x_hunter.json").unwrap(); let chapter_images =
std::fs::read_to_string("test_data/chapter_images_hunter_x_hunter.json").unwrap();
deserialize_chapter_images(&chapter_images).unwrap(); deserialize_chapter_images(&chapter_images).unwrap();
} }
} }