use cjseq::{
GeometryType as CjGeometryType, MaterialReference as CjMaterialReference,
MaterialValues as CjMaterialValues, Ring, SemanticSurfaceType, Semantics, SemanticsSurface,
SemanticsValues, Shell, Surface, TextureReference as CjTextureReference,
TextureValues as CjTextureValues, TexturedRing, TexturedShell, TexturedSurface,
};
use crate::error::Error;
use crate::fb::{
Column, GeometryType, MaterialMapping, SemanticObject, SemanticSurfaceType as FbSurfaceType,
TextureMapping,
};
use std::collections::HashMap;
use super::deserializer::decode_attributes;
const NULL: u32 = u32::MAX;
fn index(v: u32) -> Option<usize> {
if v == NULL {
None
} else {
Some(v as usize)
}
}
struct BoundaryCursor<'a> {
shells: &'a [u32],
surfaces: &'a [u32],
strings: &'a [u32],
indices: &'a [u32],
shell_cursor: usize,
surface_cursor: usize,
string_cursor: usize,
index_cursor: usize,
}
impl<'a> BoundaryCursor<'a> {
fn new(shells: &'a [u32], surfaces: &'a [u32], strings: &'a [u32], indices: &'a [u32]) -> Self {
BoundaryCursor {
shells,
surfaces,
strings,
indices,
shell_cursor: 0,
surface_cursor: 0,
string_cursor: 0,
index_cursor: 0,
}
}
fn take_ring(&mut self) -> Ring {
let size = self.strings.get(self.string_cursor).copied().unwrap_or(0) as usize;
self.string_cursor += 1;
let end = (self.index_cursor + size).min(self.indices.len());
let ring = self.indices[self.index_cursor..end]
.iter()
.map(|&i| i as usize)
.collect();
self.index_cursor = end;
ring
}
fn take_surface(&mut self) -> Surface {
let rings = self.surfaces.get(self.surface_cursor).copied().unwrap_or(0);
self.surface_cursor += 1;
(0..rings).map(|_| self.take_ring()).collect()
}
fn take_shell(&mut self) -> Shell {
let surfaces = self.shells.get(self.shell_cursor).copied().unwrap_or(0);
self.shell_cursor += 1;
(0..surfaces).map(|_| self.take_surface()).collect()
}
}
pub(crate) fn decode_points(indices: &[u32]) -> Ring {
indices.iter().map(|&i| i as usize).collect()
}
pub(crate) fn decode_rings(strings: &[u32], indices: &[u32]) -> Vec<Ring> {
let mut cursor = BoundaryCursor::new(&[], &[], strings, indices);
(0..strings.len()).map(|_| cursor.take_ring()).collect()
}
pub(crate) fn decode_surfaces(surfaces: &[u32], strings: &[u32], indices: &[u32]) -> Vec<Surface> {
let mut cursor = BoundaryCursor::new(&[], surfaces, strings, indices);
(0..surfaces.len()).map(|_| cursor.take_surface()).collect()
}
pub(crate) fn decode_shells(
shells: &[u32],
surfaces: &[u32],
strings: &[u32],
indices: &[u32],
) -> Vec<Shell> {
let mut cursor = BoundaryCursor::new(shells, surfaces, strings, indices);
(0..shells.len()).map(|_| cursor.take_shell()).collect()
}
pub(crate) fn decode_solids(
solids: &[u32],
shells: &[u32],
surfaces: &[u32],
strings: &[u32],
indices: &[u32],
) -> Vec<Vec<Shell>> {
let mut cursor = BoundaryCursor::new(shells, surfaces, strings, indices);
solids
.iter()
.map(|&n| (0..n).map(|_| cursor.take_shell()).collect())
.collect()
}
pub(crate) fn decode_semantics_surfaces(
semantics_objects: &[SemanticObject],
semantic_attr_schema: Option<flatbuffers::Vector<'_, flatbuffers::ForwardsUOffset<Column<'_>>>>,
) -> Vec<SemanticsSurface> {
let surfaces = semantics_objects.iter().map(|s| {
let thetype = to_cj_surface_type(s.type_(), s.extension_type());
let children = s
.children()
.map(|c| c.iter().map(|i| i as usize).collect::<Vec<_>>());
let attributes = semantic_attr_schema
.as_ref()
.and_then(|schema| s.attributes().map(|a| decode_attributes(schema, a)));
let other = attributes
.and_then(|v| match v {
serde_json::Value::Object(map) => Some(map.into_iter().collect()),
_ => None,
})
.unwrap_or_default();
SemanticsSurface {
thetype,
parent: s.parent().map(|p| p as usize),
children,
other,
}
});
surfaces.collect()
}
pub(crate) fn to_cj_surface_type(
surface_type: FbSurfaceType,
extension_type: Option<&str>,
) -> SemanticSurfaceType {
match surface_type {
FbSurfaceType::RoofSurface => SemanticSurfaceType::RoofSurface,
FbSurfaceType::GroundSurface => SemanticSurfaceType::GroundSurface,
FbSurfaceType::WallSurface => SemanticSurfaceType::WallSurface,
FbSurfaceType::ClosureSurface => SemanticSurfaceType::ClosureSurface,
FbSurfaceType::OuterCeilingSurface => SemanticSurfaceType::OuterCeilingSurface,
FbSurfaceType::OuterFloorSurface => SemanticSurfaceType::OuterFloorSurface,
FbSurfaceType::Window => SemanticSurfaceType::Window,
FbSurfaceType::Door => SemanticSurfaceType::Door,
FbSurfaceType::InteriorWallSurface => SemanticSurfaceType::InteriorWallSurface,
FbSurfaceType::CeilingSurface => SemanticSurfaceType::CeilingSurface,
FbSurfaceType::FloorSurface => SemanticSurfaceType::FloorSurface,
FbSurfaceType::WaterSurface => SemanticSurfaceType::WaterSurface,
FbSurfaceType::WaterGroundSurface => SemanticSurfaceType::WaterGroundSurface,
FbSurfaceType::WaterClosureSurface => SemanticSurfaceType::WaterClosureSurface,
FbSurfaceType::TrafficArea => SemanticSurfaceType::TrafficArea,
FbSurfaceType::AuxiliaryTrafficArea => SemanticSurfaceType::AuxiliaryTrafficArea,
FbSurfaceType::TransportationMarking => SemanticSurfaceType::TransportationMarking,
FbSurfaceType::TransportationHole => SemanticSurfaceType::TransportationHole,
_ => {
SemanticSurfaceType::Extension(extension_type.unwrap_or("+GenericSurface").to_string())
}
}
}
pub(crate) fn decode_semantics(
solids: &[u32],
shells: &[u32],
geometry_type: GeometryType,
semantics_objects: Vec<SemanticObject>,
semantics_values: Option<Vec<u32>>,
semantic_attr_schema: Option<flatbuffers::Vector<'_, flatbuffers::ForwardsUOffset<Column<'_>>>>,
) -> Semantics {
let surfaces = decode_semantics_surfaces(&semantics_objects, semantic_attr_schema);
let Some(semantics_values) = semantics_values else {
return Semantics {
surfaces,
values: None,
other: HashMap::new(),
};
};
let mut cursor = 0usize;
let mut take_shell = |n: usize, values: &[u32]| -> Vec<Option<usize>> {
let end = (cursor + n).min(values.len());
let out = values[cursor..end].iter().map(|&v| index(v)).collect();
cursor = end;
out
};
let values = match geometry_type {
GeometryType::MultiPoint
| GeometryType::MultiLineString
| GeometryType::MultiSurface
| GeometryType::CompositeSurface => {
SemanticsValues::Surfaces(semantics_values.iter().map(|&v| index(v)).collect())
}
GeometryType::Solid => SemanticsValues::Shells(
shells
.iter()
.map(|&n| Some(take_shell(n as usize, &semantics_values)))
.collect(),
),
GeometryType::MultiSolid | GeometryType::CompositeSolid => {
let mut shell_cursor = 0usize;
SemanticsValues::Solids(
solids
.iter()
.map(|&shell_count| {
Some(
(0..shell_count)
.map(|_| {
let n = shells.get(shell_cursor).copied().unwrap_or(0) as usize;
shell_cursor += 1;
Some(take_shell(n, &semantics_values))
})
.collect(),
)
})
.collect(),
)
}
_ => SemanticsValues::Surfaces(semantics_values.iter().map(|&v| index(v)).collect()),
};
Semantics {
surfaces,
values: Some(values),
other: HashMap::new(),
}
}
impl GeometryType {
pub fn to_str(self) -> Result<&'static str, Error> {
Ok(match self {
Self::MultiPoint => "MultiPoint",
Self::MultiLineString => "MultiLineString",
Self::MultiSurface => "MultiSurface",
Self::CompositeSurface => "CompositeSurface",
Self::Solid => "Solid",
Self::MultiSolid => "MultiSolid",
Self::CompositeSolid => "CompositeSolid",
Self::GeometryInstance => "GeometryInstance",
other => return Err(Error::UnknownEnumTag("GeometryType", format!("{other:?}"))),
})
}
pub fn to_cj(self) -> Result<CjGeometryType, Error> {
Ok(match self {
Self::MultiPoint => CjGeometryType::MultiPoint,
Self::MultiLineString => CjGeometryType::MultiLineString,
Self::MultiSurface => CjGeometryType::MultiSurface,
Self::CompositeSurface => CjGeometryType::CompositeSurface,
Self::Solid => CjGeometryType::Solid,
Self::MultiSolid => CjGeometryType::MultiSolid,
Self::CompositeSolid => CjGeometryType::CompositeSolid,
Self::GeometryInstance => CjGeometryType::GeometryInstance,
other => return Err(Error::UnknownEnumTag("GeometryType", format!("{other:?}"))),
})
}
}
pub(crate) fn decode_materials(
geometry_type: GeometryType,
material_mappings: &[MaterialMapping],
) -> Option<HashMap<String, CjMaterialReference>> {
if material_mappings.is_empty() {
return None;
}
let mut materials = HashMap::new();
for mapping in material_mappings {
let theme = mapping.theme().unwrap_or("theme").to_string();
if let Some(value) = mapping.value() {
materials.insert(
theme,
CjMaterialReference {
value: Some(value as usize),
values: None,
other: HashMap::new(),
},
);
continue;
}
let solids = mapping
.solids()
.map(|s| s.iter().collect::<Vec<_>>())
.unwrap_or_default();
let shells = mapping
.shells()
.map(|s| s.iter().collect::<Vec<_>>())
.unwrap_or_default();
let Some(vertices) = mapping.vertices().map(|v| v.iter().collect::<Vec<_>>()) else {
materials.insert(
theme,
CjMaterialReference {
value: None,
values: Some(None),
other: HashMap::new(),
},
);
continue;
};
let mut vertex_cursor = 0usize;
let mut take_shell = |n: usize| -> Vec<Option<usize>> {
let end = (vertex_cursor + n).min(vertices.len());
let out = vertices[vertex_cursor..end]
.iter()
.map(|&v| index(v))
.collect();
vertex_cursor = end;
out
};
let values = match geometry_type {
GeometryType::MultiSurface | GeometryType::CompositeSurface => {
CjMaterialValues::Surfaces(vertices.iter().map(|&v| index(v)).collect())
}
GeometryType::Solid => CjMaterialValues::Shells(
shells
.iter()
.map(|&n| {
if n == NULL {
None
} else {
Some(take_shell(n as usize))
}
})
.collect(),
),
GeometryType::MultiSolid | GeometryType::CompositeSolid => {
let mut shell_cursor = 0usize;
CjMaterialValues::Solids(
solids
.iter()
.map(|&shell_count| {
if shell_count == NULL {
return None;
}
Some(
(0..shell_count)
.map(|_| {
let n = shells.get(shell_cursor).copied().unwrap_or(0);
shell_cursor += 1;
if n == NULL {
None
} else {
Some(take_shell(n as usize))
}
})
.collect(),
)
})
.collect(),
)
}
_ => CjMaterialValues::Surfaces(vertices.iter().map(|&v| index(v)).collect()),
};
materials.insert(
theme,
CjMaterialReference {
value: None,
values: Some(Some(values)),
other: HashMap::new(),
},
);
}
Some(materials)
}
pub(crate) fn decode_textures(
geometry_type: GeometryType,
texture_mappings: &[TextureMapping],
) -> Option<HashMap<String, CjTextureReference>> {
if texture_mappings.is_empty() {
return None;
}
let mut textures = HashMap::new();
for mapping in texture_mappings {
let theme = mapping.theme().unwrap_or("theme").to_string();
let Some(vertices) = mapping.vertices().map(|v| v.iter().collect::<Vec<_>>()) else {
textures.insert(
theme,
CjTextureReference {
values: None,
other: HashMap::new(),
},
);
continue;
};
let solids = mapping
.solids()
.map(|s| s.iter().collect::<Vec<_>>())
.unwrap_or_default();
let shells = mapping
.shells()
.map(|s| s.iter().collect::<Vec<_>>())
.unwrap_or_default();
let surfaces = mapping
.surfaces()
.map(|s| s.iter().collect::<Vec<_>>())
.unwrap_or_default();
let strings = mapping
.strings()
.map(|s| s.iter().collect::<Vec<_>>())
.unwrap_or_default();
let mut cursor = TextureCursor {
surfaces: &surfaces,
shells: &shells,
strings: &strings,
vertices: &vertices,
shell_cursor: 0,
surface_cursor: 0,
string_cursor: 0,
vertex_cursor: 0,
};
let values = match geometry_type {
GeometryType::MultiSurface | GeometryType::CompositeSurface => {
CjTextureValues::Surface(
(0..surfaces.len()).map(|_| cursor.take_surface()).collect(),
)
}
GeometryType::Solid => {
CjTextureValues::Shell((0..shells.len()).map(|_| cursor.take_shell()).collect())
}
GeometryType::MultiSolid | GeometryType::CompositeSolid => CjTextureValues::Solid(
solids
.iter()
.map(|&n| (0..n).map(|_| cursor.take_shell()).collect())
.collect(),
),
_ => CjTextureValues::Surface(
(0..surfaces.len().max(1))
.map(|_| cursor.take_surface())
.collect(),
),
};
textures.insert(
theme,
CjTextureReference {
values: Some(values),
other: HashMap::new(),
},
);
}
Some(textures)
}
struct TextureCursor<'a> {
surfaces: &'a [u32],
shells: &'a [u32],
strings: &'a [u32],
vertices: &'a [u32],
shell_cursor: usize,
surface_cursor: usize,
string_cursor: usize,
vertex_cursor: usize,
}
impl TextureCursor<'_> {
fn take_ring(&mut self) -> TexturedRing {
let size = self.strings.get(self.string_cursor).copied().unwrap_or(0) as usize;
self.string_cursor += 1;
let end = (self.vertex_cursor + size).min(self.vertices.len());
let ring = self.vertices[self.vertex_cursor..end]
.iter()
.map(|&v| index(v))
.collect();
self.vertex_cursor = end;
ring
}
fn take_surface(&mut self) -> TexturedSurface {
let rings = self.surfaces.get(self.surface_cursor).copied().unwrap_or(0);
self.surface_cursor += 1;
(0..rings).map(|_| self.take_ring()).collect()
}
fn take_shell(&mut self) -> TexturedShell {
let surfaces = self.shells.get(self.shell_cursor).copied().unwrap_or(0);
self.shell_cursor += 1;
(0..surfaces).map(|_| self.take_surface()).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fb::geometry_generated::{
MaterialMappingArgs, TextureMapping as FbTextureMapping, TextureMappingArgs,
};
use anyhow::Result;
use flatbuffers::FlatBufferBuilder;
use pretty_assertions::assert_eq;
use serde_json::json;
fn decode_material_values(
geometry_type: GeometryType,
solids: &[u32],
shells: &[u32],
vertices: &[u32],
) -> serde_json::Value {
let mut fbb = FlatBufferBuilder::new();
let theme = fbb.create_string("t");
let solids_v = (!solids.is_empty()).then(|| fbb.create_vector(solids));
let shells_v = (!shells.is_empty()).then(|| fbb.create_vector(shells));
let vertices_v = fbb.create_vector(vertices);
let mapping = MaterialMapping::create(
&mut fbb,
&MaterialMappingArgs {
theme: Some(theme),
solids: solids_v,
shells: shells_v,
vertices: Some(vertices_v),
value: None,
},
);
fbb.finish(mapping, None);
let buf = fbb.finished_data().to_vec();
let mapping = flatbuffers::root::<MaterialMapping>(&buf).expect("valid mapping");
let decoded = decode_materials(geometry_type, &[mapping]).expect("one theme");
serde_json::to_value(&decoded["t"].values).expect("values serialize")
}
fn decode_texture_values(
geometry_type: GeometryType,
solids: &[u32],
shells: &[u32],
surfaces: &[u32],
strings: &[u32],
vertices: &[u32],
) -> serde_json::Value {
let mut fbb = FlatBufferBuilder::new();
let theme = fbb.create_string("t");
let solids_v = fbb.create_vector(solids);
let shells_v = fbb.create_vector(shells);
let surfaces_v = fbb.create_vector(surfaces);
let strings_v = fbb.create_vector(strings);
let vertices_v = fbb.create_vector(vertices);
let mapping = FbTextureMapping::create(
&mut fbb,
&TextureMappingArgs {
theme: Some(theme),
solids: Some(solids_v),
shells: Some(shells_v),
surfaces: Some(surfaces_v),
strings: Some(strings_v),
vertices: Some(vertices_v),
},
);
fbb.finish(mapping, None);
let buf = fbb.finished_data().to_vec();
let mapping = flatbuffers::root::<FbTextureMapping>(&buf).expect("valid mapping");
let decoded = decode_textures(geometry_type, &[mapping]).expect("one theme");
serde_json::to_value(&decoded["t"].values).expect("values serialize")
}
#[test]
fn test_decode_boundaries() -> Result<()> {
assert_eq!(decode_points(&[2, 44, 0, 7]), vec![2, 44, 0, 7]);
assert_eq!(
serde_json::to_value(decode_rings(&[3, 3], &[2, 3, 5, 77, 55, 212]))?,
json!([[2, 3, 5], [77, 55, 212]])
);
assert_eq!(
serde_json::to_value(decode_surfaces(
&[1, 1, 1],
&[4, 4, 4],
&[0, 3, 2, 1, 4, 5, 6, 7, 0, 1, 5, 4]
))?,
json!([[[0, 3, 2, 1]], [[4, 5, 6, 7]], [[0, 1, 5, 4]]])
);
let indices = [
0, 3, 2, 1, 22, 1, 2, 3, 4, 4, 5, 6, 7, 0, 1, 5, 4, 1, 2, 6, 5, 240, 243, 124, 244,
246, 724, 34, 414, 45, 111, 246, 5,
];
assert_eq!(
serde_json::to_value(decode_shells(
&[4, 4],
&[2, 1, 1, 1, 1, 1, 1, 1],
&[5, 4, 4, 4, 4, 3, 3, 3, 3],
&indices
))?,
json!([
[
[[0, 3, 2, 1, 22], [1, 2, 3, 4]],
[[4, 5, 6, 7]],
[[0, 1, 5, 4]],
[[1, 2, 6, 5]]
],
[
[[240, 243, 124]],
[[244, 246, 724]],
[[34, 414, 45]],
[[111, 246, 5]]
]
])
);
let indices = [
0, 3, 2, 1, 22, 4, 5, 6, 7, 0, 1, 5, 4, 1, 2, 6, 5, 240, 243, 124, 244, 246, 724, 34,
414, 45, 111, 246, 5, 666, 667, 668, 74, 75, 76, 880, 881, 885, 111, 122, 226,
];
assert_eq!(
serde_json::to_value(decode_solids(
&[2, 1],
&[4, 4, 4],
&[1; 12],
&[5, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3],
&indices
))?,
json!([
[
[
[[0, 3, 2, 1, 22]],
[[4, 5, 6, 7]],
[[0, 1, 5, 4]],
[[1, 2, 6, 5]]
],
[
[[240, 243, 124]],
[[244, 246, 724]],
[[34, 414, 45]],
[[111, 246, 5]]
]
],
[[
[[666, 667, 668]],
[[74, 75, 76]],
[[880, 881, 885]],
[[111, 122, 226]]
]]
])
);
Ok(())
}
#[test]
fn identical_material_arrays_decode_to_different_depths_per_type() {
let (solids, shells, vertices) = (&[1u32][..], &[2u32][..], &[0u32, 1][..]);
assert_eq!(
decode_material_values(GeometryType::Solid, solids, shells, vertices),
json!([[0, 1]]),
"a Solid's material values are one array per shell"
);
assert_eq!(
decode_material_values(GeometryType::MultiSolid, solids, shells, vertices),
json!([[[0, 1]]]),
"a MultiSolid's are one array per shell, per solid"
);
assert_eq!(
decode_material_values(GeometryType::CompositeSolid, solids, shells, vertices),
json!([[[0, 1]]]),
"a CompositeSolid decodes exactly as a MultiSolid does"
);
}
#[test]
fn test_decode_materials() -> Result<()> {
let mut fbb = FlatBufferBuilder::new();
let theme = fbb.create_string("theme1");
let mapping = MaterialMapping::create(
&mut fbb,
&MaterialMappingArgs {
theme: Some(theme),
value: Some(5),
..Default::default()
},
);
fbb.finish(mapping, None);
let buf = fbb.finished_data().to_vec();
let mapping = flatbuffers::root::<MaterialMapping>(&buf)?;
let materials = decode_materials(GeometryType::Solid, &[mapping]).expect("one theme");
assert_eq!(materials["theme1"].value, Some(5));
assert!(materials["theme1"].values.is_none());
assert_eq!(
decode_material_values(GeometryType::MultiSurface, &[], &[], &[0, 1, NULL, 2]),
json!([0, 1, null, 2])
);
assert_eq!(
decode_material_values(GeometryType::CompositeSurface, &[], &[], &[0, 1, NULL, 2]),
json!([0, 1, null, 2])
);
assert_eq!(
decode_material_values(GeometryType::Solid, &[2], &[3, 3], &[0, 1, NULL, 2, 3, 4]),
json!([[0, 1, null], [2, 3, 4]])
);
assert_eq!(
decode_material_values(
GeometryType::CompositeSolid,
&[2, 1],
&[3, 3, 3],
&[0, 1, NULL, 2, NULL, NULL, 3, 4, NULL]
),
json!([[[0, 1, null], [2, null, null]], [[3, 4, null]]])
);
Ok(())
}
#[test]
fn a_null_material_shell_or_solid_decodes_as_null() {
assert_eq!(
decode_material_values(GeometryType::Solid, &[2], &[2, NULL], &[0, 1]),
json!([[0, 1], null])
);
assert_eq!(
decode_material_values(GeometryType::CompositeSolid, &[1, NULL], &[2], &[0, 1]),
json!([[[0, 1]], null])
);
}
#[test]
fn test_decode_textures() -> Result<()> {
assert_eq!(
decode_texture_values(
GeometryType::MultiSurface,
&[],
&[3],
&[1, 1, 1],
&[4, 4, 4],
&[0, 10, 20, 30, 1, 11, 21, NULL, 2, 12, NULL, 32]
),
json!([[[0, 10, 20, 30]], [[1, 11, 21, null]], [[2, 12, null, 32]]])
);
assert_eq!(
decode_texture_values(
GeometryType::Solid,
&[2],
&[3, 2],
&[1, 1, 1, 1, 1],
&[4, 4, 4, 4, 4],
&[0, 10, 20, 30, 1, 11, 21, NULL, 2, 12, NULL, 32, 3, 13, 23, 33, 4, 14, 24, NULL]
),
json!([
[[[0, 10, 20, 30]], [[1, 11, 21, null]], [[2, 12, null, 32]]],
[[[3, 13, 23, 33]], [[4, 14, 24, null]]]
])
);
assert_eq!(
decode_texture_values(
GeometryType::CompositeSolid,
&[2, 1],
&[2, 2, 2],
&[1; 6],
&[3; 6],
&[0, 10, 20, 1, 11, NULL, 2, 12, 22, 3, NULL, 23, 4, 14, 24, 5, 15, 25]
),
json!([
[
[[[0, 10, 20]], [[1, 11, null]]],
[[[2, 12, 22]], [[3, null, 23]]]
],
[[[[4, 14, 24]], [[5, 15, 25]]]]
])
);
Ok(())
}
#[test]
fn identical_texture_arrays_decode_to_different_depths_per_type() {
let args = (
&[1u32][..],
&[1u32][..],
&[1u32][..],
&[3u32][..],
&[0u32, 10, 20][..],
);
assert_eq!(
decode_texture_values(GeometryType::Solid, args.0, args.1, args.2, args.3, args.4),
json!([[[[0, 10, 20]]]])
);
assert_eq!(
decode_texture_values(
GeometryType::MultiSolid,
args.0,
args.1,
args.2,
args.3,
args.4
),
json!([[[[[0, 10, 20]]]]])
);
}
#[test]
fn an_unknown_geometry_tag_is_an_error_and_never_a_solid() {
for (tag, name) in [
(GeometryType::MultiPoint, "MultiPoint"),
(GeometryType::MultiLineString, "MultiLineString"),
(GeometryType::MultiSurface, "MultiSurface"),
(GeometryType::CompositeSurface, "CompositeSurface"),
(GeometryType::Solid, "Solid"),
(GeometryType::MultiSolid, "MultiSolid"),
(GeometryType::CompositeSolid, "CompositeSolid"),
(GeometryType::GeometryInstance, "GeometryInstance"),
] {
assert_eq!(tag.to_str().unwrap(), name);
assert!(tag.to_cj().is_ok());
}
let unknown = GeometryType(GeometryType::ENUM_MAX + 1);
assert!(
matches!(
unknown.to_str(),
Err(Error::UnknownEnumTag("GeometryType", _))
),
"an unknown geometry tag must be reported, not spelled `Solid`"
);
assert!(matches!(
unknown.to_cj(),
Err(Error::UnknownEnumTag("GeometryType", _))
));
}
#[test]
fn an_unnameable_semantic_surface_tag_becomes_a_plus_prefixed_extension() {
assert_eq!(
to_cj_surface_type(FbSurfaceType::ExtraSemanticSurface, Some("+ThermalSurface")),
SemanticSurfaceType::Extension("+ThermalSurface".to_string())
);
for tag in [
FbSurfaceType::ExtraSemanticSurface,
FbSurfaceType(FbSurfaceType::ENUM_MAX + 1),
] {
let SemanticSurfaceType::Extension(name) = to_cj_surface_type(tag, None) else {
panic!("an unnameable surface tag must become an Extension");
};
assert_eq!(name, "+GenericSurface");
assert!(
name.starts_with('+'),
"{name} must be a valid Extension name"
);
assert_ne!(name, "ExtraSemanticSurface");
}
}
}