sugar_cli/validate/
format.rs

1use anyhow::Result;
2use serde::{Deserialize, Serialize};
3
4use super::ValidateParserError;
5use crate::{common::*, validate::parser};
6
7#[derive(Debug, Clone, Deserialize, Default, Serialize)]
8pub struct Metadata {
9    pub name: String,
10    pub symbol: Option<String>,
11    pub description: String,
12    #[serde(skip_serializing_if = "Option::is_none")]
13    pub seller_fee_basis_points: Option<u16>,
14    pub image: String,
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub animation_url: Option<String>,
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub external_url: Option<String>,
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub attributes: Option<Vec<Attribute>>,
21    pub properties: Property,
22}
23
24impl Metadata {
25    pub fn validate(&mut self) -> Result<(), ValidateParserError> {
26        parser::check_name(&self.name)?;
27        parser::check_url(&self.image)?;
28
29        // If users are using the old format, we do validation on those values.
30        if let Some(sfbp) = &self.seller_fee_basis_points {
31            parser::check_seller_fee_basis_points(*sfbp)?;
32        }
33        if let Some(symbol) = &self.symbol {
34            parser::check_symbol(symbol)?;
35        }
36
37        if let Some(creators) = &self.properties.creators {
38            parser::check_creators_shares(creators)?;
39            parser::check_creators_addresses(creators)?;
40        }
41
42        if self.properties.category.is_none() {
43            let category = match &self.animation_url {
44                Some(_) => "video",
45                None => "image",
46            };
47            self.properties.category = Some(category.to_string());
48
49            println!(
50                "{} missing `properties.category` for nft {}, defaulting to {}",
51                WARNING_EMOJI, &self.name, category
52            );
53        }
54        parser::check_category(
55            self.properties
56                .category
57                .as_ref()
58                .expect("unreachable, should never throw"),
59        )?;
60
61        if let Some(animation_url) = &self.animation_url {
62            parser::check_url(animation_url)?;
63        }
64
65        if let Some(external_url) = &self.external_url {
66            parser::check_url(external_url)?;
67        }
68
69        Ok(())
70    }
71}
72
73#[derive(Debug, Clone, Deserialize, Default, Serialize)]
74pub struct Property {
75    pub files: Vec<FileAttr>,
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub creators: Option<Vec<Creator>>,
78    pub category: Option<String>,
79}
80
81#[derive(Debug, Clone, Deserialize, Default, Serialize)]
82pub struct Creator {
83    pub address: String,
84    pub share: u16,
85}
86
87#[derive(Debug, Clone, Deserialize, Default, Serialize)]
88pub struct Attribute {
89    pub trait_type: String,
90    pub value: String,
91}
92
93#[derive(Debug, Clone, Deserialize, Default, Serialize)]
94pub struct FileAttr {
95    pub uri: String,
96    #[serde(rename = "type")]
97    pub file_type: String,
98    #[serde(default, skip_serializing_if = "bool_is_false")]
99    pub cdn: bool,
100}
101
102fn bool_is_false(value: &bool) -> bool {
103    !value
104}