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}