Skip to main content

executable_stories/
types.rs

1use serde::{Deserialize, Serialize};
2
3use crate::doc_entry::DocEntry;
4
5#[derive(Debug, Clone, Serialize)]
6pub struct Ticket {
7    pub id: String,
8    #[serde(skip_serializing_if = "Option::is_none")]
9    pub url: Option<String>,
10}
11
12/// Published raw-run schema, emitted as `$schema` so editors validate the
13/// output file as the adapter writes it. `executable-stories doctor` also
14/// reports its presence.
15pub const SCHEMA_URL: &str = "https://executable-stories.dev/schemas/raw-run.schema.json";
16
17#[derive(Serialize, Clone)]
18#[serde(rename_all = "camelCase")]
19pub struct RawRun {
20    pub schema_version: u32,
21    pub test_cases: Vec<RawTestCase>,
22    #[serde(skip_serializing_if = "Vec::is_empty")]
23    pub features: Vec<RawFeature>,
24    pub project_root: String,
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub started_at_ms: Option<f64>,
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub finished_at_ms: Option<f64>,
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub ci: Option<RawCIInfo>,
31    /// The commit this run describes, so a report states which code it
32    /// documents. Read from CI's environment first, `git rev-parse` otherwise.
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub git_sha: Option<String>,
35    /// Version of this adapter, so a report states what produced it.
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub package_version: Option<String>,
38    /// How much of each source file this run covered: `"full"` when the test
39    /// binary's arguments were inspected and narrowed nothing, `"filtered"`
40    /// when they did. Only `"full"` lets a consumer retire a scenario it no
41    /// longer reports.
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub run_scope: Option<String>,
44}
45
46/// What a file's scenarios are for, declared with [`crate::feature`].
47///
48/// Scenarios say what the system does. A declaration says why the feature
49/// exists and who it serves, so a reader meets the intent before the examples.
50#[derive(Serialize, Clone, Default, Debug)]
51#[serde(rename_all = "camelCase")]
52pub struct RawFeature {
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub source_file: Option<String>,
55    pub title: String,
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub kind: Option<String>,
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub narrative: Option<String>,
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub tags: Option<Vec<String>>,
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub glossary: Option<Vec<RawGlossaryTerm>>,
64}
65
66/// One entry in a feature's glossary.
67#[derive(Serialize, Clone, Default, Debug)]
68#[serde(rename_all = "camelCase")]
69pub struct RawGlossaryTerm {
70    pub term: String,
71    pub definition: String,
72}
73
74#[derive(Serialize, Clone, Default)]
75#[serde(rename_all = "camelCase")]
76pub struct RawTestCase {
77    pub status: String,
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub external_id: Option<String>,
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub title: Option<String>,
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub story: Option<StoryMeta>,
84    /// Test file the scenario was written in. The report groups by it, and a
85    /// feature declaration keys on the same path.
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub source_file: Option<String>,
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub duration_ms: Option<f64>,
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub error: Option<RawError>,
92    pub retry: i32,
93    pub retries: i32,
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub attachments: Option<Vec<RawAttachment>>,
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub step_events: Option<Vec<RawStepEvent>>,
98}
99
100#[derive(Serialize, Clone)]
101#[serde(rename_all = "camelCase")]
102pub struct StoryMeta {
103    pub scenario: String,
104    pub steps: Vec<StoryStep>,
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub tags: Option<Vec<String>>,
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub tickets: Option<Vec<Ticket>>,
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub covers: Option<Vec<String>>,
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub meta: Option<serde_json::Value>,
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub docs: Option<Vec<DocEntry>>,
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub source_order: Option<u32>,
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub otel_spans: Option<Vec<serde_json::Value>>,
119    /// When the scenario started (epoch ms): `Story::new`.
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub started_at_ms: Option<f64>,
122}
123
124#[derive(Serialize, Clone)]
125#[serde(rename_all = "camelCase")]
126pub struct StoryStep {
127    pub keyword: String,
128    pub text: String,
129    #[serde(skip_serializing_if = "Option::is_none")]
130    pub id: Option<String>,
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub mode: Option<String>,
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub wrapped: Option<bool>,
135    #[serde(skip_serializing_if = "Option::is_none")]
136    pub duration_ms: Option<f64>,
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub docs: Option<Vec<DocEntry>>,
139    /// Assertions attributable to this step.
140    ///
141    /// Rust has no assertion counter, so this is set only when the author wraps
142    /// a claim in `expect_step`/`fn_step("Then", ..)`. `None` means unobserved,
143    /// which is not the same as zero.
144    #[serde(skip_serializing_if = "Option::is_none")]
145    pub assertions: Option<u32>,
146    /// When the step started (epoch ms): the step call, or a wrapped body's start.
147    #[serde(skip_serializing_if = "Option::is_none")]
148    pub started_at_ms: Option<f64>,
149}
150
151#[derive(Serialize, Clone)]
152#[serde(rename_all = "camelCase")]
153pub struct RawError {
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub message: Option<String>,
156    #[serde(skip_serializing_if = "Option::is_none")]
157    pub stack: Option<String>,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
161#[serde(rename_all = "camelCase")]
162pub struct RawAttachment {
163    pub name: String,
164    pub media_type: String,
165    #[serde(skip_serializing_if = "Option::is_none")]
166    pub path: Option<String>,
167    #[serde(skip_serializing_if = "Option::is_none")]
168    pub body: Option<String>,
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub encoding: Option<String>,
171    #[serde(skip_serializing_if = "Option::is_none")]
172    pub charset: Option<String>,
173    #[serde(skip_serializing_if = "Option::is_none")]
174    pub file_name: Option<String>,
175    #[serde(skip_serializing_if = "Option::is_none")]
176    pub byte_length: Option<usize>,
177    #[serde(skip_serializing_if = "Option::is_none")]
178    pub step_index: Option<usize>,
179    #[serde(skip_serializing_if = "Option::is_none")]
180    pub step_id: Option<String>,
181}
182
183#[derive(Debug, Clone, Serialize, Deserialize)]
184#[serde(rename_all = "camelCase")]
185pub struct RawStepEvent {
186    #[serde(skip_serializing_if = "Option::is_none")]
187    pub index: Option<usize>,
188    #[serde(skip_serializing_if = "Option::is_none")]
189    pub title: Option<String>,
190    #[serde(skip_serializing_if = "Option::is_none")]
191    pub status: Option<String>,
192    #[serde(skip_serializing_if = "Option::is_none")]
193    pub duration_ms: Option<f64>,
194}
195
196#[derive(Debug, Clone, Serialize, Deserialize)]
197#[serde(rename_all = "camelCase")]
198pub struct RawCIInfo {
199    pub name: String,
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub url: Option<String>,
202    #[serde(skip_serializing_if = "Option::is_none")]
203    pub build_number: Option<String>,
204}