use std::fmt;
use crate::model::component::capability::{CapabilityKind, CapabilityRole, StructuralKind};
use crate::model::identity::{
CapabilityId, CapabilityRef, ComponentInstanceId, ComponentTypeId, JointId, LinkId,
MODULE_INSTANCE_SEPARATOR,
};
use crate::model::robot::KinematicKind;
use crate::model::structure::JointKind;
#[derive(Debug, thiserror::Error)]
pub enum ModelError {
#[error("{kind} '{value}' is not normalized")]
NotNormalized { kind: IdentifierKind, value: String },
#[error("{kind} '{value}' must not contain reserved separator '{MODULE_INSTANCE_SEPARATOR}'")]
ReservedSeparator { kind: IdentifierKind, value: String },
#[error("capability reference '{value}' must use component.capability")]
MalformedCapabilityReference { value: String },
#[error("invalid logical asset id '{value}'")]
MalformedAssetId { value: String },
#[error("unknown capability '{reference}'")]
UnknownCapability { reference: CapabilityRef },
#[error("capability '{reference}' must reference a {expected}, found {actual}")]
CapabilityKindMismatch {
reference: CapabilityRef,
expected: CapabilityKind,
actual: CapabilityKind,
},
#[error("capability '{reference}' must target a {expected}")]
CapabilityTargetKind {
reference: CapabilityRef,
expected: StructuralKind,
},
#[error("{kind} target '{id}' for capability '{reference}' not found")]
UnknownBoundTarget {
reference: CapabilityRef,
kind: StructuralKind,
id: String,
},
#[error(
"component type '{component_type}' capability '{capability_id}' references unknown {kind} '{id}'"
)]
UnknownDeclaredTarget {
component_type: ComponentTypeId,
capability_id: CapabilityId,
kind: StructuralKind,
id: String,
},
#[error("component '{instance}' references unknown component type '{component_type}'")]
UnknownComponentType {
instance: ComponentInstanceId,
component_type: ComponentTypeId,
},
#[error("component '{instance}' references unknown mount link '{link}'")]
UnknownMountLink {
instance: ComponentInstanceId,
link: LinkId,
},
#[error("component '{instance}' capability '{capability_id}' direction sign must be -1 or 1")]
DirectionSign {
instance: ComponentInstanceId,
capability_id: CapabilityId,
value: i8,
},
#[error(
"component '{instance}' direction sign references unknown capability '{capability_id}'"
)]
UnknownDirectionSignCapability {
instance: ComponentInstanceId,
capability_id: CapabilityId,
},
#[error(
"component '{instance}' role assignment references unknown capability '{capability_id}'"
)]
UnknownRoleCapability {
instance: ComponentInstanceId,
capability_id: CapabilityId,
},
#[error("capability '{capability_id}' has an empty role assignment")]
EmptyCapabilityRoles { capability_id: CapabilityId },
#[error("capability '{capability_id}' repeats role '{role}'")]
DuplicateCapabilityRole {
capability_id: CapabilityId,
role: CapabilityRole,
},
#[error("simulation capability '{component_type}.{capability_id}' has no component capability")]
SimulationWithoutCapability {
component_type: ComponentTypeId,
capability_id: CapabilityId,
},
#[error(
"simulation capability '{component_type}.{capability_id}' kind does not match component"
)]
SimulationCapabilityKindMismatch {
component_type: ComponentTypeId,
capability_id: CapabilityId,
simulated: CapabilityKind,
declared: CapabilityKind,
},
#[error("{owner} joint '{joint}' uses unsupported runtime kind '{kind:?}'")]
UnsupportedJointKind {
owner: JointOwner,
joint: JointId,
kind: JointKind,
},
#[error("motion {field} must be finite, positive, and fit in f32")]
MotionLimit { field: MotionLimitField },
#[error("{kinematics} {field} must be finite and positive")]
KinematicScalar {
kinematics: KinematicKind,
field: KinematicScalarField,
},
#[error("stock safety footprint cannot include movable joint '{joint}'")]
FootprintMovableJoint { joint: JointId },
#[error("stock safety footprint does not support mesh collision on link '{link}'")]
FootprintMesh { link: LinkId },
#[error("stock safety footprint contains a non-finite value")]
FootprintNonFinite,
#[error("stock safety footprint radius must be finite and positive")]
FootprintRadius,
#[error(transparent)]
Structure(#[from] StructureError),
}
impl From<crate::identity::TopologyIdError> for ModelError {
fn from(error: crate::identity::TopologyIdError) -> Self {
use crate::identity::TopologyIdError;
match error {
TopologyIdError::Robot(value) => Self::NotNormalized {
kind: IdentifierKind::RobotId,
value,
},
TopologyIdError::ComponentInstance(value) => Self::NotNormalized {
kind: IdentifierKind::ComponentInstance,
value,
},
TopologyIdError::Service(value) => Self::NotNormalized {
kind: IdentifierKind::Service,
value,
},
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum StructureError {
#[error("duplicate {kind} identity '{name}'")]
DuplicateIdentity { kind: StructuralKind, name: String },
#[error("joint '{joint}' references unknown {role} link '{link}'")]
UnknownJointLink {
joint: JointId,
role: LinkRole,
link: LinkId,
},
#[error("link '{link}' is the child of multiple joints")]
MultipleParentJoints { link: LinkId },
#[error("joint '{joint}' cannot use '{link}' as both parent and child")]
SelfReferentialJoint { joint: JointId, link: LinkId },
#[error("structure must have exactly one root link, found {found}")]
RootLinkCount { found: usize },
#[error("structure contains a joint cycle involving '{link}'")]
JointCycle { link: LinkId },
#[error("{owner} pose must be finite")]
Pose { owner: PoseOwner },
#[error("link '{link}' mass must be finite and non-negative")]
Mass { link: LinkId },
#[error("link '{link}' inertia must be finite and positive semidefinite")]
Inertia { link: LinkId },
#[error("link '{link}' geometry dimensions must be finite and positive")]
Geometry { link: LinkId },
#[error("joint '{joint}' axis must be finite")]
AxisNotFinite { joint: JointId },
#[error("joint '{joint}' axis must be non-zero")]
AxisNotOriented { joint: JointId },
#[error("joint '{joint}' limits must be finite with lower <= upper")]
JointLimits { joint: JointId },
#[error("joint '{joint}' dynamics must be finite and non-negative")]
JointDynamics { joint: JointId },
#[error("joint '{joint}' mimics unknown joint '{mimicked}'")]
UnknownMimicJoint { joint: JointId, mimicked: JointId },
#[error("joint '{joint}' safety limits must be finite with lower <= upper")]
JointSafety { joint: JointId },
#[error("structure root link must be '{expected}', found '{found}'")]
RootLinkName { expected: LinkId, found: LinkId },
#[error("structure must attach 'base_link' under 'base_footprint' with a fixed joint")]
MissingBaseLink,
#[error("structure must attach 'base_link' directly under 'base_footprint' with a fixed joint")]
MisattachedBaseLink,
#[error("canonical structure document is invalid: {0}")]
Document(#[from] serde_json::Error),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum IdentifierKind {
RobotId,
RobotLink,
RobotJoint,
ComponentType,
ComponentInstance,
Capability,
Service,
}
impl fmt::Display for IdentifierKind {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::RobotId => "robot id",
Self::RobotLink => "robot link",
Self::RobotJoint => "robot joint",
Self::ComponentType => "component type",
Self::ComponentInstance => "component instance id",
Self::Capability => "capability id",
Self::Service => "service id",
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum JointOwner {
Robot,
ComponentType(ComponentTypeId),
}
impl fmt::Display for JointOwner {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Robot => formatter.write_str("robot"),
Self::ComponentType(component_type) => write!(formatter, "{component_type}"),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LinkRole {
Parent,
Child,
}
impl fmt::Display for LinkRole {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::Parent => "parent",
Self::Child => "child",
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PoseOwner {
LinkInertial(LinkId),
LinkVisual(LinkId),
LinkCollision(LinkId),
Joint(JointId),
}
impl fmt::Display for PoseOwner {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::LinkInertial(link) => write!(formatter, "link '{link}' inertial"),
Self::LinkVisual(link) => write!(formatter, "link '{link}' visual"),
Self::LinkCollision(link) => write!(formatter, "link '{link}' collision"),
Self::Joint(joint) => write!(formatter, "joint '{joint}'"),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MotionLimitField {
MaxLinearSpeedMps,
MaxAngularSpeedRadps,
}
impl fmt::Display for MotionLimitField {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::MaxLinearSpeedMps => "max_linear_speed_mps",
Self::MaxAngularSpeedRadps => "max_angular_speed_radps",
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum KinematicScalarField {
WheelRadiusM,
WheelBaseM,
TrackM,
MaxSteeringAngleRad,
}
impl fmt::Display for KinematicScalarField {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::WheelRadiusM => "wheel_radius_m",
Self::WheelBaseM => "wheel_base_m",
Self::TrackM => "track_m",
Self::MaxSteeringAngleRad => "max_steering_angle_rad",
})
}
}