use std::collections::TryReserveError;
use crate::{
AssetChainEntry, DiagnosticCode, DiagnosticDetails, DiagnosticSeverity, LogicalBounds,
Measurement, MetadataComponent, MetadataOperation, NestedPath, OutputFormat, Recovery,
ResourceValue, ValueKind,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum FallbackRecipe {
UnknownElement,
MalformedElement,
KnownElementUnavailable,
OfficialImage,
OfficialImageError,
OfficialForbiddenResource,
OfficialEmbeddableOffline,
}
impl FallbackRecipe {
pub(crate) const ALL: [Self; 7] = [
Self::UnknownElement,
Self::MalformedElement,
Self::KnownElementUnavailable,
Self::OfficialImage,
Self::OfficialImageError,
Self::OfficialForbiddenResource,
Self::OfficialEmbeddableOffline,
];
pub(crate) const fn id(self) -> &'static str {
match self {
Self::UnknownElement => "renderer.unknown-element.v1",
Self::MalformedElement => "renderer.malformed-element.v1",
Self::KnownElementUnavailable => "renderer.known-element-unavailable.v1",
Self::OfficialImage => {
"official.image@sha256:0a04dbba7ed6a0ce33aa61d9abf02b13685715241022483139b28eb666b8219d"
}
Self::OfficialImageError => {
"official.image-error@sha256:f3c63c8600147961d006c0eea92ea6aff87bdc54711a2368af895a78154919e9"
}
Self::OfficialForbiddenResource => {
"official.forbidden-resource@sha256:4452f99158233e3703e46dbbb83f6900a894fbceacd3e98b141332d552e22827"
}
Self::OfficialEmbeddableOffline => "official.embeddable-offline@sha256:d1d9207be2a4022886b9916ef0068b654c47f64d9ea59cd6d31d6432c5eecb01",
}
}
pub(crate) fn from_id(id: &str) -> Option<Self> {
Self::ALL.into_iter().find(|recipe| recipe.id() == id)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum LocalRecovery {
Placeholder,
Omitted,
}
impl LocalRecovery {
pub(crate) const fn placeholder_if(replaced: bool) -> Self {
if replaced {
Self::Placeholder
} else {
Self::Omitted
}
}
pub(crate) const fn recovery(self) -> Recovery {
match self {
Self::Placeholder => Recovery::Placeholder,
Self::Omitted => Recovery::Omitted,
}
}
const fn recipe(self, placeholder: FallbackRecipe) -> Option<FallbackRecipe> {
match self {
Self::Placeholder => Some(placeholder),
Self::Omitted => None,
}
}
fn manifested(
recovery: Recovery,
recipe: Option<FallbackRecipe>,
placeholder: FallbackRecipe,
) -> Option<Self> {
match (recovery, recipe) {
(Recovery::Placeholder, Some(recipe)) if recipe == placeholder => {
Some(Self::Placeholder)
}
(Recovery::Omitted, None) => Some(Self::Omitted),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum ImageRecovery {
OfficialImage,
OfficialImageError,
Omitted,
}
impl ImageRecovery {
pub(crate) const fn for_format(format: OutputFormat, error_status: bool) -> Self {
match format {
OutputFormat::Png if error_status => Self::OfficialImageError,
OutputFormat::Png => Self::OfficialImage,
OutputFormat::Svg | OutputFormat::Pdf => Self::Omitted,
}
}
const fn local(self) -> LocalRecovery {
match self {
Self::OfficialImage | Self::OfficialImageError => LocalRecovery::Placeholder,
Self::Omitted => LocalRecovery::Omitted,
}
}
const fn recipe(self) -> Option<FallbackRecipe> {
match self {
Self::OfficialImage => Some(FallbackRecipe::OfficialImage),
Self::OfficialImageError => Some(FallbackRecipe::OfficialImageError),
Self::Omitted => None,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum LocalLimit {
GeometrySegmentsPerElement,
TextScalarsPerElement,
ElementWorkUnits,
AssetBytes,
AssetWorkUnits,
RasterDimension,
DecodedPixelsPerAsset,
NestedSvgNodes,
NestedAssetDepth,
CoordinateMagnitude,
}
impl LocalLimit {
pub(crate) const fn code(self) -> DiagnosticCode {
match self {
Self::GeometrySegmentsPerElement | Self::TextScalarsPerElement => {
DiagnosticCode::RESOURCE_ELEMENT_COMPLEXITY_REPLACED
}
Self::ElementWorkUnits => DiagnosticCode::RESOURCE_ELEMENT_WORK_EXCEEDED,
Self::AssetBytes => DiagnosticCode::RESOURCE_ASSET_BYTES_EXCEEDED,
Self::AssetWorkUnits => DiagnosticCode::RESOURCE_ASSET_WORK_EXCEEDED,
Self::RasterDimension => DiagnosticCode::RESOURCE_IMAGE_DIMENSIONS_EXCEEDED,
Self::DecodedPixelsPerAsset => DiagnosticCode::RESOURCE_IMAGE_PIXELS_EXCEEDED,
Self::NestedSvgNodes => DiagnosticCode::RESOURCE_NESTED_SVG_COMPLEXITY_EXCEEDED,
Self::NestedAssetDepth => DiagnosticCode::RESOURCE_NESTED_ASSET_DEPTH_EXCEEDED,
Self::CoordinateMagnitude => DiagnosticCode::RESOURCE_COORDINATE_RANGE_EXCEEDED,
}
}
pub(crate) const fn field(self) -> &'static str {
match self {
Self::GeometrySegmentsPerElement => "max_geometry_segments_per_element",
Self::TextScalarsPerElement => "max_text_scalars_per_element",
Self::ElementWorkUnits => "max_element_work_units",
Self::AssetBytes => "max_asset_bytes",
Self::AssetWorkUnits => "max_asset_work_units",
Self::RasterDimension => "max_raster_dimension",
Self::DecodedPixelsPerAsset => "max_decoded_pixels_per_asset",
Self::NestedSvgNodes => "max_nested_svg_nodes",
Self::NestedAssetDepth => "max_nested_asset_depth",
Self::CoordinateMagnitude => "max_coordinate_magnitude",
}
}
pub(crate) const fn value_kind(self) -> ValueKind {
match self {
Self::GeometrySegmentsPerElement
| Self::TextScalarsPerElement
| Self::NestedSvgNodes => ValueKind::Count,
Self::DecodedPixelsPerAsset => ValueKind::Pixels,
Self::ElementWorkUnits | Self::AssetWorkUnits => ValueKind::WorkUnits,
Self::AssetBytes => ValueKind::Bytes,
Self::RasterDimension => ValueKind::Dimension,
Self::NestedAssetDepth => ValueKind::Depth,
Self::CoordinateMagnitude => ValueKind::Coordinate,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) enum ImageFailure {
ForbiddenResource,
MissingFile,
MalformedFileRecord,
UnsupportedFormat,
DecodeFailed {
media_type: &'static str,
},
LimitCrossed {
limit: LocalLimit,
configured: ResourceValue,
attempted: crate::ResourceMeasurement,
},
}
impl ImageFailure {
pub(crate) const fn code(self) -> DiagnosticCode {
match self {
Self::ForbiddenResource => DiagnosticCode::IMAGE_FORBIDDEN_RESOURCE,
Self::MissingFile => DiagnosticCode::IMAGE_MISSING_FILE,
Self::MalformedFileRecord => DiagnosticCode::ASSET_MALFORMED_FILE_RECORD,
Self::UnsupportedFormat => DiagnosticCode::IMAGE_UNSUPPORTED_FORMAT,
Self::DecodeFailed { .. } => DiagnosticCode::IMAGE_DECODE_FAILED,
Self::LimitCrossed { limit, .. } => limit.code(),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum MetadataFailure {
ProvenanceSerialization,
DiagnosticSummarySerialization,
SceneSerialization,
SceneCompression,
SceneEmbedding,
ManifestSerialization,
ManifestEmbedding,
}
impl MetadataFailure {
pub(crate) const fn code(self) -> DiagnosticCode {
match self {
Self::ProvenanceSerialization => {
DiagnosticCode::METADATA_PROVENANCE_SERIALIZATION_FAILED
}
Self::DiagnosticSummarySerialization => {
DiagnosticCode::METADATA_DIAGNOSTIC_SUMMARY_SERIALIZATION_FAILED
}
Self::SceneSerialization => DiagnosticCode::METADATA_SCENE_SERIALIZATION_FAILED,
Self::SceneCompression => DiagnosticCode::METADATA_SCENE_COMPRESSION_FAILED,
Self::SceneEmbedding => DiagnosticCode::METADATA_SCENE_EMBEDDING_FAILED,
Self::ManifestSerialization => DiagnosticCode::METADATA_MANIFEST_SERIALIZATION_FAILED,
Self::ManifestEmbedding => DiagnosticCode::METADATA_MANIFEST_EMBEDDING_FAILED,
}
}
pub(crate) const fn component(self) -> MetadataComponent {
match self {
Self::ProvenanceSerialization => MetadataComponent::Provenance,
Self::DiagnosticSummarySerialization => MetadataComponent::DiagnosticSummary,
Self::SceneSerialization | Self::SceneCompression | Self::SceneEmbedding => {
MetadataComponent::EmbeddedScene
}
Self::ManifestSerialization | Self::ManifestEmbedding => MetadataComponent::Manifest,
}
}
pub(crate) const fn operation(self) -> MetadataOperation {
match self {
Self::SceneCompression => MetadataOperation::Compression,
Self::SceneEmbedding | Self::ManifestEmbedding => MetadataOperation::Embedding,
Self::ProvenanceSerialization
| Self::DiagnosticSummarySerialization
| Self::SceneSerialization
| Self::ManifestSerialization => MetadataOperation::Serialization,
}
}
}
#[derive(Debug)]
pub(crate) enum DiagnosticKind<'a> {
MissingElementId,
DuplicateElementId,
MissingTimestamp,
ZeroRoughSeed,
RelationshipCycle,
UnsupportedElementType(LocalRecovery),
MalformedElement(LocalRecovery),
UnsupportedProperty,
EditorPropertyIgnored,
UnsupportedPropertyValue {
recovery: Recovery,
recipe: Option<FallbackRecipe>,
},
KnownElementReplaced(LocalRecovery),
SupplementConflict { asset_id: &'a str },
MediaTypeMismatch {
asset_id: &'a str,
claimed_media_type: Option<&'a str>,
validated_media_type: Option<&'a str>,
},
ImageUnavailable {
failure: ImageFailure,
asset_id: &'a str,
recovery: ImageRecovery,
},
ForbiddenResource {
asset_id: &'a str,
recovery: LocalRecovery,
},
AnimationFlattened {
asset_id: &'a str,
media_type: &'a str,
},
UnsafeSvgContentRemoved {
asset_chain: Vec<AssetChainEntry>,
paths: Vec<NestedPath>,
},
NestedForbiddenResource {
asset_chain: Vec<AssetChainEntry>,
paths: Vec<NestedPath>,
recovery: LocalRecovery,
},
NestedMediaTypeMismatch {
asset_chain: Vec<AssetChainEntry>,
paths: Vec<NestedPath>,
claimed_media_type: String,
validated_media_type: String,
},
NestedImageRemoved {
failure: ImageFailure,
asset_chain: Vec<AssetChainEntry>,
paths: Vec<NestedPath>,
},
NestedResourceLimit {
limit: LocalLimit,
configured: ResourceValue,
attempted: crate::ResourceMeasurement,
asset_chain: Vec<AssetChainEntry>,
paths: Vec<NestedPath>,
recovery: LocalRecovery,
},
UnsafeLinkOmitted,
InvalidColor,
LiberationSansSubstituted,
NotoEmojiSubstituted,
EmbeddableNotoEmojiSubstituted,
MissingGlyph { codepoint: u32 },
LocalLimitCrossed {
limit: LocalLimit,
configured: ResourceValue,
attempted: crate::ResourceMeasurement,
recovery: LocalRecovery,
},
OptionalMetadataOmitted {
field: &'static str,
configured: u64,
value_kind: ValueKind,
measurement: Measurement<u64>,
},
MetadataOmitted(MetadataFailure),
#[cfg_attr(not(feature = "pdf"), allow(dead_code))]
PdfRasterFallback {
logical_bounds: LogicalBounds,
pixel_width: u64,
pixel_height: u64,
causes: Vec<NestedPath>,
},
}
impl DiagnosticKind<'_> {
pub(crate) fn manifested(code: &str, recovery: Recovery, recipe: Option<&str>) -> Option<Self> {
let recipe = match recipe {
Some(recipe) => Some(FallbackRecipe::from_id(recipe)?),
None => None,
};
Some(match DiagnosticCode::from_stable_str(code)? {
DiagnosticCode::COMPATIBILITY_UNSUPPORTED_PROPERTY_VALUE => {
Self::UnsupportedPropertyValue { recovery, recipe }
}
DiagnosticCode::COLOR_INVALID
if recovery == Recovery::Substituted && recipe.is_none() =>
{
Self::InvalidColor
}
DiagnosticCode::COMPATIBILITY_MALFORMED_ELEMENT => Self::MalformedElement(
LocalRecovery::manifested(recovery, recipe, FallbackRecipe::MalformedElement)?,
),
DiagnosticCode::COMPATIBILITY_UNSUPPORTED_ELEMENT_TYPE => Self::UnsupportedElementType(
LocalRecovery::manifested(recovery, recipe, FallbackRecipe::UnknownElement)?,
),
_ => return None,
})
}
pub(crate) const fn code(&self) -> DiagnosticCode {
match self {
Self::NestedMediaTypeMismatch { .. } => DiagnosticCode::ASSET_MEDIA_TYPE_MISMATCH,
Self::MissingElementId => DiagnosticCode::NORMALIZATION_MISSING_ELEMENT_ID,
Self::DuplicateElementId => DiagnosticCode::NORMALIZATION_DUPLICATE_ELEMENT_ID,
Self::MissingTimestamp => DiagnosticCode::NORMALIZATION_MISSING_TIMESTAMP,
Self::ZeroRoughSeed => DiagnosticCode::NORMALIZATION_ZERO_ROUGH_SEED,
Self::RelationshipCycle => DiagnosticCode::NORMALIZATION_RELATIONSHIP_CYCLE,
Self::UnsupportedElementType(_) => {
DiagnosticCode::COMPATIBILITY_UNSUPPORTED_ELEMENT_TYPE
}
Self::MalformedElement(_) => DiagnosticCode::COMPATIBILITY_MALFORMED_ELEMENT,
Self::UnsupportedProperty | Self::EditorPropertyIgnored => {
DiagnosticCode::COMPATIBILITY_UNSUPPORTED_PROPERTY
}
Self::UnsupportedPropertyValue { .. } => {
DiagnosticCode::COMPATIBILITY_UNSUPPORTED_PROPERTY_VALUE
}
Self::KnownElementReplaced(_) => DiagnosticCode::COMPATIBILITY_KNOWN_ELEMENT_REPLACED,
Self::SupplementConflict { .. } => DiagnosticCode::ASSET_SUPPLEMENT_CONFLICT,
Self::MediaTypeMismatch { .. } => DiagnosticCode::ASSET_MEDIA_TYPE_MISMATCH,
Self::ImageUnavailable { failure, .. } | Self::NestedImageRemoved { failure, .. } => {
failure.code()
}
Self::NestedResourceLimit { limit, .. } => limit.code(),
Self::ForbiddenResource { .. } | Self::NestedForbiddenResource { .. } => {
DiagnosticCode::IMAGE_FORBIDDEN_RESOURCE
}
Self::AnimationFlattened { .. } => DiagnosticCode::IMAGE_ANIMATION_FLATTENED,
Self::UnsafeSvgContentRemoved { .. } => {
DiagnosticCode::IMAGE_UNSAFE_SVG_CONTENT_REMOVED
}
Self::UnsafeLinkOmitted => DiagnosticCode::LINK_UNSAFE_OMITTED,
Self::InvalidColor => DiagnosticCode::COLOR_INVALID,
Self::LiberationSansSubstituted => DiagnosticCode::FONT_LIBERATION_SANS_SUBSTITUTED,
Self::NotoEmojiSubstituted | Self::EmbeddableNotoEmojiSubstituted => {
DiagnosticCode::FONT_NOTO_EMOJI_SUBSTITUTED
}
Self::MissingGlyph { .. } => DiagnosticCode::FONT_MISSING_GLYPH,
Self::LocalLimitCrossed { limit, .. } => limit.code(),
Self::OptionalMetadataOmitted { .. } => {
DiagnosticCode::RESOURCE_OPTIONAL_METADATA_OMITTED
}
Self::MetadataOmitted(failure) => failure.code(),
Self::PdfRasterFallback { .. } => DiagnosticCode::PDF_RASTER_FALLBACK,
}
}
pub(crate) const fn severity(&self) -> DiagnosticSeverity {
match self {
Self::AnimationFlattened { .. }
| Self::MissingTimestamp
| Self::EditorPropertyIgnored
| Self::LiberationSansSubstituted
| Self::NotoEmojiSubstituted
| Self::EmbeddableNotoEmojiSubstituted => DiagnosticSeverity::Info,
_ => DiagnosticSeverity::Warning,
}
}
pub(crate) fn into_details(self) -> Result<DiagnosticDetails, TryReserveError> {
Ok(match self {
Self::NestedMediaTypeMismatch {
asset_chain,
paths,
claimed_media_type,
validated_media_type,
} => DiagnosticDetails::NestedAssetClaim {
asset_chain,
paths,
claimed_media_type,
validated_media_type,
recovery: Recovery::Repaired,
},
Self::MissingElementId
| Self::DuplicateElementId
| Self::MissingTimestamp
| Self::ZeroRoughSeed => compatibility(Recovery::Repaired, None)?,
Self::RelationshipCycle => compatibility(Recovery::Removed, None)?,
Self::UnsupportedElementType(recovery) => compatibility(
recovery.recovery(),
recovery.recipe(FallbackRecipe::UnknownElement),
)?,
Self::MalformedElement(recovery) => compatibility(
recovery.recovery(),
recovery.recipe(FallbackRecipe::MalformedElement),
)?,
Self::UnsupportedProperty | Self::EditorPropertyIgnored => {
compatibility(Recovery::Ignored, None)?
}
Self::UnsupportedPropertyValue { recovery, recipe } => compatibility(recovery, recipe)?,
Self::KnownElementReplaced(recovery) => compatibility(
recovery.recovery(),
recovery.recipe(FallbackRecipe::KnownElementUnavailable),
)?,
Self::SupplementConflict { asset_id } => {
asset(asset_id, None, None, Recovery::Ignored, None)?
}
Self::MediaTypeMismatch {
asset_id,
claimed_media_type,
validated_media_type,
} => asset(
asset_id,
claimed_media_type,
validated_media_type,
Recovery::Repaired,
None,
)?,
Self::ImageUnavailable {
failure:
ImageFailure::LimitCrossed {
limit,
configured,
attempted,
},
recovery,
..
} => resource_limit(limit, configured, attempted, recovery.local()),
Self::ImageUnavailable {
failure,
asset_id,
recovery,
} => asset(
asset_id,
None,
match failure {
ImageFailure::DecodeFailed { media_type } => Some(media_type),
ImageFailure::MissingFile
| ImageFailure::ForbiddenResource
| ImageFailure::MalformedFileRecord
| ImageFailure::UnsupportedFormat
| ImageFailure::LimitCrossed { .. } => None,
},
recovery.local().recovery(),
recovery.recipe(),
)?,
Self::ForbiddenResource { asset_id, recovery } => asset(
asset_id,
None,
None,
recovery.recovery(),
recovery.recipe(FallbackRecipe::OfficialForbiddenResource),
)?,
Self::AnimationFlattened {
asset_id,
media_type,
} => asset(asset_id, None, Some(media_type), Recovery::Flattened, None)?,
Self::UnsafeSvgContentRemoved { asset_chain, paths }
| Self::NestedImageRemoved {
asset_chain, paths, ..
} => DiagnosticDetails::NestedSource {
asset_chain,
paths,
recovery: Recovery::Removed,
},
Self::NestedForbiddenResource {
asset_chain,
paths,
recovery,
} => DiagnosticDetails::NestedSource {
asset_chain,
paths,
recovery: recovery.recovery(),
},
Self::NestedResourceLimit {
limit,
configured,
attempted,
asset_chain,
paths,
recovery,
} => DiagnosticDetails::NestedResourceLimit {
field: limit.field(),
configured,
value_kind: limit.value_kind(),
measurement: attempted,
asset_chain,
paths,
recovery: recovery.recovery(),
recipe_id: if recovery == LocalRecovery::Placeholder {
Some(try_owned("official.nested-resource@sha256:0fb00e2eda840ab048f9e0fc0698bb4b43d5618d6f67880149ce164412387677")?)
} else {
None
},
},
Self::UnsafeLinkOmitted => compatibility(Recovery::Omitted, None)?,
Self::InvalidColor | Self::LiberationSansSubstituted | Self::NotoEmojiSubstituted => {
compatibility(Recovery::Substituted, None)?
}
Self::EmbeddableNotoEmojiSubstituted => compatibility(
Recovery::Substituted,
Some(FallbackRecipe::OfficialEmbeddableOffline),
)?,
Self::MissingGlyph { codepoint } => DiagnosticDetails::MissingGlyph {
codepoint,
recovery: Recovery::Substituted,
},
Self::LocalLimitCrossed {
limit,
configured,
attempted,
recovery,
} => resource_limit(limit, configured, attempted, recovery),
Self::OptionalMetadataOmitted {
field,
configured,
value_kind,
measurement,
} => DiagnosticDetails::ResourceLimit {
field,
configured: ResourceValue::Integer(configured),
value_kind,
measurement: match measurement {
Measurement::Attempted { value } => Measurement::Attempted {
value: ResourceValue::Integer(value),
},
Measurement::AtLeast { value } => Measurement::AtLeast {
value: ResourceValue::Integer(value),
},
},
recovery: Some(Recovery::MetadataOmitted),
},
Self::MetadataOmitted(failure) => DiagnosticDetails::Metadata {
component: failure.component(),
operation: failure.operation(),
recovery: Recovery::MetadataOmitted,
},
Self::PdfRasterFallback {
logical_bounds,
pixel_width,
pixel_height,
causes,
} => DiagnosticDetails::PdfRasterFallback {
recovery: Some(Recovery::Rasterized),
logical_bounds: Some(logical_bounds),
pixel_width: Some(pixel_width),
pixel_height: Some(pixel_height),
causes,
},
})
}
}
fn compatibility(
recovery: Recovery,
recipe: Option<FallbackRecipe>,
) -> Result<DiagnosticDetails, TryReserveError> {
Ok(DiagnosticDetails::Compatibility {
recovery,
recipe_id: recipe.map(|recipe| try_owned(recipe.id())).transpose()?,
})
}
fn asset(
asset_id: &str,
claimed_media_type: Option<&str>,
validated_media_type: Option<&str>,
recovery: Recovery,
recipe: Option<FallbackRecipe>,
) -> Result<DiagnosticDetails, TryReserveError> {
Ok(DiagnosticDetails::Asset {
asset_id: try_owned(asset_id)?,
claimed_media_type: claimed_media_type.map(try_owned).transpose()?,
validated_media_type: validated_media_type.map(try_owned).transpose()?,
recovery,
recipe_id: recipe.map(|recipe| try_owned(recipe.id())).transpose()?,
})
}
fn resource_limit(
limit: LocalLimit,
configured: ResourceValue,
attempted: crate::ResourceMeasurement,
recovery: LocalRecovery,
) -> DiagnosticDetails {
DiagnosticDetails::ResourceLimit {
field: limit.field(),
configured,
value_kind: limit.value_kind(),
measurement: attempted,
recovery: Some(recovery.recovery()),
}
}
fn try_owned(value: &str) -> Result<String, TryReserveError> {
let mut owned = String::new();
owned.try_reserve_exact(value.len())?;
owned.push_str(value);
Ok(owned)
}
#[cfg(test)]
mod tests {
use std::collections::BTreeSet;
use std::path::Path;
use super::*;
use crate::scene_surface::SCENE_SURFACE_POLICY;
fn scene_surface_manifest() -> serde_json::Value {
let path = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../tests/oracle/scene-surfaces/revision-1.json");
let bytes = std::fs::read(&path).expect("the scene-surface manifest is checked in");
serde_json::from_slice(&bytes).expect("the scene-surface manifest is JSON")
}
fn manifest_strings<'a>(
manifest: &'a serde_json::Value,
table: &str,
member: &str,
) -> BTreeSet<&'a str> {
manifest[table]
.as_array()
.unwrap_or_else(|| panic!("{table} is a manifest table"))
.iter()
.map(|entry| {
entry[member]
.as_str()
.unwrap_or_else(|| panic!("{table}[].{member} is a string"))
})
.collect()
}
#[test]
fn registered_codes_are_exactly_the_manifest_registry() {
let manifest = scene_surface_manifest();
let manifested = manifest_strings(&manifest, "registryMappings", "code");
let registered = DiagnosticCode::ALL
.iter()
.map(|code| code.as_str())
.collect::<BTreeSet<_>>();
assert_eq!(registered.len(), DiagnosticCode::ALL.len());
assert_eq!(registered, manifested);
for code in DiagnosticCode::ALL {
assert_eq!(DiagnosticCode::from_stable_str(code.as_str()), Some(code));
}
assert_eq!(
DiagnosticCode::from_stable_str("scene.invalid-envelope"),
None
);
}
#[test]
fn generated_field_fallbacks_map_to_registry_kinds() {
const ASSET_SHAPED: [(&str, &str, &str); 3] = [
("file_record", "/$key", "asset.malformed-file-record"),
("file_record", "/mimeType", "asset.media-type-mismatch"),
(
"nested.local_svg_resource",
"/*",
"image.forbidden-resource",
),
];
let mut unmapped = Vec::new();
for field in SCENE_SURFACE_POLICY.fields {
let Some(code) = field.invalid.code else {
continue;
};
if code == "scene.invalid-envelope" {
assert_eq!(field.record_kind, "scene", "{}", field.path);
assert!(field.invalid.recovery.is_none(), "{}", field.path);
continue;
}
assert!(
DiagnosticCode::from_stable_str(code).is_some(),
"{} {} names unregistered code {code}",
field.record_kind,
field.path
);
let recoveries = field
.invalid
.recovery
.into_iter()
.chain(field.invalid.format_recoveries.into_iter().flatten());
for recovery in recoveries {
let recovery = Recovery::from_manifest(recovery)
.unwrap_or_else(|| panic!("{} names recovery {recovery}", field.path));
let kind = DiagnosticKind::manifested(code, recovery, field.invalid.recipe);
match kind {
Some(kind) => assert_eq!(kind.code().as_str(), code, "{}", field.path),
None => unmapped.push((field.record_kind, field.path, code)),
}
}
}
assert_eq!(unmapped, ASSET_SHAPED);
}
#[test]
fn recipe_identities_are_the_manifests() {
let manifest = scene_surface_manifest();
let renderer_owned =
manifest_strings(&manifest, "rendererOwnedRecipeReferences", "recipeId");
let official = manifest_strings(&manifest, "fallbackRecipes", "recipeId");
assert_eq!(
official,
crate::fonts::revision_1()
.fallback_recipe_identities
.iter()
.copied()
.collect::<BTreeSet<_>>()
);
for recipe in FallbackRecipe::ALL {
assert_eq!(FallbackRecipe::from_id(recipe.id()), Some(recipe));
let known = if recipe.id().starts_with("renderer.") {
renderer_owned.contains(recipe.id())
} else {
official.contains(recipe.id())
};
assert!(known, "{}", recipe.id());
}
assert_eq!(
renderer_owned.len(),
FallbackRecipe::ALL
.iter()
.filter(|recipe| recipe.id().starts_with("renderer."))
.count()
);
for field in SCENE_SURFACE_POLICY.fields {
if let Some(recipe) = field.invalid.recipe {
assert!(FallbackRecipe::from_id(recipe).is_some(), "{}", field.path);
}
}
}
#[test]
fn details_follow_the_registry_bindings() {
let details = |kind: DiagnosticKind<'_>| kind.into_details().expect("details allocate");
assert_eq!(
details(DiagnosticKind::MissingElementId),
DiagnosticDetails::Compatibility {
recovery: Recovery::Repaired,
recipe_id: None,
}
);
assert_eq!(
details(DiagnosticKind::RelationshipCycle).recovery(),
Some(Recovery::Removed)
);
assert_eq!(
details(DiagnosticKind::KnownElementReplaced(
LocalRecovery::Placeholder
)),
DiagnosticDetails::Compatibility {
recovery: Recovery::Placeholder,
recipe_id: Some(FallbackRecipe::KnownElementUnavailable.id().to_owned()),
}
);
assert_eq!(
details(DiagnosticKind::UnsupportedElementType(
LocalRecovery::Omitted
)),
DiagnosticDetails::Compatibility {
recovery: Recovery::Omitted,
recipe_id: None,
}
);
assert_eq!(
details(DiagnosticKind::ImageUnavailable {
failure: ImageFailure::DecodeFailed {
media_type: "image/webp",
},
asset_id: "asset",
recovery: ImageRecovery::for_format(OutputFormat::Png, true),
}),
DiagnosticDetails::Asset {
asset_id: "asset".to_owned(),
claimed_media_type: None,
validated_media_type: Some("image/webp".to_owned()),
recovery: Recovery::Placeholder,
recipe_id: Some(FallbackRecipe::OfficialImageError.id().to_owned()),
}
);
assert_eq!(
details(DiagnosticKind::ImageUnavailable {
failure: ImageFailure::LimitCrossed {
limit: LocalLimit::AssetBytes,
configured: ResourceValue::Integer(10),
attempted: Measurement::Attempted {
value: ResourceValue::Integer(11)
},
},
asset_id: "asset",
recovery: ImageRecovery::for_format(OutputFormat::Svg, true),
}),
DiagnosticDetails::ResourceLimit {
field: "max_asset_bytes",
configured: ResourceValue::Integer(10),
value_kind: ValueKind::Bytes,
measurement: Measurement::Attempted {
value: ResourceValue::Integer(11),
},
recovery: Some(Recovery::Omitted),
}
);
assert_eq!(
details(DiagnosticKind::MetadataOmitted(
MetadataFailure::SceneCompression
)),
DiagnosticDetails::Metadata {
component: MetadataComponent::EmbeddedScene,
operation: MetadataOperation::Compression,
recovery: Recovery::MetadataOmitted,
}
);
assert_eq!(
details(DiagnosticKind::MissingGlyph { codepoint: 0x1F600 }).recovery(),
Some(Recovery::Substituted)
);
}
#[test]
fn image_recovery_follows_the_format() {
assert_eq!(
ImageRecovery::for_format(OutputFormat::Png, false),
ImageRecovery::OfficialImage
);
assert_eq!(
ImageRecovery::for_format(OutputFormat::Png, true),
ImageRecovery::OfficialImageError
);
for format in [OutputFormat::Svg, OutputFormat::Pdf] {
assert_eq!(
ImageRecovery::for_format(format, true),
ImageRecovery::Omitted
);
assert_eq!(
ImageRecovery::for_format(format, false),
ImageRecovery::Omitted
);
}
}
#[test]
fn manifested_admits_only_registered_bindings() {
assert!(matches!(
DiagnosticKind::manifested(
"compatibility.unsupported-property-value",
Recovery::Placeholder,
Some(FallbackRecipe::KnownElementUnavailable.id()),
),
Some(DiagnosticKind::UnsupportedPropertyValue {
recovery: Recovery::Placeholder,
recipe: Some(FallbackRecipe::KnownElementUnavailable),
})
));
assert!(matches!(
DiagnosticKind::manifested("color.invalid", Recovery::Substituted, None),
Some(DiagnosticKind::InvalidColor)
));
assert!(DiagnosticKind::manifested("color.invalid", Recovery::Omitted, None).is_none());
assert!(matches!(
DiagnosticKind::manifested(
"compatibility.malformed-element",
Recovery::Placeholder,
Some(FallbackRecipe::MalformedElement.id()),
),
Some(DiagnosticKind::MalformedElement(LocalRecovery::Placeholder))
));
assert!(DiagnosticKind::manifested(
"compatibility.malformed-element",
Recovery::Placeholder,
Some(FallbackRecipe::UnknownElement.id()),
)
.is_none());
assert!(
DiagnosticKind::manifested("scene.invalid-envelope", Recovery::Omitted, None).is_none()
);
assert!(
DiagnosticKind::manifested("font.missing-glyph", Recovery::Substituted, None).is_none()
);
assert!(DiagnosticKind::manifested(
"compatibility.unsupported-property-value",
Recovery::Placeholder,
Some("renderer.future.v2"),
)
.is_none());
}
}