From 223fc29143688c08770a1e7893f285c7aa95a754 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonah=20Br=C3=BCchert?= Date: Sat, 2 Aug 2025 03:48:58 +0200 Subject: [PATCH 1/3] Make route colors optional When generating feeds using the structures from gtfs_structures, it is desirable to be able to serialize an empty column. I suspect it is also relevant to know on the comsumer side wether black is a brand color or a fallback. This change is a an API break. --- src/objects.rs | 28 +++++++++++++++++++++------- src/serde_helpers.rs | 30 +++++++++++------------------- src/tests.rs | 14 +++++++------- 3 files changed, 39 insertions(+), 33 deletions(-) diff --git a/src/objects.rs b/src/objects.rs index 6795f95..3b98207 100644 --- a/src/objects.rs +++ b/src/objects.rs @@ -340,20 +340,20 @@ pub struct Route { pub order: Option, /// Route color designation that matches public facing material #[serde( - deserialize_with = "deserialize_route_color", - serialize_with = "serialize_color", + deserialize_with = "deserialize_optional_color", + serialize_with = "serialize_optional_color", rename = "route_color", - default = "default_route_color" + default )] - pub color: RGB8, + pub color: Option, /// Legible color to use for text drawn against a background of [Route::route_color] #[serde( - deserialize_with = "deserialize_route_text_color", - serialize_with = "serialize_color", + deserialize_with = "deserialize_optional_color", + serialize_with = "serialize_optional_color", rename = "route_text_color", default )] - pub text_color: RGB8, + pub text_color: Option, /// Indicates whether a rider can board the transit vehicle anywhere along the vehicle’s travel path #[serde(default)] pub continuous_pickup: ContinuousPickupDropOff, @@ -362,6 +362,20 @@ pub struct Route { pub continuous_drop_off: ContinuousPickupDropOff, } +impl Route { + /// Legible color to use for text drawn against a background of [Route::route_color] + /// Defaults to white (FFFFFF) when omitted or left empty. + pub fn text_color(&self) -> RGB8 { + self.text_color.unwrap_or_default() + } + + /// Legible color to use for text drawn against a background of [Route::route_color] + /// Defaults to black (000000) when omitted or left empty. + pub fn color(&self) -> RGB8 { + self.color.unwrap_or_else(default_route_color) + } +} + impl Type for Route { fn object_type(&self) -> ObjectType { ObjectType::Route diff --git a/src/serde_helpers.rs b/src/serde_helpers.rs index 1cc0b1a..b850651 100644 --- a/src/serde_helpers.rs +++ b/src/serde_helpers.rs @@ -139,13 +139,7 @@ where } } -pub fn parse_color( - s: &str, - default: impl std::ops::FnOnce() -> RGB8, -) -> Result { - if s.is_empty() { - return Ok(default()); - } +pub fn parse_color(s: &str) -> Result { if s.len() != 6 { return Err(crate::Error::InvalidColor(s.to_owned())); } @@ -158,26 +152,24 @@ pub fn parse_color( Ok(RGB8::new(r, g, b)) } -pub fn deserialize_route_color<'de, D>(de: D) -> Result -where - D: Deserializer<'de>, -{ - String::deserialize(de) - .and_then(|s| parse_color(&s, default_route_color).map_err(de::Error::custom)) -} - -pub fn deserialize_route_text_color<'de, D>(de: D) -> Result +pub fn deserialize_optional_color<'de, D>(de: D) -> Result, D::Error> where D: Deserializer<'de>, { - String::deserialize(de).and_then(|s| parse_color(&s, RGB8::default).map_err(de::Error::custom)) + Option::<&str>::deserialize(de)? + .map(|s| parse_color(s).map_err(de::Error::custom)) + .transpose() } -pub fn serialize_color(color: &RGB8, serializer: S) -> Result +pub fn serialize_optional_color(color: &Option, serializer: S) -> Result where S: Serializer, { - serializer.serialize_str(format!("{:02X}{:02X}{:02X}", color.r, color.g, color.b).as_str()) + match color { + Some(color) => serializer + .serialize_str(format!("{:02X}{:02X}{:02X}", color.r, color.g, color.b).as_str()), + None => serializer.serialize_none(), + } } pub fn default_route_color() -> RGB8 { diff --git a/src/tests.rs b/src/tests.rs index 4c3aa70..fee2717 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -84,19 +84,19 @@ fn read_routes() { let gtfs = Gtfs::from_path("fixtures/basic").expect("impossible to read gtfs"); assert_eq!(3, gtfs.routes.len()); assert_eq!(RouteType::Bus, gtfs.get_route("1").unwrap().route_type); - assert_eq!(RGB8::new(0, 0, 0), gtfs.get_route("1").unwrap().color); + assert_eq!(RGB8::new(0, 0, 0), gtfs.get_route("1").unwrap().color()); assert_eq!( RGB8::new(255, 255, 255), - gtfs.get_route("1").unwrap().text_color + gtfs.get_route("1").unwrap().text_color() ); - assert_eq!(RGB8::new(0, 0, 0), gtfs.get_route("1").unwrap().color); + assert_eq!(RGB8::new(0, 0, 0), gtfs.get_route("1").unwrap().color()); assert_eq!( RGB8::new(0, 0, 0), - gtfs.get_route("default_colors").unwrap().text_color + gtfs.get_route("default_colors").unwrap().text_color() ); assert_eq!( RGB8::new(255, 255, 255), - gtfs.get_route("default_colors").unwrap().color + gtfs.get_route("default_colors").unwrap().color() ); assert_eq!(Some(1), gtfs.get_route("1").unwrap().order); assert_eq!( @@ -440,8 +440,8 @@ fn read_only_required_fields() { let fare_attribute = gtfs.fare_attributes.get("50").unwrap(); let feed = >fs.feed_info[0]; let shape = >fs.shapes.get("A_shp").unwrap()[0]; - assert_eq!(route.color, RGB8::new(255, 255, 255)); - assert_eq!(route.text_color, RGB8::new(0, 0, 0)); + assert_eq!(route.color(), RGB8::new(255, 255, 255)); + assert_eq!(route.text_color(), RGB8::new(0, 0, 0)); assert_eq!(fare_attribute.transfer_duration, None); assert_eq!(feed.start_date, None); assert_eq!(feed.end_date, None); From e4e43a1edfaa8a95264f34c700efd6be73bb7103 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonah=20Br=C3=BCchert?= Date: Thu, 7 Aug 2025 17:39:28 +0200 Subject: [PATCH 2/3] Prepare v0.45.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2a7ebfa..b66ab01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] description = "Read GTFS (public transit timetables) files" name = "gtfs-structures" -version = "0.44.0" +version = "0.45.0" authors = [ "Tristram Gräbener ", "Antoine Desbordes ", From 1f4ba9457a93ef7ff61ea6f53875cb68ad847f3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonah=20Br=C3=BCchert?= Date: Thu, 7 Aug 2025 18:55:49 +0200 Subject: [PATCH 3/3] Fix clippy warnings --- src/objects.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/objects.rs b/src/objects.rs index 3b98207..d4b3a3b 100644 --- a/src/objects.rs +++ b/src/objects.rs @@ -391,9 +391,9 @@ impl Id for Route { impl fmt::Display for Route { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if let Some(long_name) = self.long_name.as_ref() { - write!(f, "{}", long_name) + write!(f, "{long_name}") } else if let Some(short_name) = self.short_name.as_ref() { - write!(f, "{}", short_name) + write!(f, "{short_name}") } else { write!(f, "{}", self.id) }