Line data Source code
1 : use async_graphql::SimpleObject;
2 : use chrono::{DateTime, Utc};
3 : use serde::Serialize;
4 : use std::env;
5 : use utoipa::ToSchema;
6 :
7 : /// Build metadata exposed via GraphQL, REST, and logs.
8 : #[derive(Clone, Debug, PartialEq, Eq, Serialize, SimpleObject, ToSchema)]
9 : #[graphql(rename_fields = "camelCase")]
10 : #[serde(rename_all = "camelCase")]
11 : pub struct BuildInfo {
12 : pub version: String,
13 : pub git_sha: String,
14 : pub build_time: String,
15 : #[serde(skip_serializing_if = "Option::is_none")]
16 : pub message: Option<String>,
17 : }
18 :
19 : #[derive(Clone, Debug)]
20 : pub struct BuildInfoProvider {
21 : info: BuildInfo,
22 : }
23 :
24 : impl BuildInfoProvider {
25 : /// Construct a provider using environment variables, falling back to sensible defaults.
26 : #[must_use]
27 71 : pub fn from_env() -> Self {
28 284 : Self::from_lookup(|key| env::var(key).ok())
29 71 : }
30 :
31 : /// Construct a provider using a custom lookup function (useful for tests).
32 : #[must_use]
33 75 : pub fn from_lookup<F>(mut lookup: F) -> Self
34 75 : where
35 75 : F: FnMut(&str) -> Option<String>,
36 : {
37 75 : let version = lookup("APP_VERSION")
38 75 : .or_else(|| lookup("VERSION"))
39 75 : .unwrap_or_else(|| "dev".to_string());
40 :
41 75 : let git_sha = lookup("GIT_SHA").unwrap_or_else(|| "unknown".to_string());
42 :
43 75 : let build_time = lookup("BUILD_TIME")
44 75 : .and_then(|value| normalize_build_time(&value))
45 75 : .unwrap_or_else(|| "unknown".to_string());
46 :
47 75 : let message = lookup("BUILD_MESSAGE");
48 :
49 75 : let info = BuildInfo {
50 75 : version,
51 75 : git_sha,
52 75 : build_time,
53 75 : message,
54 75 : };
55 :
56 75 : Self { info }
57 75 : }
58 :
59 : /// Fetch the resolved build info values.
60 : #[must_use]
61 13 : pub fn build_info(&self) -> BuildInfo {
62 13 : self.info.clone()
63 13 : }
64 : }
65 :
66 73 : fn normalize_build_time(value: &str) -> Option<String> {
67 73 : DateTime::parse_from_rfc3339(value)
68 73 : .or_else(|_| DateTime::parse_from_rfc3339(&format!("{value}Z")))
69 73 : .map(|dt| dt.with_timezone(&Utc).to_rfc3339())
70 73 : .ok()
71 73 : }
|