posthog_cli/api/
releases.rs1use std::collections::HashMap;
2
3use anyhow::{Context, Result};
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6use tracing::{info, warn};
7use uuid::Uuid;
8
9use crate::{
10 api::client::ClientError,
11 invocation_context::context,
12 utils::{files::content_hash, git::GitInfo},
13};
14
15#[derive(Debug, Clone, Deserialize)]
16pub struct Release {
17 pub id: Uuid,
18 pub hash_id: String,
19 pub version: String,
20 pub project: String,
21}
22
23#[derive(Debug, Clone, Default)]
24pub struct ReleaseBuilder {
25 project: Option<String>,
26 version: Option<String>,
27 metadata: HashMap<String, Value>,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
32struct CreateReleaseRequest {
33 #[serde(skip_serializing_if = "HashMap::is_empty")]
34 pub metadata: HashMap<String, Value>,
35 pub hash_id: String,
36 pub version: String,
37 pub project: String,
38}
39
40impl Release {
41 pub fn lookup(project: &str, version: &str) -> Result<Option<Self>, ClientError> {
42 let hash_id = content_hash([project, version]);
43 let client = &context().client;
44
45 let path = format!("error_tracking/releases/hash/{hash_id}");
46 let response = client.send_get(&path, |req| req);
47
48 if let Err(err) = response {
49 if let ClientError::ApiError(404, _, _) = err {
50 warn!("release {} of project {} not found", version, project);
51 return Ok(None);
52 }
53 warn!("failed to get release from hash: {}", err);
54 Err(err)
55 } else {
56 info!("found release {} of project {}", version, project);
57 Ok(Some(response.unwrap().json()?))
58 }
59 }
60}
61
62impl ReleaseBuilder {
63 pub fn init_from_git(info: GitInfo) -> Self {
64 let mut metadata = HashMap::new();
65 metadata.insert(
66 "git".to_string(),
67 serde_json::to_value(info.clone()).expect("can serialize gitinfo"),
68 );
69
70 Self {
71 metadata,
72 version: Some(info.commit_id), project: info.repo_name,
74 }
75 }
76
77 pub fn with_git(&mut self, info: GitInfo) -> &mut Self {
78 self.with_metadata("git", info)
79 .expect("We can serialise git info")
80 }
81
82 pub fn with_metadata<T>(&mut self, key: &str, val: T) -> Result<&mut Self>
83 where
84 T: Serialize,
85 {
86 self.metadata
87 .insert(key.to_string(), serde_json::to_value(val)?);
88 Ok(self)
89 }
90
91 pub fn with_project(&mut self, project: &str) -> &mut Self {
92 self.project = Some(project.to_string());
93 self
94 }
95
96 pub fn with_version(&mut self, version: &str) -> &mut Self {
97 self.version = Some(version.to_string());
98 self
99 }
100
101 pub fn can_create(&self) -> bool {
102 self.version.is_some() && self.project.is_some()
103 }
104
105 pub fn missing(&self) -> Vec<&str> {
106 let mut missing = Vec::new();
107
108 if self.version.is_none() {
109 missing.push("version");
110 }
111 if self.project.is_none() {
112 missing.push("project");
113 }
114 missing
115 }
116
117 pub fn fetch_or_create(self) -> Result<Release> {
118 if !self.can_create() {
119 anyhow::bail!(
120 "Tried to create a release while missing key fields: {}",
121 self.missing().join(", ")
122 )
123 }
124 let version = self.version.as_ref().unwrap();
125 let project = self.project.as_ref().unwrap();
126 if let Some(release) = Release::lookup(project, version)? {
127 Ok(release)
128 } else {
129 self.create_release()
130 }
131 }
132
133 pub fn create_release(self) -> Result<Release> {
134 if !self.can_create() {
138 anyhow::bail!(
139 "Tried to create a release while missing key fields: {}",
140 self.missing().join(", ")
141 )
142 }
143 let version = self.version.unwrap();
144 let project = self.project.unwrap();
145 let metadata = self.metadata;
146
147 let hash_id = content_hash([project.as_bytes(), version.as_bytes()]);
148
149 let request = CreateReleaseRequest {
150 metadata,
151 hash_id,
152 version,
153 project,
154 };
155
156 let client = &context().client;
157
158 let response = client
159 .send_post("error_tracking/releases", |req| req.json(&request))
160 .context("Failed to create release")?;
161
162 let response = response.json::<Release>()?;
163 info!(
164 "Release {} of {} created successfully! {}",
165 request.version, request.project, response.id
166 );
167 Ok(response)
168 }
169}