grafbase_local_common/
environment.rs

1#![allow(dead_code)]
2
3use crate::{
4    consts::{
5        DATABASE_DIRECTORY, DOT_GRAFBASE_DIRECTORY, GRAFBASE_DIRECTORY_NAME, GRAFBASE_SCHEMA_FILE_NAME, REGISTRY_FILE,
6        RESOLVERS_DIRECTORY_NAME,
7    },
8    errors::CommonError,
9};
10use once_cell::sync::OnceCell;
11use std::{
12    env,
13    path::{Path, PathBuf},
14};
15
16/// a static representation of the current environment
17///
18/// must be initialized before use
19#[derive(Debug)]
20pub struct Environment {
21    /// the path of the (assumed) user project root (`$PROJECT`), the nearest ancestor directory
22    /// with a `grafbase/schema.graphql` file
23    pub project_path: PathBuf,
24    /// the path of `$PROJECT/.grafbase/`, the Grafbase local developer tool cache and database directory,
25    /// in the nearest ancestor directory with `grafbase/schema.graphql`
26    pub project_dot_grafbase_path: PathBuf,
27    /// the path of `$PROJECT/grafbase/`, the Grafbase schema directory in the nearest ancestor directory
28    /// with `grafbase/schema.graphql`
29    pub project_grafbase_path: PathBuf,
30    /// the path of `$PROJECT/grafbase/schema.graphql`, the Grafbase schema,
31    /// in the nearest ancestor directory with said directory and file
32    pub project_grafbase_schema_path: PathBuf,
33    /// the path of `$HOME/.grafbase`, the user level local developer tool cache directory
34    pub user_dot_grafbase_path: PathBuf,
35    /// the path of `$PROJECT/.grafbase/registry.json`, the registry derived from `schema.graphql`,
36    /// in the nearest ancestor directory with a `grabase/schema.graphql` file
37    pub project_grafbase_registry_path: PathBuf,
38    /// the path of the `grafbase/resolvers` directory.
39    pub resolvers_source_path: PathBuf,
40    /// the path within `$PROJECT/.grafbase/` containing build artifacts for custom resolvers.
41    pub resolvers_build_artifact_path: PathBuf,
42    /// the path within '$PROJECT/.grafbase' containing the database
43    pub database_directory_path: PathBuf,
44}
45
46/// static singleton for the environment struct
47static ENVIRONMENT: OnceCell<Environment> = OnceCell::new();
48
49#[must_use]
50pub fn get_user_dot_grafbase_path() -> Option<PathBuf> {
51    dirs::home_dir().map(|home| home.join(DOT_GRAFBASE_DIRECTORY))
52}
53
54impl Environment {
55    /// initializes the static Environment instance
56    ///
57    /// # Errors
58    ///
59    /// returns [`CommonError::ReadCurrentDirectory`] if the current directory path cannot be read
60    ///
61    /// returns [`CommonError::FindGrafbaseDirectory`] if the grafbase directory is not found
62    pub fn try_init() -> Result<(), CommonError> {
63        let project_grafbase_schema_path =
64            Self::get_project_grafbase_path()?.ok_or(CommonError::FindGrafbaseDirectory)?;
65        let project_grafbase_path = project_grafbase_schema_path
66            .parent()
67            .expect("the schema directory must have a parent by definiton")
68            .to_path_buf();
69        let project_path = project_grafbase_path
70            .parent()
71            .expect("the grafbase directory must have a parent directory by definition")
72            .to_path_buf();
73        let project_dot_grafbase_path = project_path.join(DOT_GRAFBASE_DIRECTORY);
74        let user_dot_grafbase_path =
75            get_user_dot_grafbase_path().unwrap_or_else(|| project_grafbase_path.join(DOT_GRAFBASE_DIRECTORY));
76        let project_grafbase_registry_path = project_dot_grafbase_path.join(REGISTRY_FILE);
77        let resolvers_source_path = project_grafbase_path.join(RESOLVERS_DIRECTORY_NAME);
78        let resolvers_build_artifact_path = project_dot_grafbase_path.join(RESOLVERS_DIRECTORY_NAME);
79        let database_directory_path = project_dot_grafbase_path.join(DATABASE_DIRECTORY);
80        ENVIRONMENT
81            .set(Self {
82                project_path,
83                project_dot_grafbase_path,
84                project_grafbase_path,
85                project_grafbase_schema_path,
86                user_dot_grafbase_path,
87                project_grafbase_registry_path,
88                resolvers_source_path,
89                resolvers_build_artifact_path,
90                database_directory_path,
91            })
92            .expect("cannot set environment twice");
93
94        Ok(())
95    }
96
97    /// returns a reference to the static Environment instance
98    ///
99    /// # Panics
100    ///
101    /// panics if the Environment object was not previously initialized using `Environment::try_init()`
102    #[must_use]
103    pub fn get() -> &'static Self {
104        match ENVIRONMENT.get() {
105            Some(environment) => environment,
106            // must be initialized in `main`
107            #[allow(clippy::panic)]
108            None => panic!("the environment object is uninitialized"),
109        }
110    }
111
112    /// searches for the closest ancestor directory
113    /// named "grafbase" which contains a "schema.graphql" file.
114    /// if already inside a `grafbase` directory, looks for `schema.graphql` inside the current ancestor as well
115    ///
116    /// # Errors
117    ///
118    /// returns [`CommonError::ReadCurrentDirectory`] if the current directory path cannot be read
119    fn get_project_grafbase_path() -> Result<Option<PathBuf>, CommonError> {
120        let project_grafbase_path = env::current_dir()
121            .map_err(|_| CommonError::ReadCurrentDirectory)?
122            .ancestors()
123            .find_map(|ancestor| {
124                let mut path = PathBuf::from(ancestor);
125
126                // if we're looking at a directory called `grafbase`, also check for the schema in the current directory
127                if let Some(first) = path.components().next() {
128                    if Path::new(&first) == PathBuf::from(GRAFBASE_DIRECTORY_NAME) {
129                        path.push(GRAFBASE_SCHEMA_FILE_NAME);
130                        if path.is_file() {
131                            return Some(path);
132                        }
133                        path.pop();
134                    }
135                }
136
137                path.push(
138                    [GRAFBASE_DIRECTORY_NAME, GRAFBASE_SCHEMA_FILE_NAME]
139                        .iter()
140                        .collect::<PathBuf>(),
141                );
142
143                if path.is_file() {
144                    Some(path)
145                } else {
146                    None
147                }
148            });
149
150        Ok(project_grafbase_path)
151    }
152}