ruff_notebook/schema.rs
1//! The JSON schema of a Jupyter Notebook, entrypoint is [`RawNotebook`]
2//!
3//! Generated by <https://app.quicktype.io/> from
4//! <https://github.com/jupyter/nbformat/blob/16b53251aabf472ad9406ddb1f78b0421c014eeb/nbformat/v4/nbformat.v4.schema.json>
5//! Jupyter Notebook v4.5 JSON schema.
6//!
7//! The following changes were made to the generated version:
8//! * Only keep the required structs and enums.
9//! * `Cell::id` is optional because it wasn't required <v4.5
10//! * `#[serde(deny_unknown_fields)]` was added where the schema had
11//! `"additionalProperties": false`
12//! * `#[serde(flatten)] pub other: BTreeMap<String, Value>` for
13//! `"additionalProperties": true` as preparation for round-trip support.
14//! * `#[serde(skip_serializing_if = "Option::is_none")]` was added to optional
15//! fields where `null` values should not be serialized.
16//! * `Cell::execution_count` is a required property only for code cells, but
17//! we serialize it for all cells. This is because we can't know if a cell is
18//! a code cell or not without looking at the `cell_type` property, which
19//! would require a custom serializer.
20
21use std::collections::{BTreeMap, HashMap};
22
23use serde::{Deserialize, Serialize};
24use serde_json::Value;
25
26fn sort_alphabetically<T: Serialize, S: serde::Serializer>(
27 value: &T,
28 serializer: S,
29) -> Result<S::Ok, S::Error> {
30 let value = serde_json::to_value(value).map_err(serde::ser::Error::custom)?;
31 value.serialize(serializer)
32}
33
34/// This is used to serialize any value implementing [`Serialize`] alphabetically.
35///
36/// The reason for this is to maintain consistency in the generated JSON string,
37/// which is useful for diffing. The default serializer keeps the order of the
38/// fields as they are defined in the struct, which will not be consistent when
39/// there are `extra` fields.
40///
41/// # Example
42///
43/// ```
44/// use std::collections::BTreeMap;
45///
46/// use serde::Serialize;
47///
48/// use ruff_notebook::SortAlphabetically;
49///
50/// #[derive(Serialize)]
51/// struct MyStruct {
52/// a: String,
53/// #[serde(flatten)]
54/// extra: BTreeMap<String, String>,
55/// b: String,
56/// }
57///
58/// let my_struct = MyStruct {
59/// a: "a".to_string(),
60/// extra: BTreeMap::from([
61/// ("d".to_string(), "d".to_string()),
62/// ("c".to_string(), "c".to_string()),
63/// ]),
64/// b: "b".to_string(),
65/// };
66///
67/// let serialized = serde_json::to_string_pretty(&SortAlphabetically(&my_struct)).unwrap();
68/// assert_eq!(
69/// serialized,
70/// r#"{
71/// "a": "a",
72/// "b": "b",
73/// "c": "c",
74/// "d": "d"
75/// }"#
76/// );
77/// ```
78#[derive(Serialize)]
79pub struct SortAlphabetically<T: Serialize>(#[serde(serialize_with = "sort_alphabetically")] pub T);
80
81/// The root of the JSON of a Jupyter Notebook
82///
83/// Generated by <https://app.quicktype.io/> from
84/// <https://github.com/jupyter/nbformat/blob/16b53251aabf472ad9406ddb1f78b0421c014eeb/nbformat/v4/nbformat.v4.schema.json>
85/// Jupyter Notebook v4.5 JSON schema.
86#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
87#[serde(deny_unknown_fields)]
88pub struct RawNotebook {
89 /// Array of cells of the current notebook.
90 pub cells: Vec<Cell>,
91 /// Notebook root-level metadata.
92 pub metadata: RawNotebookMetadata,
93 /// Notebook format (major number). Incremented between backwards incompatible changes to the
94 /// notebook format.
95 pub nbformat: i64,
96 /// Notebook format (minor number). Incremented for backward compatible changes to the
97 /// notebook format.
98 pub nbformat_minor: i64,
99}
100
101/// String identifying the type of cell.
102#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
103#[serde(tag = "cell_type")]
104pub enum Cell {
105 #[serde(rename = "code")]
106 Code(CodeCell),
107 #[serde(rename = "markdown")]
108 Markdown(MarkdownCell),
109 #[serde(rename = "raw")]
110 Raw(RawCell),
111}
112
113/// Notebook raw nbconvert cell.
114#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
115#[serde(deny_unknown_fields)]
116pub struct RawCell {
117 #[serde(skip_serializing_if = "Option::is_none")]
118 pub attachments: Option<Value>,
119 /// Technically, id isn't required (it's not even present) in schema v4.0 through v4.4, but
120 /// it's required in v4.5. Main issue is that pycharm creates notebooks without an id
121 /// <https://youtrack.jetbrains.com/issue/PY-59438/Jupyter-notebooks-created-with-PyCharm-are-missing-the-id-field-in-cells-in-the-.ipynb-json>
122 #[serde(skip_serializing_if = "Option::is_none")]
123 pub id: Option<String>,
124 /// Cell-level metadata.
125 pub metadata: CellMetadata,
126 pub source: SourceValue,
127}
128
129/// Notebook markdown cell.
130#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
131#[serde(deny_unknown_fields)]
132pub struct MarkdownCell {
133 #[serde(skip_serializing_if = "Option::is_none")]
134 pub attachments: Option<Value>,
135 /// Technically, id isn't required (it's not even present) in schema v4.0 through v4.4, but
136 /// it's required in v4.5. Main issue is that pycharm creates notebooks without an id
137 /// <https://youtrack.jetbrains.com/issue/PY-59438/Jupyter-notebooks-created-with-PyCharm-are-missing-the-id-field-in-cells-in-the-.ipynb-json>
138 #[serde(skip_serializing_if = "Option::is_none")]
139 pub id: Option<String>,
140 /// Cell-level metadata.
141 pub metadata: CellMetadata,
142 pub source: SourceValue,
143}
144
145/// Notebook code cell.
146#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
147#[serde(deny_unknown_fields)]
148pub struct CodeCell {
149 /// The code cell's prompt number. Will be null if the cell has not been run.
150 pub execution_count: Option<i64>,
151 /// Technically, id isn't required (it's not even present) in schema v4.0 through v4.4, but
152 /// it's required in v4.5. Main issue is that pycharm creates notebooks without an id
153 /// <https://youtrack.jetbrains.com/issue/PY-59438/Jupyter-notebooks-created-with-PyCharm-are-missing-the-id-field-in-cells-in-the-.ipynb-json>
154 #[serde(skip_serializing_if = "Option::is_none")]
155 pub id: Option<String>,
156 /// Cell-level metadata.
157 pub metadata: CellMetadata,
158 /// Execution, display, or stream outputs.
159 pub outputs: Vec<Value>,
160 pub source: SourceValue,
161}
162
163/// Cell-level metadata.
164#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
165pub struct CellMetadata {
166 /// VS Code specific cell metadata.
167 ///
168 /// This is [`Some`] only if the cell's preferred language is different from the notebook's
169 /// preferred language.
170 /// <https://github.com/microsoft/vscode/blob/e6c009a3d4ee60f352212b978934f52c4689fbd9/extensions/ipynb/src/serializers.ts#L117-L122>
171 #[serde(skip_serializing_if = "Option::is_none")]
172 pub vscode: Option<CodeCellMetadataVSCode>,
173 /// For additional properties that isn't required by Ruff.
174 #[serde(flatten)]
175 pub extra: HashMap<String, Value>,
176}
177
178/// VS Code specific cell metadata.
179/// <https://github.com/microsoft/vscode/blob/e6c009a3d4ee60f352212b978934f52c4689fbd9/extensions/ipynb/src/serializers.ts#L104-L107>
180#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
181#[serde(rename_all = "camelCase")]
182pub struct CodeCellMetadataVSCode {
183 /// <https://code.visualstudio.com/docs/languages/identifiers>
184 pub language_id: String,
185}
186
187/// Notebook root-level metadata.
188#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
189pub struct RawNotebookMetadata {
190 /// The author(s) of the notebook document
191 #[serde(skip_serializing_if = "Option::is_none")]
192 pub authors: Option<Value>,
193 /// Kernel information.
194 #[serde(skip_serializing_if = "Option::is_none")]
195 pub kernelspec: Option<Kernelspec>,
196 /// Language information.
197 #[serde(skip_serializing_if = "Option::is_none")]
198 pub language_info: Option<LanguageInfo>,
199 /// Original notebook format (major number) before converting the notebook between versions.
200 /// This should never be written to a file.
201 #[serde(skip_serializing_if = "Option::is_none")]
202 pub orig_nbformat: Option<i64>,
203 /// The title of the notebook document
204 #[serde(skip_serializing_if = "Option::is_none")]
205 pub title: Option<String>,
206 /// For additional properties.
207 #[serde(flatten)]
208 pub extra: BTreeMap<String, Value>,
209}
210
211/// Kernel information.
212#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
213pub struct Kernelspec {
214 /// The language name. This isn't mentioned in the spec but is populated by various tools and
215 /// can be used as a fallback if [`language_info`] is missing.
216 ///
217 /// This is also used by VS Code to determine the preferred language of the notebook:
218 /// <https://github.com/microsoft/vscode/blob/1c31e758985efe11bc0453a45ea0bb6887e670a4/extensions/ipynb/src/deserializers.ts#L20-L22>.
219 ///
220 /// [`language_info`]: RawNotebookMetadata::language_info
221 #[serde(skip_serializing_if = "Option::is_none")]
222 pub language: Option<String>,
223 /// For additional properties that isn't required by Ruff.
224 #[serde(flatten)]
225 pub extra: HashMap<String, Value>,
226}
227
228/// Language information.
229#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
230pub struct LanguageInfo {
231 /// The codemirror mode to use for code in this language.
232 #[serde(skip_serializing_if = "Option::is_none")]
233 pub codemirror_mode: Option<Value>,
234 /// The file extension for files in this language.
235 #[serde(skip_serializing_if = "Option::is_none")]
236 pub file_extension: Option<String>,
237 /// The mimetype corresponding to files in this language.
238 #[serde(skip_serializing_if = "Option::is_none")]
239 pub mimetype: Option<String>,
240 /// The programming language which this kernel runs.
241 pub name: String,
242 /// The pygments lexer to use for code in this language.
243 #[serde(skip_serializing_if = "Option::is_none")]
244 pub pygments_lexer: Option<String>,
245 /// For additional properties.
246 #[serde(flatten)]
247 pub extra: BTreeMap<String, Value>,
248}
249
250/// mimetype output (e.g. text/plain), represented as either an array of strings or a
251/// string.
252///
253/// Contents of the cell, represented as an array of lines.
254///
255/// The stream's text output, represented as an array of strings.
256#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
257#[serde(untagged)]
258pub enum SourceValue {
259 String(String),
260 StringArray(Vec<String>),
261}