1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
use crate::value::ValueEnum;
use crate::{
    DataSetError, DataSetResult, HashMap, SchemaFingerprint, SchemaLinker, SchemaLinkerResult,
    SchemaNamedType, Value,
};
use std::sync::Arc;
use uuid::Uuid;

/// Accumulates linked types and can be used to create a schema. This allows validation of types
/// and some work that can be pre-cached, such as generating default values for enums. (Values
/// are not a concept that exists in the hydrate-schema crate)
#[derive(Default)]
pub struct SchemaSetBuilder {
    schemas_by_type_uuid: HashMap<Uuid, SchemaFingerprint>,
    schemas_by_name: HashMap<String, SchemaFingerprint>,
    schemas: HashMap<SchemaFingerprint, SchemaNamedType>,
    default_enum_values: HashMap<SchemaFingerprint, Value>,
}

impl SchemaSetBuilder {
    pub fn build(self) -> SchemaSet {
        let inner = SchemaSetInner {
            schemas_by_type_uuid: self.schemas_by_type_uuid,
            schemas_by_name: self.schemas_by_name,
            schemas: self.schemas,
            default_enum_values: self.default_enum_values,
        };

        SchemaSet {
            inner: Arc::new(inner),
        }
    }

    pub fn add_linked_types(
        &mut self,
        linker: SchemaLinker,
    ) -> SchemaLinkerResult<()> {
        let linked = linker.link_schemas()?;

        //TODO: check no name collisions and merge with DB

        for (k, v) in linked.schemas {
            if let Some(enum_schema) = v.try_as_enum() {
                let default_value = Value::Enum(ValueEnum::new(
                    enum_schema.default_value().name().to_string(),
                ));
                let old = self.default_enum_values.insert(k, default_value.clone());
                if let Some(old) = old {
                    assert_eq!(old.as_enum().unwrap(), default_value.as_enum().unwrap());
                }
            }
            let v_fingerprint = v.fingerprint();
            let old = self.schemas.insert(k, v);
            if let Some(old) = old {
                assert_eq!(old.fingerprint(), v_fingerprint);
            }
        }

        for (k, v) in linked.schemas_by_name {
            let old = self.schemas_by_name.insert(k, v);
            assert!(old.is_none());
        }

        for (k, v) in linked.schemas_by_type_uuid {
            let old = self.schemas_by_type_uuid.insert(k, v);
            assert!(old.is_none());
        }

        Ok(())
    }

    pub fn restore_named_types(
        &mut self,
        named_types: Vec<SchemaNamedType>,
    ) {
        for named_type in named_types {
            self.schemas.insert(named_type.fingerprint(), named_type);
        }
    }
}

pub struct SchemaSetInner {
    schemas_by_type_uuid: HashMap<Uuid, SchemaFingerprint>,
    schemas_by_name: HashMap<String, SchemaFingerprint>,
    schemas: HashMap<SchemaFingerprint, SchemaNamedType>,
    default_enum_values: HashMap<SchemaFingerprint, Value>,
}

#[derive(Clone)]
pub struct SchemaSet {
    inner: Arc<SchemaSetInner>,
}

impl SchemaSet {
    pub fn schemas(&self) -> &HashMap<SchemaFingerprint, SchemaNamedType> {
        &self.inner.schemas
    }

    pub fn schemas_by_type_uuid(&self) -> &HashMap<Uuid, SchemaFingerprint> {
        &self.inner.schemas_by_type_uuid
    }

    pub fn default_value_for_enum(
        &self,
        fingerprint: SchemaFingerprint,
    ) -> Option<&Value> {
        self.inner.default_enum_values.get(&fingerprint)
    }

    pub fn find_named_type_by_type_uuid(
        &self,
        type_uuid: Uuid,
    ) -> DataSetResult<&SchemaNamedType> {
        Ok(self
            .try_find_named_type_by_type_uuid(type_uuid)
            .ok_or(DataSetError::SchemaNotFound)?)
    }

    pub fn try_find_named_type_by_type_uuid(
        &self,
        type_uuid: Uuid,
    ) -> Option<&SchemaNamedType> {
        self.inner
            .schemas_by_type_uuid
            .get(&type_uuid)
            .map(|fingerprint| self.find_named_type_by_fingerprint(*fingerprint))
            .flatten()
    }

    pub fn find_named_type(
        &self,
        name: impl AsRef<str>,
    ) -> DataSetResult<&SchemaNamedType> {
        Ok(self
            .try_find_named_type(name)
            .ok_or(DataSetError::SchemaNotFound)?)
    }

    pub fn try_find_named_type(
        &self,
        name: impl AsRef<str>,
    ) -> Option<&SchemaNamedType> {
        self.inner
            .schemas_by_name
            .get(name.as_ref())
            .map(|fingerprint| self.find_named_type_by_fingerprint(*fingerprint))
            .flatten()
    }

    pub fn find_named_type_by_fingerprint(
        &self,
        fingerprint: SchemaFingerprint,
    ) -> Option<&SchemaNamedType> {
        self.inner.schemas.get(&fingerprint)
    }
}