scrypto_bindgen/
schema.rs

1use radix_blueprint_schema_init::*;
2use radix_common::prelude::*;
3use radix_engine_interface::blueprints::package::*;
4use std::collections::BTreeMap;
5use std::fmt::{Debug, Display};
6
7pub trait PackageSchemaResolver {
8    fn lookup_schema(&self, schema_hash: &SchemaHash) -> Option<Rc<VersionedScryptoSchema>>;
9
10    fn resolve_type_kind(
11        &self,
12        type_identifier: &ScopedTypeId,
13    ) -> Result<LocalTypeKind<ScryptoCustomSchema>, SchemaError>;
14
15    fn resolve_type_metadata(
16        &self,
17        type_identifier: &ScopedTypeId,
18    ) -> Result<TypeMetadata, SchemaError>;
19
20    fn resolve_type_validation(
21        &self,
22        type_identifier: &ScopedTypeId,
23    ) -> Result<TypeValidation<ScryptoCustomTypeValidation>, SchemaError>;
24
25    fn package_address(&self) -> PackageAddress;
26}
27
28pub fn package_interface_from_package_definition<S>(
29    package_definition: BTreeMap<BlueprintVersionKey, BlueprintDefinition>,
30    schema_resolver: &S,
31) -> Result<PackageInterface, SchemaError>
32where
33    S: PackageSchemaResolver,
34{
35    let mut package_interface = PackageInterface::default();
36
37    for (blueprint_key, blueprint_definition) in package_definition.into_iter() {
38        let blueprint_name = blueprint_key.blueprint;
39
40        if let Some((_, fields)) = blueprint_definition.interface.state.fields {
41            for field in fields {
42                if let BlueprintPayloadDef::Static(scoped_type_id) = field.field {
43                    package_interface.auxiliary_types.insert(scoped_type_id);
44                }
45            }
46        }
47
48        let functions = &mut package_interface
49            .blueprints
50            .entry(blueprint_name)
51            .or_default()
52            .functions;
53
54        for (function_name, function_schema) in blueprint_definition.interface.functions {
55            let BlueprintPayloadDef::Static(input_type_identifier) = &function_schema.input else {
56                Err(SchemaError::GenericTypeRefsNotSupported)?
57            };
58
59            // Input Types
60            let inputs_scoped_type_id = {
61                let type_kind = schema_resolver.resolve_type_kind(input_type_identifier)?;
62                if let TypeKind::Tuple { field_types } = type_kind {
63                    Ok(field_types
64                        .into_iter()
65                        .map(|local_type_id| ScopedTypeId(input_type_identifier.0, local_type_id))
66                        .collect::<Vec<_>>())
67                } else {
68                    Err(SchemaError::FunctionInputIsNotATuple(
69                        *input_type_identifier,
70                    ))
71                }
72            }?;
73
74            // Input Field Names
75            let inputs_field_names = {
76                let type_metadata = schema_resolver.resolve_type_metadata(input_type_identifier)?;
77                match type_metadata.child_names.as_ref() {
78                    /* Encountered a struct with field names, return them. */
79                    Some(ChildNames::NamedFields(field_names)) => field_names
80                        .iter()
81                        .map(|entry| entry.as_ref().to_owned())
82                        .collect::<Vec<_>>(),
83                    /* A struct that has enum variants?? */
84                    Some(ChildNames::EnumVariants(..)) => {
85                        panic!(
86                            "We have checked that this is a Tuple and it can't have enum variants."
87                        )
88                    }
89                    /* Encountered a tuple-struct. Generate field names as `arg{n}` */
90                    None => (0..inputs_scoped_type_id.len())
91                        .map(|i| format!("arg{i}"))
92                        .collect::<Vec<_>>(),
93                }
94            };
95
96            // Output types
97            let BlueprintPayloadDef::Static(output_local_type_index) = &function_schema.output
98            else {
99                return Err(SchemaError::GenericTypeRefsNotSupported);
100            };
101
102            // Auxiliary types
103            // The auxiliary types are found by walking the input and output of each function and
104            // storing the type-ids encountered in the inputs and outputs.
105            for input_type in inputs_scoped_type_id.iter() {
106                get_scoped_type_ids_in_path(
107                    input_type,
108                    schema_resolver,
109                    &mut package_interface.auxiliary_types,
110                )?;
111            }
112            get_scoped_type_ids_in_path(
113                output_local_type_index,
114                schema_resolver,
115                &mut package_interface.auxiliary_types,
116            )?;
117
118            let function = Function {
119                ident: function_name.to_owned(),
120                receiver: function_schema.receiver.clone(),
121                arguments: inputs_field_names
122                    .into_iter()
123                    .zip(inputs_scoped_type_id)
124                    .collect::<IndexMap<String, ScopedTypeId>>(),
125                returns: *output_local_type_index,
126            };
127            functions.push(function);
128        }
129    }
130
131    Ok(package_interface)
132}
133
134#[derive(Clone, Debug, Default)]
135pub struct PackageInterface {
136    /// The interface definition of the various blueprints contained in the package. The key is the
137    /// blueprint name and the value is the interface of the blueprint.
138    pub blueprints: IndexMap<String, BlueprintInterface>,
139    /// A set of [`ScopedTypeId`] of the auxiliary types found in the package interface. Auxiliary
140    /// types are types which appear somewhere in the interface of the package. As an example, an
141    /// enum that appears as a function input that requires generation for the interface to make
142    /// sense.
143    pub auxiliary_types: HashSet<ScopedTypeId>,
144}
145
146#[derive(Clone, Debug, Default)]
147pub struct BlueprintInterface {
148    /// The functions and methods encountered in the blueprint interface.
149    pub functions: Vec<Function>,
150}
151
152#[derive(Clone, Debug)]
153pub struct Function {
154    pub ident: String,
155    pub receiver: Option<ReceiverInfo>,
156    pub arguments: IndexMap<String, ScopedTypeId>,
157    pub returns: ScopedTypeId,
158}
159
160fn get_scoped_type_ids_in_path<S>(
161    type_id: &ScopedTypeId,
162    schema_resolver: &S,
163    collection: &mut HashSet<ScopedTypeId>,
164) -> Result<(), SchemaError>
165where
166    S: PackageSchemaResolver,
167{
168    if !collection.insert(*type_id) {
169        return Ok(());
170    }
171
172    let type_kind = schema_resolver.resolve_type_kind(type_id)?;
173
174    match type_kind {
175        TypeKind::Any
176        | TypeKind::Bool
177        | TypeKind::I8
178        | TypeKind::I16
179        | TypeKind::I32
180        | TypeKind::I64
181        | TypeKind::I128
182        | TypeKind::U8
183        | TypeKind::U16
184        | TypeKind::U32
185        | TypeKind::U64
186        | TypeKind::U128
187        | TypeKind::String
188        | TypeKind::Custom(ScryptoCustomTypeKind::Reference)
189        | TypeKind::Custom(ScryptoCustomTypeKind::Own)
190        | TypeKind::Custom(ScryptoCustomTypeKind::Decimal)
191        | TypeKind::Custom(ScryptoCustomTypeKind::PreciseDecimal)
192        | TypeKind::Custom(ScryptoCustomTypeKind::NonFungibleLocalId) => {}
193        TypeKind::Array { element_type } => {
194            let scoped_type_id = ScopedTypeId(type_id.0, element_type);
195            get_scoped_type_ids_in_path(&scoped_type_id, schema_resolver, collection)?;
196        }
197        TypeKind::Tuple { field_types } => {
198            for field_type in field_types {
199                let scoped_type_id = ScopedTypeId(type_id.0, field_type);
200                get_scoped_type_ids_in_path(&scoped_type_id, schema_resolver, collection)?;
201            }
202        }
203        TypeKind::Enum { variants } => {
204            for field_types in variants.values() {
205                for field_type in field_types {
206                    let scoped_type_id = ScopedTypeId(type_id.0, *field_type);
207                    get_scoped_type_ids_in_path(&scoped_type_id, schema_resolver, collection)?;
208                }
209            }
210        }
211        TypeKind::Map {
212            key_type,
213            value_type,
214        } => {
215            let scoped_type_id = ScopedTypeId(type_id.0, key_type);
216            get_scoped_type_ids_in_path(&scoped_type_id, schema_resolver, collection)?;
217
218            let scoped_type_id = ScopedTypeId(type_id.0, value_type);
219            get_scoped_type_ids_in_path(&scoped_type_id, schema_resolver, collection)?;
220        }
221    }
222
223    Ok(())
224}
225
226#[derive(Clone, Debug)]
227pub enum SchemaError {
228    FunctionInputIsNotATuple(ScopedTypeId),
229    NonExistentLocalTypeIndex(LocalTypeId),
230    FailedToGetSchemaFromSchemaHash,
231    GenericTypeRefsNotSupported,
232    NoNameFound,
233}
234
235impl Display for SchemaError {
236    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237        Debug::fmt(&self, f)
238    }
239}