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
/// A trait for identifiable resources within Cloud Spanner.
///
/// The format is typically something like `<kind>/<name>` where `kind` is the plural form
/// of the resource kind, e.g.: `projects` or `databases` and `name` is the name of a particular instance of that resource.
///
/// For example, the database resource named `my-database` in the `my-instance` instance in the `my-gcp-project` project
/// will have the following identifier: `projects/my-gcp-project/instances/my-instance/databases/my-database`.
pub trait SpannerResource {
    /// The name of this particular instance of the resource.
    fn name(&self) -> &str;

    /// The full path to all resources under the same parent.
    ///
    /// For example, for the `InstanceId` resource, this would return something like `projects/my-project/instances`
    fn resources_path(&self) -> String;

    /// The full path to this particular resource.
    ///
    /// For example, `projects/my-project/instances/my-instance`
    fn id(&self) -> String {
        format!("{}/{}", self.resources_path(), self.name())
    }
}

/// The resource that identifies a particular GCP project.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProjectId(String);

impl ProjectId {
    /// Creates a new `ProjectId` resource using the specified name.
    pub fn new(name: &str) -> Self {
        Self(name.to_string())
    }
}

impl SpannerResource for ProjectId {
    fn name(&self) -> &str {
        &self.0
    }

    fn resources_path(&self) -> String {
        "projects".to_string()
    }
}

/// The resource that identifies a Cloud Spanner instance in a particular GCP project.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InstanceId(ProjectId, String);

impl InstanceId {
    /// Creates a new `InstanceId` resource using the specified project resource and name.
    pub fn new(project: ProjectId, name: &str) -> Self {
        Self(project, name.to_string())
    }

    /// Returns a reference to the project hosting this Cloud Spanner instance.
    pub fn project(&self) -> &ProjectId {
        &self.0
    }
}

impl SpannerResource for InstanceId {
    fn name(&self) -> &str {
        &self.1
    }

    fn resources_path(&self) -> String {
        format!("{}/instances", self.0.id())
    }
}

/// The resource that identifies a Cloud Spanner database in a particular Cloud Spanner instance.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DatabaseId(InstanceId, String);
impl DatabaseId {
    /// Creates a new `DatabaseId` resource using the specified instance resource and name.
    pub fn new(instance: InstanceId, name: &str) -> Self {
        Self(instance, name.to_string())
    }
}

impl SpannerResource for DatabaseId {
    fn name(&self) -> &str {
        &self.1
    }

    fn resources_path(&self) -> String {
        format!("{}/databases", self.0.id())
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_project_id() {
        let project_id = ProjectId::new("test-project");
        assert_eq!(project_id.name(), "test-project");
        assert_eq!(project_id.resources_path(), "projects".to_string());
        assert_eq!(project_id.id(), "projects/test-project".to_string());
    }
    #[test]
    fn test_instance_id() {
        let instance_id = InstanceId::new(ProjectId::new("test-project"), "test-instance");
        assert_eq!(instance_id.name(), "test-instance");
        assert_eq!(
            instance_id.resources_path(),
            "projects/test-project/instances"
        );
        assert_eq!(
            instance_id.id(),
            "projects/test-project/instances/test-instance"
        );
    }

    #[test]
    fn test_database_id() {
        let database_id = DatabaseId::new(
            InstanceId::new(ProjectId::new("test-project"), "test-instance"),
            "test-database",
        );
        assert_eq!(database_id.name(), "test-database");
        assert_eq!(
            database_id.resources_path(),
            "projects/test-project/instances/test-instance/databases"
        );
        assert_eq!(
            database_id.id(),
            "projects/test-project/instances/test-instance/databases/test-database"
        );
    }
}