feat(client): add InjectDrcError API and CLI command

This commit is contained in:
Milind Sharma 2026-02-20 17:38:07 +08:00
parent 909007f74b
commit a896a1c38e
6 changed files with 304 additions and 17 deletions

View File

@ -41,9 +41,9 @@ Legend:
| Common (base) | 6 | 4 | 67% |
| Common editor/document | 23 | 12 | 52% |
| Project manager | 5 | 3 | 60% |
| Board editor (PCB) | 22 | 18 | 82% |
| Board editor (PCB) | 22 | 19 | 86% |
| Schematic editor (dedicated proto commands) | 0 | 0 | n/a |
| **Total** | **56** | **37** | **66%** |
| **Total** | **56** | **38** | **68%** |
### Common (base)
@ -112,7 +112,7 @@ Legend:
| `RefillZones` | Not yet | - |
| `GetPadShapeAsPolygon` | Implemented | `KiCadClient::get_pad_shape_as_polygon_raw`, `KiCadClient::get_pad_shape_as_polygon` |
| `CheckPadstackPresenceOnLayers` | Implemented | `KiCadClient::check_padstack_presence_on_layers_raw`, `KiCadClient::check_padstack_presence_on_layers` |
| `InjectDrcError` | Not yet | - |
| `InjectDrcError` | Implemented | `KiCadClient::inject_drc_error_raw`, `KiCadClient::inject_drc_error` |
| `GetVisibleLayers` | Implemented | `KiCadClient::get_visible_layers` |
| `SetVisibleLayers` | Implemented | `KiCadClient::set_visible_layers` |
| `GetActiveLayer` | Implemented | `KiCadClient::get_active_layer` |

View File

@ -261,6 +261,12 @@ Set editor appearance:
cargo run --bin kicad-ipc-cli -- set-appearance --inactive-layer-display hidden --net-color-display all --board-flip normal --ratsnest-display all-layers
```
Inject DRC marker:
```bash
cargo run --bin kicad-ipc-cli -- inject-drc-error --severity error --message "API marker test" --x-nm 1000000 --y-nm 1000000
```
Show typed netclass map:
```bash

View File

@ -9,7 +9,7 @@ use crate::model::board::{
ArcStartMidEndNm, BoardEditorAppearanceSettings, BoardEnabledLayers, BoardFlipMode,
BoardLayerClass, BoardLayerGraphicsDefault, BoardLayerInfo, BoardNet, BoardOriginKind,
BoardStackup, BoardStackupDielectricProperties, BoardStackupLayer, BoardStackupLayerType,
ColorRgba, GraphicsDefaults, InactiveLayerDisplayMode, NetClassBoardSettings,
ColorRgba, DrcSeverity, GraphicsDefaults, InactiveLayerDisplayMode, NetClassBoardSettings,
NetClassForNetEntry, NetClassInfo, NetClassType, NetColorDisplayMode, PadNetEntry,
PadShapeAsPolygonEntry, PadstackPresenceEntry, PadstackPresenceState, PcbArc,
PcbBoardGraphicShape, PcbBoardText, PcbBoardTextBox, PcbDimension, PcbField, PcbFootprint,
@ -65,6 +65,7 @@ const CMD_GET_NETCLASS_FOR_NETS: &str = "kiapi.board.commands.GetNetClassForNets
const CMD_GET_PAD_SHAPE_AS_POLYGON: &str = "kiapi.board.commands.GetPadShapeAsPolygon";
const CMD_CHECK_PADSTACK_PRESENCE_ON_LAYERS: &str =
"kiapi.board.commands.CheckPadstackPresenceOnLayers";
const CMD_INJECT_DRC_ERROR: &str = "kiapi.board.commands.InjectDrcError";
const CMD_GET_SELECTION: &str = "kiapi.common.commands.GetSelection";
const CMD_BEGIN_COMMIT: &str = "kiapi.common.commands.BeginCommit";
const CMD_END_COMMIT: &str = "kiapi.common.commands.EndCommit";
@ -95,6 +96,7 @@ const RES_BOARD_EDITOR_APPEARANCE_SETTINGS: &str =
const RES_NETCLASS_FOR_NETS_RESPONSE: &str = "kiapi.board.commands.NetClassForNetsResponse";
const RES_PAD_SHAPE_AS_POLYGON_RESPONSE: &str = "kiapi.board.commands.PadShapeAsPolygonResponse";
const RES_PADSTACK_PRESENCE_RESPONSE: &str = "kiapi.board.commands.PadstackPresenceResponse";
const RES_INJECT_DRC_ERROR_RESPONSE: &str = "kiapi.board.commands.InjectDrcErrorResponse";
const RES_VECTOR2: &str = "kiapi.common.types.Vector2";
const RES_SELECTION_RESPONSE: &str = "kiapi.common.commands.SelectionResponse";
const RES_BEGIN_COMMIT_RESPONSE: &str = "kiapi.common.commands.BeginCommitResponse";
@ -1047,6 +1049,46 @@ impl KiCadClient {
Ok(entries)
}
pub async fn inject_drc_error_raw(
&self,
severity: DrcSeverity,
message: impl Into<String>,
position: Option<Vector2Nm>,
item_ids: Vec<String>,
) -> Result<prost_types::Any, KiCadError> {
let board = self.current_board_document_proto().await?;
let command = board_commands::InjectDrcError {
board: Some(board),
severity: drc_severity_to_proto(severity),
message: message.into(),
position: position.map(vector2_nm_to_proto),
items: item_ids
.into_iter()
.map(|value| common_types::Kiid { value })
.collect(),
};
let response = self
.send_command(envelope::pack_any(&command, CMD_INJECT_DRC_ERROR))
.await?;
response_payload_as_any(response, RES_INJECT_DRC_ERROR_RESPONSE)
}
pub async fn inject_drc_error(
&self,
severity: DrcSeverity,
message: impl Into<String>,
position: Option<Vector2Nm>,
item_ids: Vec<String>,
) -> Result<Option<String>, KiCadError> {
let payload = self
.inject_drc_error_raw(severity, message, position, item_ids)
.await?;
let response: board_commands::InjectDrcErrorResponse =
decode_any(&payload, RES_INJECT_DRC_ERROR_RESPONSE)?;
Ok(response.marker.map(|marker| marker.value))
}
pub async fn get_board_stackup_raw(&self) -> Result<prost_types::Any, KiCadError> {
let command = board_commands::GetBoardStackup {
board: Some(self.current_board_document_proto().await?),
@ -1674,6 +1716,19 @@ fn board_origin_kind_to_proto(kind: BoardOriginKind) -> i32 {
}
}
fn drc_severity_to_proto(value: DrcSeverity) -> i32 {
match value {
DrcSeverity::Warning => board_commands::DrcSeverity::DrsWarning as i32,
DrcSeverity::Error => board_commands::DrcSeverity::DrsError as i32,
DrcSeverity::Exclusion => board_commands::DrcSeverity::DrsExclusion as i32,
DrcSeverity::Ignore => board_commands::DrcSeverity::DrsIgnore as i32,
DrcSeverity::Info => board_commands::DrcSeverity::DrsInfo as i32,
DrcSeverity::Action => board_commands::DrcSeverity::DrsAction as i32,
DrcSeverity::Debug => board_commands::DrcSeverity::DrsDebug as i32,
DrcSeverity::Undefined => board_commands::DrcSeverity::DrsUndefined as i32,
}
}
fn commit_action_to_proto(action: CommitAction) -> i32 {
match action {
CommitAction::Commit => common_commands::CommitAction::CmaCommit as i32,
@ -2854,12 +2909,12 @@ fn default_client_name() -> String {
mod tests {
use super::{
any_to_pretty_debug, board_editor_appearance_settings_to_proto, commit_action_to_proto,
ensure_item_request_ok, layer_to_model, map_commit_session, map_hit_test_result,
map_item_bounding_boxes, map_polygon_with_holes, model_document_to_proto,
normalize_socket_uri, pad_netlist_from_footprint_items, response_payload_as_any,
select_single_board_document, select_single_project_path, selection_item_detail,
summarize_item_details, summarize_selection, text_horizontal_alignment_to_proto,
text_spec_to_proto, PCB_OBJECT_TYPES,
drc_severity_to_proto, ensure_item_request_ok, layer_to_model, map_commit_session,
map_hit_test_result, map_item_bounding_boxes, map_polygon_with_holes,
model_document_to_proto, normalize_socket_uri, pad_netlist_from_footprint_items,
response_payload_as_any, select_single_board_document, select_single_project_path,
selection_item_detail, summarize_item_details, summarize_selection,
text_horizontal_alignment_to_proto, text_spec_to_proto, PCB_OBJECT_TYPES,
};
use crate::error::KiCadError;
use crate::model::common::{
@ -3027,6 +3082,18 @@ mod tests {
);
}
#[test]
fn drc_severity_to_proto_maps_known_variants() {
assert_eq!(
drc_severity_to_proto(crate::model::board::DrcSeverity::Warning),
crate::proto::kiapi::board::commands::DrcSeverity::DrsWarning as i32
);
assert_eq!(
drc_severity_to_proto(crate::model::board::DrcSeverity::Error),
crate::proto::kiapi::board::commands::DrcSeverity::DrsError as i32
);
}
#[test]
fn board_editor_appearance_settings_to_proto_maps_known_variants() {
let proto = board_editor_appearance_settings_to_proto(

View File

@ -24,7 +24,7 @@ pub use crate::model::board::{
ArcStartMidEndNm, BoardEditorAppearanceSettings, BoardEnabledLayers, BoardFlipMode,
BoardLayerClass, BoardLayerGraphicsDefault, BoardLayerInfo, BoardNet, BoardOriginKind,
BoardStackup, BoardStackupDielectricProperties, BoardStackupLayer, BoardStackupLayerType,
ColorRgba, GraphicsDefaults, InactiveLayerDisplayMode, NetClassBoardSettings,
ColorRgba, DrcSeverity, GraphicsDefaults, InactiveLayerDisplayMode, NetClassBoardSettings,
NetClassForNetEntry, NetClassInfo, NetClassType, NetColorDisplayMode, PadNetEntry,
PadShapeAsPolygonEntry, PadstackPresenceEntry, PadstackPresenceState, PcbArc,
PcbBoardGraphicShape, PcbBoardText, PcbBoardTextBox, PcbDimension, PcbField, PcbFootprint,

View File

@ -224,6 +224,54 @@ pub enum RatsnestDisplayMode {
Unknown(i32),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DrcSeverity {
Warning,
Error,
Exclusion,
Ignore,
Info,
Action,
Debug,
Undefined,
}
impl std::fmt::Display for DrcSeverity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let value = match self {
Self::Warning => "warning",
Self::Error => "error",
Self::Exclusion => "exclusion",
Self::Ignore => "ignore",
Self::Info => "info",
Self::Action => "action",
Self::Debug => "debug",
Self::Undefined => "undefined",
};
write!(f, "{value}")
}
}
impl FromStr for DrcSeverity {
type Err = String;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"warning" => Ok(Self::Warning),
"error" => Ok(Self::Error),
"exclusion" => Ok(Self::Exclusion),
"ignore" => Ok(Self::Ignore),
"info" => Ok(Self::Info),
"action" => Ok(Self::Action),
"debug" => Ok(Self::Debug),
"undefined" => Ok(Self::Undefined),
_ => Err(format!(
"unknown drc severity `{value}`; expected warning, error, exclusion, ignore, info, action, debug, or undefined"
)),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct BoardEditorAppearanceSettings {
pub inactive_layer_display: InactiveLayerDisplayMode,
@ -424,7 +472,7 @@ pub enum PcbItem {
mod tests {
use std::str::FromStr;
use super::BoardOriginKind;
use super::{BoardOriginKind, DrcSeverity};
#[test]
fn board_origin_kind_parses_known_values() {
@ -443,4 +491,22 @@ mod tests {
let result = BoardOriginKind::from_str("other");
assert!(result.is_err());
}
#[test]
fn drc_severity_parses_known_values() {
assert_eq!(
DrcSeverity::from_str("warning").expect("warning should parse"),
DrcSeverity::Warning
);
assert_eq!(
DrcSeverity::from_str("error").expect("error should parse"),
DrcSeverity::Error
);
}
#[test]
fn drc_severity_rejects_unknown_values() {
let result = DrcSeverity::from_str("fatal");
assert!(result.is_err());
}
}

View File

@ -7,9 +7,9 @@ use std::time::Duration;
use kicad_ipc::{
BoardFlipMode, BoardOriginKind, ClientBuilder, CommitAction, CommitSession, DocumentType,
EditorFrameType, InactiveLayerDisplayMode, KiCadClient, KiCadError, NetColorDisplayMode,
PadstackPresenceState, PcbObjectTypeCode, RatsnestDisplayMode, TextObjectSpec,
TextShapeGeometry, TextSpec, Vector2Nm,
DrcSeverity, EditorFrameType, InactiveLayerDisplayMode, KiCadClient, KiCadError,
NetColorDisplayMode, PadstackPresenceState, PcbObjectTypeCode, RatsnestDisplayMode,
TextObjectSpec, TextShapeGeometry, TextSpec, Vector2Nm,
};
const REPORT_MAX_PAD_NET_ROWS: usize = 2_000;
@ -68,6 +68,13 @@ enum Command {
x_nm: i64,
y_nm: i64,
},
InjectDrcError {
severity: DrcSeverity,
message: String,
x_nm: Option<i64>,
y_nm: Option<i64>,
item_ids: Vec<String>,
},
RefreshEditor {
frame: EditorFrameType,
},
@ -371,6 +378,25 @@ async fn run() -> Result<(), KiCadError> {
.await?;
println!("set_origin_kind={} x_nm={} y_nm={}", kind, x_nm, y_nm);
}
Command::InjectDrcError {
severity,
message,
x_nm,
y_nm,
item_ids,
} => {
let position = match (x_nm, y_nm) {
(Some(x_nm), Some(y_nm)) => Some(Vector2Nm { x_nm, y_nm }),
_ => None,
};
let marker = client
.inject_drc_error(severity, message, position, item_ids)
.await?;
println!(
"drc_marker_id={}",
marker.unwrap_or_else(|| "-".to_string())
);
}
Command::RefreshEditor { frame } => {
client.refresh_editor(frame).await?;
println!("refresh_editor=ok frame={}", frame);
@ -1005,6 +1031,79 @@ fn parse_args_from(mut args: Vec<String>) -> Result<(CliConfig, Command), KiCadE
})?,
}
}
"inject-drc-error" => {
let mut severity = DrcSeverity::Error;
let mut message = None;
let mut x_nm = None;
let mut y_nm = None;
let mut item_ids = Vec::new();
let mut i = 1;
while i < args.len() {
match args[i].as_str() {
"--severity" => {
let value = args.get(i + 1).ok_or_else(|| KiCadError::Config {
reason: "missing value for inject-drc-error --severity".to_string(),
})?;
severity = parse_drc_severity(value)
.map_err(|err| KiCadError::Config { reason: err })?;
i += 2;
}
"--message" => {
let value = args.get(i + 1).ok_or_else(|| KiCadError::Config {
reason: "missing value for inject-drc-error --message".to_string(),
})?;
message = Some(value.clone());
i += 2;
}
"--x-nm" => {
let value = args.get(i + 1).ok_or_else(|| KiCadError::Config {
reason: "missing value for inject-drc-error --x-nm".to_string(),
})?;
x_nm = Some(value.parse::<i64>().map_err(|err| KiCadError::Config {
reason: format!("invalid inject-drc-error --x-nm `{value}`: {err}"),
})?);
i += 2;
}
"--y-nm" => {
let value = args.get(i + 1).ok_or_else(|| KiCadError::Config {
reason: "missing value for inject-drc-error --y-nm".to_string(),
})?;
y_nm = Some(value.parse::<i64>().map_err(|err| KiCadError::Config {
reason: format!("invalid inject-drc-error --y-nm `{value}`: {err}"),
})?);
i += 2;
}
"--item-id" => {
let value = args.get(i + 1).ok_or_else(|| KiCadError::Config {
reason: "missing value for inject-drc-error --item-id".to_string(),
})?;
item_ids.push(value.clone());
i += 2;
}
_ => {
i += 1;
}
}
}
if (x_nm.is_some() && y_nm.is_none()) || (x_nm.is_none() && y_nm.is_some()) {
return Err(KiCadError::Config {
reason:
"inject-drc-error requires both --x-nm and --y-nm when providing a position"
.to_string(),
});
}
Command::InjectDrcError {
severity,
message: message.ok_or_else(|| KiCadError::Config {
reason: "inject-drc-error requires `--message <text>`".to_string(),
})?,
x_nm,
y_nm,
item_ids,
}
}
"refresh-editor" => {
let mut frame = EditorFrameType::PcbEditor;
let mut i = 1;
@ -1488,6 +1587,22 @@ fn parse_ratsnest_display_mode(value: &str) -> Result<RatsnestDisplayMode, Strin
}
}
fn parse_drc_severity(value: &str) -> Result<DrcSeverity, String> {
match value {
"warning" => Ok(DrcSeverity::Warning),
"error" => Ok(DrcSeverity::Error),
"exclusion" => Ok(DrcSeverity::Exclusion),
"ignore" => Ok(DrcSeverity::Ignore),
"info" => Ok(DrcSeverity::Info),
"action" => Ok(DrcSeverity::Action),
"debug" => Ok(DrcSeverity::Debug),
"undefined" => Ok(DrcSeverity::Undefined),
_ => Err(format!(
"unknown drc severity `{value}`; expected warning, error, exclusion, ignore, info, action, debug, or undefined"
)),
}
}
fn default_config() -> CliConfig {
CliConfig {
socket: None,
@ -1499,7 +1614,7 @@ fn default_config() -> CliConfig {
fn print_help() {
println!(
"kicad-ipc-cli\n\nUSAGE:\n cargo run --bin kicad-ipc-cli -- [--socket URI] [--token TOKEN] [--client-name NAME] [--timeout-ms N] <command> [command options]\n\nCOMMANDS:\n ping Check IPC connectivity\n version Fetch KiCad version\n open-docs [--type <type>] List open docs (default type: pcb)\n project-path Get current project path from open PCB docs\n board-open Exit non-zero if no PCB doc is open\n net-classes List project netclass definitions\n text-variables List text variables for current board document\n expand-text-variables Expand variables in provided text values\n Options: --text <value> (repeatable)\n text-extents Measure text bounding box\n Options: --text <value>\n text-as-shapes Convert text to rendered shapes\n Options: --text <value> (repeatable)\n nets List board nets (requires one open PCB)\n netlist-pads Emit pad-level netlist data (with footprint context)\n items-by-id --id <uuid> ... Show parsed details for specific item IDs\n item-bbox --id <uuid> ... Show bounding boxes for item IDs\n hit-test --id <uuid> --x-nm <x> --y-nm <y> [--tolerance-nm <n>]\n Hit-test one item at a point\n types-pcb List PCB KiCad object type IDs from proto enum\n items-raw --type-id <id> ... Dump raw Any payloads for requested item type IDs\n items-raw-all-pcb [--debug] Dump all PCB item payloads across all PCB object types\n pad-shape-polygon --pad-id <uuid> ... --layer-id <i32> [--debug]\n Dump pad polygons on a target layer\n padstack-presence --item-id <uuid> ... --layer-id <i32> ... [--debug]\n Check padstack shape presence matrix across layers\n title-block Show title block fields\n board-as-string Dump board as KiCad s-expression text\n selection-as-string Dump current selection as KiCad s-expression text\n stackup Show typed board stackup\n graphics-defaults Show typed graphics defaults\n appearance Show typed editor appearance settings\n set-appearance --inactive-layer-display <normal|dimmed|hidden>\n --net-color-display <all|ratsnest|off>\n --board-flip <normal|flipped-x>\n --ratsnest-display <all-layers|visible-layers>\n Set editor appearance settings\n netclass Show typed netclass map for current board nets\n proto-coverage-board-read Print board-read command coverage vs proto\n board-read-report [--out P] Write markdown board reconstruction report\n enabled-layers List enabled board layers\n set-enabled-layers --copper-layer-count <u32> [--layer-id <i32> ...]\n Set enabled board layer set\n active-layer Show active board layer\n set-active-layer --layer-id <i32>\n Set active board layer\n visible-layers Show currently visible board layers\n set-visible-layers --layer-id <i32> ...\n Set visible board layers\n board-origin [--type <t>] Show board origin (`grid` default, or `drill`)\n set-board-origin --type <t> --x-nm <i64> --y-nm <i64>\n Set board origin (`grid` or `drill`)\n refresh-editor [--frame <f>] Refresh a specific editor frame (default: pcb)\n begin-commit Start staged commit and print commit ID\n end-commit --id <uuid> [--action <commit|drop>] [--message <text>]\n End staged commit with commit/drop action\n selection-summary Show current selection item type counts\n selection-details Show parsed details for selected items\n selection-raw Show raw Any payload bytes for selected items\n smoke ping + version + board-open summary\n help Show help\n\nTYPES:\n schematic | symbol | pcb | footprint | drawing-sheet | project\n"
"kicad-ipc-cli\n\nUSAGE:\n cargo run --bin kicad-ipc-cli -- [--socket URI] [--token TOKEN] [--client-name NAME] [--timeout-ms N] <command> [command options]\n\nCOMMANDS:\n ping Check IPC connectivity\n version Fetch KiCad version\n open-docs [--type <type>] List open docs (default type: pcb)\n project-path Get current project path from open PCB docs\n board-open Exit non-zero if no PCB doc is open\n net-classes List project netclass definitions\n text-variables List text variables for current board document\n expand-text-variables Expand variables in provided text values\n Options: --text <value> (repeatable)\n text-extents Measure text bounding box\n Options: --text <value>\n text-as-shapes Convert text to rendered shapes\n Options: --text <value> (repeatable)\n nets List board nets (requires one open PCB)\n netlist-pads Emit pad-level netlist data (with footprint context)\n items-by-id --id <uuid> ... Show parsed details for specific item IDs\n item-bbox --id <uuid> ... Show bounding boxes for item IDs\n hit-test --id <uuid> --x-nm <x> --y-nm <y> [--tolerance-nm <n>]\n Hit-test one item at a point\n types-pcb List PCB KiCad object type IDs from proto enum\n items-raw --type-id <id> ... Dump raw Any payloads for requested item type IDs\n items-raw-all-pcb [--debug] Dump all PCB item payloads across all PCB object types\n pad-shape-polygon --pad-id <uuid> ... --layer-id <i32> [--debug]\n Dump pad polygons on a target layer\n padstack-presence --item-id <uuid> ... --layer-id <i32> ... [--debug]\n Check padstack shape presence matrix across layers\n title-block Show title block fields\n board-as-string Dump board as KiCad s-expression text\n selection-as-string Dump current selection as KiCad s-expression text\n stackup Show typed board stackup\n graphics-defaults Show typed graphics defaults\n appearance Show typed editor appearance settings\n set-appearance --inactive-layer-display <normal|dimmed|hidden>\n --net-color-display <all|ratsnest|off>\n --board-flip <normal|flipped-x>\n --ratsnest-display <all-layers|visible-layers>\n Set editor appearance settings\n inject-drc-error --severity <s> --message <text> [--x-nm <i64> --y-nm <i64>] [--item-id <uuid> ...]\n Inject a DRC marker (severity: warning|error|exclusion|ignore|info|action|debug|undefined)\n netclass Show typed netclass map for current board nets\n proto-coverage-board-read Print board-read command coverage vs proto\n board-read-report [--out P] Write markdown board reconstruction report\n enabled-layers List enabled board layers\n set-enabled-layers --copper-layer-count <u32> [--layer-id <i32> ...]\n Set enabled board layer set\n active-layer Show active board layer\n set-active-layer --layer-id <i32>\n Set active board layer\n visible-layers Show currently visible board layers\n set-visible-layers --layer-id <i32> ...\n Set visible board layers\n board-origin [--type <t>] Show board origin (`grid` default, or `drill`)\n set-board-origin --type <t> --x-nm <i64> --y-nm <i64>\n Set board origin (`grid` or `drill`)\n refresh-editor [--frame <f>] Refresh a specific editor frame (default: pcb)\n begin-commit Start staged commit and print commit ID\n end-commit --id <uuid> [--action <commit|drop>] [--message <text>]\n End staged commit with commit/drop action\n selection-summary Show current selection item type counts\n selection-details Show parsed details for selected items\n selection-raw Show raw Any payload bytes for selected items\n smoke ping + version + board-open summary\n help Show help\n\nTYPES:\n schematic | symbol | pcb | footprint | drawing-sheet | project\n"
);
}
@ -2053,7 +2168,7 @@ fn hex_char(value: u8) -> char {
mod tests {
use super::{parse_args_from, Command};
use kicad_ipc::{
BoardFlipMode, BoardOriginKind, CommitAction, InactiveLayerDisplayMode,
BoardFlipMode, BoardOriginKind, CommitAction, DrcSeverity, InactiveLayerDisplayMode,
NetColorDisplayMode, RatsnestDisplayMode,
};
@ -2224,4 +2339,37 @@ mod tests {
other => panic!("unexpected command variant: {other:?}"),
}
}
#[test]
fn parse_args_parses_inject_drc_error() {
let (_, command) = parse_args_from(vec![
"inject-drc-error".to_string(),
"--severity".to_string(),
"warning".to_string(),
"--message".to_string(),
"marker".to_string(),
"--x-nm".to_string(),
"100".to_string(),
"--y-nm".to_string(),
"200".to_string(),
])
.expect("inject-drc-error args should parse");
match command {
Command::InjectDrcError {
severity,
message,
x_nm,
y_nm,
item_ids,
} => {
assert_eq!(severity, DrcSeverity::Warning);
assert_eq!(message, "marker");
assert_eq!(x_nm, Some(100));
assert_eq!(y_nm, Some(200));
assert!(item_ids.is_empty());
}
other => panic!("unexpected command variant: {other:?}"),
}
}
}