Merge pull request #1 from Milind220/codex/pcb-write-sync-wrapper

Finish v0.1.0 API - Full read + write API for all available commands as of KiCAD v10 - rc1.1
This commit is contained in:
Milind Sharma 2026-02-20 23:32:28 +08:00 committed by GitHub
commit 5674b4f176
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 3470 additions and 57 deletions

View File

@ -20,6 +20,34 @@ Early scaffold phase. Core architecture + step-by-step implementation plan:
- CLI runbook: `/Users/milindsharma/Developer/kicad-oss/kicad-ipc-rust/docs/TEST_CLI.md`
## Runtime Compatibility Notes (Current Test Rig)
- Last verified: 2026-02-20
- KiCad version (`kicad-ipc-cli version`): `10.0.0 (10.0.0-rc1)`
Commands wrapped in this crate but currently unhandled/unsupported by this KiCad build:
| Command | Runtime status | Notes |
| --- | --- | --- |
| `RefreshEditor` | `AS_UNHANDLED` | KiCad responds `no handler available for request of type kiapi.common.commands.RefreshEditor`. |
Deferred manual/runtime verification (implemented after 2026-02-20 while user unavailable):
- `GetKiCadBinaryPath`
- `GetPluginSettingsPath`
- `SaveDocument`
- `SaveCopyOfDocument`
- `RevertDocument`
- `RunAction`
- `CreateItems`
- `UpdateItems`
- `DeleteItems`
- `ParseAndCreateItemsFromString`
- `SetNetClasses`
- `SetTextVariables`
- `UpdateBoardStackup`
- `InteractiveMoveItems`
## KiCad v10 RC1.1 API Completion Matrix
Source of truth for this matrix:
@ -38,12 +66,12 @@ Legend:
| Section | Proto Commands | Implemented | Coverage |
| --- | ---: | ---: | ---: |
| Common (base) | 6 | 4 | 67% |
| Common editor/document | 23 | 9 | 39% |
| Project manager | 5 | 3 | 60% |
| Board editor (PCB) | 22 | 13 | 59% |
| Common (base) | 6 | 6 | 100% |
| Common editor/document | 23 | 23 | 100% |
| Project manager | 5 | 5 | 100% |
| Board editor (PCB) | 22 | 22 | 100% |
| Schematic editor (dedicated proto commands) | 0 | 0 | n/a |
| **Total** | **56** | **29** | **52%** |
| **Total** | **56** | **56** | **100%** |
### Common (base)
@ -51,75 +79,75 @@ Legend:
| --- | --- | --- |
| `Ping` | Implemented | `KiCadClient::ping` |
| `GetVersion` | Implemented | `KiCadClient::get_version` |
| `GetKiCadBinaryPath` | Not yet | - |
| `GetKiCadBinaryPath` | Implemented | `KiCadClient::get_kicad_binary_path_raw`, `KiCadClient::get_kicad_binary_path` |
| `GetTextExtents` | Implemented | `KiCadClient::get_text_extents_raw`, `KiCadClient::get_text_extents` |
| `GetTextAsShapes` | Implemented | `KiCadClient::get_text_as_shapes_raw`, `KiCadClient::get_text_as_shapes` |
| `GetPluginSettingsPath` | Not yet | - |
| `GetPluginSettingsPath` | Implemented | `KiCadClient::get_plugin_settings_path_raw`, `KiCadClient::get_plugin_settings_path` |
### Common editor/document
| KiCad Command | Status | Rust API |
| --- | --- | --- |
| `RefreshEditor` | Not yet | - |
| `RefreshEditor` | Implemented | `KiCadClient::refresh_editor` |
| `GetOpenDocuments` | Implemented | `KiCadClient::get_open_documents`, `KiCadClient::get_current_project_path`, `KiCadClient::has_open_board` |
| `SaveDocument` | Not yet | - |
| `SaveCopyOfDocument` | Not yet | - |
| `RevertDocument` | Not yet | - |
| `RunAction` | Not yet | - |
| `BeginCommit` | Not yet | - |
| `EndCommit` | Not yet | - |
| `CreateItems` | Not yet | - |
| `SaveDocument` | Implemented | `KiCadClient::save_document_raw`, `KiCadClient::save_document` |
| `SaveCopyOfDocument` | Implemented | `KiCadClient::save_copy_of_document_raw`, `KiCadClient::save_copy_of_document` |
| `RevertDocument` | Implemented | `KiCadClient::revert_document_raw`, `KiCadClient::revert_document` |
| `RunAction` | Implemented | `KiCadClient::run_action_raw`, `KiCadClient::run_action` |
| `BeginCommit` | Implemented | `KiCadClient::begin_commit_raw`, `KiCadClient::begin_commit` |
| `EndCommit` | Implemented | `KiCadClient::end_commit_raw`, `KiCadClient::end_commit` |
| `CreateItems` | Implemented | `KiCadClient::create_items_raw`, `KiCadClient::create_items` |
| `GetItems` | Implemented | `KiCadClient::get_items_raw_by_type_codes`, `KiCadClient::get_items_by_type_codes`, `KiCadClient::get_items_details_by_type_codes`, `KiCadClient::get_all_pcb_items_raw`, `KiCadClient::get_all_pcb_items`, `KiCadClient::get_all_pcb_items_details`, `KiCadClient::get_pad_netlist` |
| `GetItemsById` | Implemented | `KiCadClient::get_items_by_id_raw`, `KiCadClient::get_items_by_id`, `KiCadClient::get_items_by_id_details` |
| `UpdateItems` | Not yet | - |
| `DeleteItems` | Not yet | - |
| `UpdateItems` | Implemented | `KiCadClient::update_items_raw`, `KiCadClient::update_items` |
| `DeleteItems` | Implemented | `KiCadClient::delete_items_raw`, `KiCadClient::delete_items` |
| `GetBoundingBox` | Implemented | `KiCadClient::get_item_bounding_boxes` |
| `GetSelection` | Implemented | `KiCadClient::get_selection_raw`, `KiCadClient::get_selection`, `KiCadClient::get_selection_summary`, `KiCadClient::get_selection_details` |
| `AddToSelection` | Not yet | - |
| `RemoveFromSelection` | Not yet | - |
| `ClearSelection` | Not yet | - |
| `AddToSelection` | Implemented | `KiCadClient::add_to_selection_raw`, `KiCadClient::add_to_selection` |
| `RemoveFromSelection` | Implemented | `KiCadClient::remove_from_selection_raw`, `KiCadClient::remove_from_selection` |
| `ClearSelection` | Implemented | `KiCadClient::clear_selection_raw`, `KiCadClient::clear_selection` |
| `HitTest` | Implemented | `KiCadClient::hit_test_item` |
| `GetTitleBlockInfo` | Implemented | `KiCadClient::get_title_block_info` |
| `SaveDocumentToString` | Implemented | `KiCadClient::get_board_as_string` |
| `SaveSelectionToString` | Implemented | `KiCadClient::get_selection_as_string` |
| `ParseAndCreateItemsFromString` | Not yet | - |
| `ParseAndCreateItemsFromString` | Implemented | `KiCadClient::parse_and_create_items_from_string_raw`, `KiCadClient::parse_and_create_items_from_string` |
### Project manager
| KiCad Command | Status | Rust API |
| --- | --- | --- |
| `GetNetClasses` | Implemented | `KiCadClient::get_net_classes_raw`, `KiCadClient::get_net_classes` |
| `SetNetClasses` | Not yet | - |
| `SetNetClasses` | Implemented | `KiCadClient::set_net_classes_raw`, `KiCadClient::set_net_classes` |
| `ExpandTextVariables` | Implemented | `KiCadClient::expand_text_variables_raw`, `KiCadClient::expand_text_variables` |
| `GetTextVariables` | Implemented | `KiCadClient::get_text_variables_raw`, `KiCadClient::get_text_variables` |
| `SetTextVariables` | Not yet | - |
| `SetTextVariables` | Implemented | `KiCadClient::set_text_variables_raw`, `KiCadClient::set_text_variables` |
### Board editor (PCB)
| KiCad Command | Status | Rust API |
| --- | --- | --- |
| `GetBoardStackup` | Implemented | `KiCadClient::get_board_stackup_raw`, `KiCadClient::get_board_stackup` |
| `UpdateBoardStackup` | Not yet | - |
| `UpdateBoardStackup` | Implemented | `KiCadClient::update_board_stackup_raw`, `KiCadClient::update_board_stackup` |
| `GetBoardEnabledLayers` | Implemented | `KiCadClient::get_board_enabled_layers` |
| `SetBoardEnabledLayers` | Not yet | - |
| `SetBoardEnabledLayers` | Implemented | `KiCadClient::set_board_enabled_layers` |
| `GetGraphicsDefaults` | Implemented | `KiCadClient::get_graphics_defaults_raw`, `KiCadClient::get_graphics_defaults` |
| `GetBoardOrigin` | Implemented | `KiCadClient::get_board_origin` |
| `SetBoardOrigin` | Not yet | - |
| `SetBoardOrigin` | Implemented | `KiCadClient::set_board_origin` |
| `GetNets` | Implemented | `KiCadClient::get_nets` |
| `GetItemsByNet` | Implemented | `KiCadClient::get_items_by_net_raw`, `KiCadClient::get_items_by_net` |
| `GetItemsByNetClass` | Implemented | `KiCadClient::get_items_by_net_class_raw`, `KiCadClient::get_items_by_net_class` |
| `GetNetClassForNets` | Implemented | `KiCadClient::get_netclass_for_nets_raw`, `KiCadClient::get_netclass_for_nets` |
| `RefillZones` | Not yet | - |
| `RefillZones` | Implemented | `KiCadClient::refill_zones` |
| `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` | Not yet | - |
| `SetVisibleLayers` | Implemented | `KiCadClient::set_visible_layers` |
| `GetActiveLayer` | Implemented | `KiCadClient::get_active_layer` |
| `SetActiveLayer` | Not yet | - |
| `SetActiveLayer` | Implemented | `KiCadClient::set_active_layer` |
| `GetBoardEditorAppearanceSettings` | Implemented | `KiCadClient::get_board_editor_appearance_settings_raw`, `KiCadClient::get_board_editor_appearance_settings` |
| `SetBoardEditorAppearanceSettings` | Not yet | - |
| `InteractiveMoveItems` | Not yet | - |
| `SetBoardEditorAppearanceSettings` | Implemented | `KiCadClient::set_board_editor_appearance_settings` |
| `InteractiveMoveItems` | Implemented | `KiCadClient::interactive_move_items_raw`, `KiCadClient::interactive_move_items` |
### Schematic editor

View File

@ -29,6 +29,18 @@ Version:
cargo run --bin kicad-ipc-cli -- version
```
Resolve KiCad binary path (default `kicad-cli`):
```bash
cargo run --bin kicad-ipc-cli -- kicad-binary-path --binary-name kicad-cli
```
Resolve plugin settings path (default identifier `kicad-ipc-rust`):
```bash
cargo run --bin kicad-ipc-cli -- plugin-settings-path --identifier kicad-ipc-rust
```
List open PCB docs:
```bash
@ -53,12 +65,24 @@ List project net classes:
cargo run --bin kicad-ipc-cli -- net-classes
```
Write current net classes back with selected merge mode:
```bash
cargo run --bin kicad-ipc-cli -- set-net-classes --merge-mode merge
```
List text variables for current board document:
```bash
cargo run --bin kicad-ipc-cli -- text-variables
```
Set text variables:
```bash
cargo run --bin kicad-ipc-cli -- set-text-variables --merge-mode merge --var REV=A
```
Expand text variables in one or more input strings:
```bash
@ -83,18 +107,36 @@ List enabled board layers:
cargo run --bin kicad-ipc-cli -- enabled-layers
```
Set enabled board layers:
```bash
cargo run --bin kicad-ipc-cli -- set-enabled-layers --copper-layer-count 2 --layer-id 47 --layer-id 52
```
Show active layer:
```bash
cargo run --bin kicad-ipc-cli -- active-layer
```
Set active layer:
```bash
cargo run --bin kicad-ipc-cli -- set-active-layer --layer-id 0
```
Show visible layers:
```bash
cargo run --bin kicad-ipc-cli -- visible-layers
```
Set visible layers:
```bash
cargo run --bin kicad-ipc-cli -- set-visible-layers --layer-id 0 --layer-id 31
```
Show board origin (grid origin by default):
```bash
@ -107,6 +149,80 @@ Show drill origin:
cargo run --bin kicad-ipc-cli -- board-origin --type drill
```
Set board origin:
```bash
cargo run --bin kicad-ipc-cli -- set-board-origin --type grid --x-nm 1000000 --y-nm 2000000
```
Refresh PCB editor:
```bash
cargo run --bin kicad-ipc-cli -- refresh-editor --frame pcb
```
If your KiCad build does not expose this handler yet, this call may return `AS_UNHANDLED`.
Start a staged commit and print commit ID:
```bash
cargo run --bin kicad-ipc-cli -- --client-name write-test begin-commit
```
End a staged commit:
```bash
cargo run --bin kicad-ipc-cli -- --client-name write-test end-commit --id <commit-id> --action drop --message "cli test cleanup"
```
Save current board document:
```bash
cargo run --bin kicad-ipc-cli -- save-doc
```
Save a copy of current board document:
```bash
cargo run --bin kicad-ipc-cli -- save-copy --path /tmp/example.kicad_pcb --overwrite --include-project
```
Revert current board document from disk:
```bash
cargo run --bin kicad-ipc-cli -- revert-doc
```
Run a raw KiCad tool action:
```bash
cargo run --bin kicad-ipc-cli -- run-action --action pcbnew.InteractiveSelection.ClearSelection
```
Create raw Any item payload(s):
```bash
cargo run --bin kicad-ipc-cli -- create-items --item type.googleapis.com/kiapi.board.types.Text=<hex_payload>
```
Update raw Any item payload(s):
```bash
cargo run --bin kicad-ipc-cli -- update-items --item type.googleapis.com/kiapi.board.types.Text=<hex_payload>
```
Delete items by ID:
```bash
cargo run --bin kicad-ipc-cli -- delete-items --id <uuid> --id <uuid>
```
Parse and create items from s-expression:
```bash
cargo run --bin kicad-ipc-cli -- parse-create-items --contents "(kicad_pcb (version 20240108))"
```
Show summary of current PCB selection by item type:
```bash
@ -125,6 +241,24 @@ Show raw protobuf payload bytes for selected items:
cargo run --bin kicad-ipc-cli -- selection-raw
```
Add items to current selection:
```bash
cargo run --bin kicad-ipc-cli -- add-to-selection --id <uuid> --id <uuid>
```
Remove items from current selection:
```bash
cargo run --bin kicad-ipc-cli -- remove-from-selection --id <uuid> --id <uuid>
```
Clear current selection:
```bash
cargo run --bin kicad-ipc-cli -- clear-selection
```
Show pad-level netlist entries (footprint/pad/net):
```bash
@ -207,10 +341,35 @@ Show typed stackup/graphics/appearance:
```bash
cargo run --bin kicad-ipc-cli -- stackup
cargo run --bin kicad-ipc-cli -- update-stackup
cargo run --bin kicad-ipc-cli -- graphics-defaults
cargo run --bin kicad-ipc-cli -- appearance
```
Set editor appearance:
```bash
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
```
Refill all zones:
```bash
cargo run --bin kicad-ipc-cli -- refill-zones
```
Start interactive move tool for one or more item IDs:
```bash
cargo run --bin kicad-ipc-cli -- interactive-move --id <uuid> --id <uuid>
```
Show typed netclass map:
```bash
@ -259,6 +418,12 @@ Custom token:
cargo run --bin kicad-ipc-cli -- --token "$KICAD_API_TOKEN" version
```
Stable client name (needed when pairing `begin-commit` and `end-commit` across separate CLI runs):
```bash
cargo run --bin kicad-ipc-cli -- --client-name write-test begin-commit
```
Custom timeout:
```bash

File diff suppressed because it is too large Load Diff

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,
@ -33,8 +33,9 @@ pub use crate::model::board::{
Vector2Nm,
};
pub use crate::model::common::{
DocumentSpecifier, DocumentType, ItemBoundingBox, ItemHitTestResult, PcbObjectTypeCode,
SelectionItemDetail, SelectionSummary, SelectionTypeCount, TextAsShapesEntry,
TextAttributesSpec, TextBoxSpec, TextExtents, TextHorizontalAlignment, TextObjectSpec,
TextShape, TextShapeGeometry, TextSpec, TextVerticalAlignment, TitleBlockInfo, VersionInfo,
CommitAction, CommitSession, DocumentSpecifier, DocumentType, EditorFrameType, ItemBoundingBox,
ItemHitTestResult, MapMergeMode, PcbObjectTypeCode, RunActionStatus, SelectionItemDetail,
SelectionSummary, SelectionTypeCount, TextAsShapesEntry, TextAttributesSpec, TextBoxSpec,
TextExtents, TextHorizontalAlignment, TextObjectSpec, TextShape, TextShapeGeometry, TextSpec,
TextVerticalAlignment, TitleBlockInfo, VersionInfo,
};

View File

@ -164,6 +164,7 @@ pub struct BoardStackupLayer {
pub struct BoardStackup {
pub finish_type_name: String,
pub impedance_controlled: bool,
pub edge_has_connector: bool,
pub edge_has_castellated_pads: bool,
pub edge_has_edge_plating: bool,
pub layers: Vec<BoardStackupLayer>,
@ -224,6 +225,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 +473,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 +492,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

@ -12,6 +12,65 @@ pub struct VersionInfo {
pub full_version: String,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum EditorFrameType {
ProjectManager,
SchematicEditor,
PcbEditor,
SpiceSimulator,
SymbolEditor,
FootprintEditor,
DrawingSheetEditor,
}
impl EditorFrameType {
pub(crate) fn to_proto(self) -> i32 {
match self {
Self::ProjectManager => common_types::FrameType::FtProjectManager as i32,
Self::SchematicEditor => common_types::FrameType::FtSchematicEditor as i32,
Self::PcbEditor => common_types::FrameType::FtPcbEditor as i32,
Self::SpiceSimulator => common_types::FrameType::FtSpiceSimulator as i32,
Self::SymbolEditor => common_types::FrameType::FtSymbolEditor as i32,
Self::FootprintEditor => common_types::FrameType::FtFootprintEditor as i32,
Self::DrawingSheetEditor => common_types::FrameType::FtDrawingSheetEditor as i32,
}
}
}
impl std::fmt::Display for EditorFrameType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let value = match self {
Self::ProjectManager => "project-manager",
Self::SchematicEditor => "schematic",
Self::PcbEditor => "pcb",
Self::SpiceSimulator => "spice",
Self::SymbolEditor => "symbol",
Self::FootprintEditor => "footprint",
Self::DrawingSheetEditor => "drawing-sheet",
};
write!(f, "{value}")
}
}
impl FromStr for EditorFrameType {
type Err = String;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"project-manager" => Ok(Self::ProjectManager),
"schematic" => Ok(Self::SchematicEditor),
"pcb" => Ok(Self::PcbEditor),
"spice" => Ok(Self::SpiceSimulator),
"symbol" => Ok(Self::SymbolEditor),
"footprint" => Ok(Self::FootprintEditor),
"drawing-sheet" => Ok(Self::DrawingSheetEditor),
_ => Err(format!(
"unknown frame `{value}`; expected one of: project-manager, schematic, pcb, spice, symbol, footprint, drawing-sheet"
)),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DocumentType {
Schematic,
@ -113,6 +172,77 @@ pub struct SelectionItemDetail {
pub raw_len: usize,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CommitSession {
pub id: String,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CommitAction {
Commit,
Drop,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RunActionStatus {
Ok,
Invalid,
FrameNotOpen,
Unknown(i32),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum MapMergeMode {
Merge,
Replace,
}
impl std::fmt::Display for MapMergeMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Merge => write!(f, "merge"),
Self::Replace => write!(f, "replace"),
}
}
}
impl FromStr for MapMergeMode {
type Err = String;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"merge" => Ok(Self::Merge),
"replace" => Ok(Self::Replace),
_ => Err(format!(
"unknown merge mode `{value}`; expected `merge` or `replace`"
)),
}
}
}
impl std::fmt::Display for CommitAction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Commit => write!(f, "commit"),
Self::Drop => write!(f, "drop"),
}
}
}
impl FromStr for CommitAction {
type Err = String;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"commit" => Ok(Self::Commit),
"drop" => Ok(Self::Drop),
_ => Err(format!(
"unknown commit action `{value}`; expected `commit` or `drop`"
)),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TitleBlockInfo {
pub title: String,
@ -299,3 +429,48 @@ impl std::fmt::Display for ItemHitTestResult {
write!(f, "{value}")
}
}
#[cfg(test)]
mod tests {
use super::{CommitAction, EditorFrameType, MapMergeMode};
use std::str::FromStr;
#[test]
fn commit_action_parses_known_values() {
assert_eq!(CommitAction::from_str("commit"), Ok(CommitAction::Commit));
assert_eq!(CommitAction::from_str("drop"), Ok(CommitAction::Drop));
}
#[test]
fn commit_action_rejects_unknown_values() {
assert!(CommitAction::from_str("rollback").is_err());
}
#[test]
fn editor_frame_type_parses_known_values() {
assert_eq!(
EditorFrameType::from_str("pcb"),
Ok(EditorFrameType::PcbEditor)
);
assert_eq!(
EditorFrameType::from_str("project-manager"),
Ok(EditorFrameType::ProjectManager)
);
}
#[test]
fn editor_frame_type_rejects_unknown_values() {
assert!(EditorFrameType::from_str("layout").is_err());
}
#[test]
fn map_merge_mode_parses_known_values() {
assert_eq!(MapMergeMode::from_str("merge"), Ok(MapMergeMode::Merge));
assert_eq!(MapMergeMode::from_str("replace"), Ok(MapMergeMode::Replace));
}
#[test]
fn map_merge_mode_rejects_unknown_values() {
assert!(MapMergeMode::from_str("upsert").is_err());
}
}

File diff suppressed because it is too large Load Diff