Skip to main content

pointlock_ir/
selector.rs

1//! Element selectors and geometric types.
2//!
3//! [`ElementSelectorIR`] and [`TextMatchIR`] are isomorphic to DeviceRail
4//! `ElementSelector` / `TextMatch` (field names and limits verbatim) with one
5//! canonicalization difference (02 §2.4): absence is expressed by omitting
6//! the field, never by null — the single-representation rule for hashing.
7//! `pointlock-provider-devicerail` re-inserts nulls at the wire boundary.
8
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11
12use crate::vocab::{TextMatchMode, UiContextKind};
13
14/// Structured element selector (isomorphic to DeviceRail `ElementSelector`).
15///
16/// At least one property must be present (`minProperties: 1`) — enforced by
17/// the schema; serde-side an all-`None` value is representable and left to
18/// the compiler to reject.
19#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema)]
20#[serde(rename_all = "camelCase", deny_unknown_fields)]
21#[schemars(extend("minProperties" = 1))]
22pub struct ElementSelectorIR {
23    /// UI context scoping (native/web, optional context id).
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub context: Option<SelectorContext>,
26    /// Accessibility role.
27    #[serde(skip_serializing_if = "Option::is_none")]
28    #[schemars(length(min = 1, max = 256))]
29    pub role: Option<String>,
30    /// Accessible name.
31    #[serde(skip_serializing_if = "Option::is_none")]
32    #[schemars(length(min = 1, max = 65536))]
33    pub name: Option<String>,
34    /// Stable identifier (resource id / test id).
35    #[serde(skip_serializing_if = "Option::is_none")]
36    #[schemars(length(min = 1, max = 4096))]
37    pub identifier: Option<String>,
38    /// Text content match.
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub text: Option<TextMatchIR>,
41    /// Current value match.
42    #[serde(skip_serializing_if = "Option::is_none")]
43    #[schemars(length(max = 65536))]
44    pub value: Option<String>,
45    /// CSS selector (web contexts).
46    #[serde(skip_serializing_if = "Option::is_none")]
47    #[schemars(length(min = 1, max = 65536))]
48    pub css: Option<String>,
49}
50
51/// UI context reference inside a selector (inline object in the baseline
52/// schema, hence `#[schemars(inline)]`).
53#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
54#[serde(rename_all = "camelCase", deny_unknown_fields)]
55#[schemars(inline)]
56pub struct SelectorContext {
57    /// Context kind (`native` | `web`).
58    pub context_kind: UiContextKind,
59    /// Optional concrete context id.
60    #[serde(skip_serializing_if = "Option::is_none")]
61    #[schemars(length(min = 1, max = 4096))]
62    pub context_id: Option<String>,
63}
64
65/// Text matcher (isomorphic to DeviceRail `TextMatch`).
66///
67/// `mode` and `caseSensitive` are required because sealed IR materializes
68/// defaults (`exact` / `false`) — the single-representation rule (02 §2.4).
69#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
70#[serde(rename_all = "camelCase", deny_unknown_fields)]
71pub struct TextMatchIR {
72    /// The text to match.
73    #[schemars(length(min = 1, max = 65536))]
74    pub value: String,
75    /// Match mode (materialized default: `exact`).
76    pub mode: TextMatchMode,
77    /// Case sensitivity (materialized default: `false`).
78    pub case_sensitive: bool,
79}
80
81/// Rectangular region, field-compatible with DeviceRail `UiRect`.
82///
83/// Uses `serde_json::Number` (not `f64`) so integer inputs round-trip as
84/// integers — required by the canonical-form rule that numbers keep their
85/// shortest ES representation (02 §12.1).
86#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
87#[serde(deny_unknown_fields)]
88pub struct RectIR {
89    /// X origin.
90    pub x: serde_json::Number,
91    /// Y origin.
92    pub y: serde_json::Number,
93    /// Width (≥ 0).
94    #[schemars(range(min = 0))]
95    pub width: serde_json::Number,
96    /// Height (≥ 0).
97    #[schemars(range(min = 0))]
98    pub height: serde_json::Number,
99}