Skip to main content

spiral/
location.rs

1//! Locations resolved on a branch, and the Tasks that refresh their inventories.
2
3use uuid::Uuid;
4
5use crate::api::tasks::{
6    LocationRefreshDefinition, TaskDefinition, TaskReadTarget, TaskReadTargetLatestOnBranch,
7};
8use crate::catalog_types::ProjectBranchId;
9use crate::{Error, Project, ResourceAddress, Run, Snapshot, TaskHandle};
10
11const LOCATION_KIND: &str = "location";
12
13/// One Location at the commit a [`Snapshot`] selects.
14#[derive(Clone)]
15pub struct Location {
16    project: Project,
17    branch_id: Option<ProjectBranchId>,
18    resource_id: Uuid,
19    version_id: Uuid,
20}
21
22impl Project {
23    /// Pin the head of `branch` and resolve the Location at `path`.
24    pub async fn location(&self, branch: &str, path: &str) -> Result<Location, Error> {
25        self.branch(branch).await?.snapshot().location(path).await
26    }
27}
28
29impl Snapshot {
30    /// Resolve the Location at `path` in this snapshot.
31    pub async fn location(&self, path: &str) -> Result<Location, Error> {
32        let address = path.parse::<ResourceAddress>()?;
33        if address.reference().is_some() {
34            return Err(Error::invalid(
35                "select the Location's branch with the snapshot, not an `@` reference",
36            ));
37        }
38        let resource = self.resource_at_path(address.path()).await?;
39        if resource.descriptor.kind.0 != LOCATION_KIND {
40            return Err(Error::invalid(format!("`{path}` is not a Location")));
41        }
42        Ok(Location {
43            project: self.project.clone(),
44            branch_id: self.branch_id,
45            resource_id: resource.descriptor.resource_id.as_uuid(),
46            version_id: resource.version_id.as_uuid(),
47        })
48    }
49}
50
51impl Location {
52    /// The Location's stable resource identifier.
53    pub fn resource_id(&self) -> Uuid {
54        self.resource_id
55    }
56
57    /// The exact Location version this handle resolved.
58    pub fn version_id(&self) -> Uuid {
59        self.version_id
60    }
61
62    /// Submit a refresh of the latest Location version on this snapshot's branch.
63    ///
64    /// The refresh lists the root, reconciles the inventory, and publishes the inventory
65    /// and a new Location version in one commit. Its result lists the new Location
66    /// version, preceded by the inventory version when the listing changed it. Returns
67    /// `None` when a refresh of this Location and branch is already waiting to run; that
68    /// refresh covers this request.
69    pub async fn refresh(&self, run: Run) -> Result<Option<TaskHandle>, Error> {
70        let branch_id = self.branch_id.ok_or_else(|| {
71            Error::invalid("refreshing a Location needs a branch snapshot, not an exact commit")
72        })?;
73        let definition = TaskDefinition::LocationRefresh(LocationRefreshDefinition {
74            read_target: TaskReadTarget::LatestOnBranch(TaskReadTargetLatestOnBranch {
75                branch_id: branch_id.as_uuid(),
76            }),
77            location_resource_id: self.resource_id,
78        });
79        self.project.submit_task(definition, None, run).await
80    }
81}