sb3_decoder/decoder/raw_structs/raw_block.rs
1//! The raw_block module contains the [`RawBlock`] struct and related functionality.
2
3use std::collections::HashMap;
4
5/// The [`RawBlock`] struct represents a block in its raw form in a Scratch 3.0 project.
6/// It contains the opcode, inputs, fields, next block, parent block, and shadow status.
7///
8/// Please refer to the [Scratch
9/// Wiki](https://en.scratch-wiki.info/wiki/Scratch_File_Format#Blocks) for more details on the
10/// structure of blocks in Scratch projects.
11#[derive(serde::Deserialize)]
12#[serde(rename_all = "camelCase")]
13pub struct RawBlock {
14 /// The opcode of the block (e.g., "event_whenflagclicked", "motion_movesteps").
15 pub opcode: String,
16
17 /// The next block ID in the sequence. `None` if there is no next block and the script ends
18 /// here.
19 pub next: Option<String>,
20
21 /// The previous block ID in the sequence if the block is a top-level block. If not, and this
22 /// block is a child of another block, this will link to the caller block.
23 pub parent: Option<String>,
24
25 /// The inputs to the block, represented as a map from input name to a [`RawBlockInput`].
26 pub inputs: HashMap<String, RawBlockInput>,
27
28 /// The fields of the block, represented as a map from field name to a tuple containing a
29 /// variable name and its ID.
30 pub fields: HashMap<String, (String, Option<String>)>,
31
32 /// Indicates whether the block is a top-level block in a script (cap block).
33 pub top_level: bool,
34}
35
36/// The [`RawBlockInput`] struct represents an input to a block in its raw form in a Scratch 3.0
37/// project.
38#[derive(Debug, Clone, PartialEq)]
39pub struct RawBlockInput {
40 /// The shadow type of the block.
41 /// 1 for shadow block,
42 /// 2 for non-shadow block.
43 /// 3 for shadow block but is obscured by a non-shadow block.
44 pub shadow: u8,
45
46 /// The type of the input.
47 /// 4 to 8 for a number.
48 /// 9 for hex color string.
49 /// 10 for string.
50 /// 11 for broadcast.
51 /// 12 and 13 for variables and lists, respectively.
52 ///
53 /// A variant is used by this crate to represent a block reference (i.e., when the input is
54 /// another block). In this case, the `kind` will be `0`, and the `value` will be a string
55 /// of the block ID.
56 pub kind: u8,
57
58 /// The value of the input.
59 /// number for 4 to 8,
60 /// string for 9 to 13.
61 pub value: serde_json::Value,
62}
63
64impl<'de> serde::Deserialize<'de> for RawBlockInput {
65 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
66 where
67 D: serde::Deserializer<'de>,
68 {
69 let vec: Vec<serde_json::Value> = serde::Deserialize::deserialize(deserializer)?;
70
71 if vec.len() < 2 {
72 return Err(serde::de::Error::custom("Invalid RawBlockInput format"));
73 }
74
75 let shadow = vec[0]
76 .as_u64()
77 .ok_or_else(|| serde::de::Error::custom("Invalid shadow value"))? as u8;
78
79 // Case 1: index 1 is a block ID (string)
80 if let Some(block_id) = vec[1].as_str() {
81 return Ok(RawBlockInput {
82 shadow,
83 kind: 0, // Use 0 or a special marker for "block ref"
84 value: serde_json::Value::String(block_id.to_string()),
85 });
86 }
87
88 // Case 2: index 1 is an array [kind, value]
89 if let Some(sub_vec) = vec[1].as_array() {
90 if sub_vec.len() < 2 {
91 return Err(serde::de::Error::custom("Invalid sub-array format"));
92 }
93
94 let kind = sub_vec[0]
95 .as_u64()
96 .ok_or_else(|| serde::de::Error::custom("Invalid type value"))? as u8;
97
98 let value = sub_vec[1].clone();
99
100 return Ok(RawBlockInput { shadow, kind, value });
101 }
102
103 Err(serde::de::Error::custom("Invalid RawBlockInput second element"))
104 }
105}