1#![deny(missing_docs)]
17
18pub mod export;
19pub mod limits;
20#[cfg(feature = "media-grants")]
21pub mod media_grant;
22#[cfg(feature = "pagination")]
23pub mod pagination;
24#[cfg(feature = "webhook-signature")]
25pub mod webhook_signature;
26
27use std::{fmt, str::FromStr};
28
29use serde::{Deserialize, Serialize};
30use thiserror::Error;
31
32#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
34#[serde(rename_all = "lowercase")]
35pub enum DeploymentEnvironment {
36 #[default]
38 Local,
39 Testing,
41 Staging,
43 Production,
45}
46
47impl DeploymentEnvironment {
48 const fn as_str(self) -> &'static str {
49 match self {
50 Self::Local => "local",
51 Self::Testing => "testing",
52 Self::Staging => "staging",
53 Self::Production => "production",
54 }
55 }
56}
57
58impl fmt::Display for DeploymentEnvironment {
59 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
60 formatter.write_str(self.as_str())
61 }
62}
63
64impl FromStr for DeploymentEnvironment {
65 type Err = ParseEnvironmentError;
66
67 fn from_str(value: &str) -> Result<Self, Self::Err> {
68 match value {
69 "local" => Ok(Self::Local),
70 "testing" => Ok(Self::Testing),
71 "staging" => Ok(Self::Staging),
72 "production" => Ok(Self::Production),
73 _ => Err(ParseEnvironmentError(value.to_owned())),
74 }
75 }
76}
77
78#[derive(Clone, Debug, Error, Eq, PartialEq)]
80#[error("unsupported environment `{0}`; expected local, testing, staging, or production")]
81pub struct ParseEnvironmentError(String);
82
83#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
85#[serde(rename_all = "lowercase")]
86pub enum LogFormat {
87 #[default]
89 Auto,
90 Json,
92 Pretty,
94}
95
96impl LogFormat {
97 #[must_use]
99 pub const fn resolve(self, environment: DeploymentEnvironment) -> Self {
100 match (self, environment) {
101 (Self::Auto, DeploymentEnvironment::Local) => Self::Pretty,
102 (
103 Self::Auto,
104 DeploymentEnvironment::Testing
105 | DeploymentEnvironment::Staging
106 | DeploymentEnvironment::Production,
107 ) => Self::Json,
108 (explicit, _) => explicit,
109 }
110 }
111}
112
113#[derive(Clone, Copy, Debug, Eq, PartialEq)]
115pub enum ProcessKind {
116 Api,
118 Worker,
120 Migrate,
122 Seed,
124 Agent,
126}
127
128impl ProcessKind {
129 const fn as_str(self) -> &'static str {
130 match self {
131 Self::Api => "api",
132 Self::Worker => "worker",
133 Self::Migrate => "migrate",
134 Self::Seed => "seed",
135 Self::Agent => "agent",
136 }
137 }
138}
139
140impl fmt::Display for ProcessKind {
141 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
142 formatter.write_str(self.as_str())
143 }
144}
145
146#[derive(Clone, Debug, Eq, PartialEq)]
148pub struct ServiceIdentity {
149 product: String,
150 process: ProcessKind,
151 version: String,
152 commit: String,
153 environment: DeploymentEnvironment,
154}
155
156impl ServiceIdentity {
157 pub fn new(
159 product: impl Into<String>,
160 process: ProcessKind,
161 version: impl Into<String>,
162 commit: impl Into<String>,
163 environment: DeploymentEnvironment,
164 ) -> Self {
165 Self {
166 product: product.into(),
167 process,
168 version: version.into(),
169 commit: commit.into(),
170 environment,
171 }
172 }
173
174 pub fn product(&self) -> &str {
176 &self.product
177 }
178
179 pub const fn process(&self) -> ProcessKind {
181 self.process
182 }
183
184 pub fn version(&self) -> &str {
186 &self.version
187 }
188
189 pub fn commit(&self) -> &str {
191 &self.commit
192 }
193
194 pub const fn environment(&self) -> DeploymentEnvironment {
196 self.environment
197 }
198
199 pub fn service_name(&self) -> String {
201 format!("{}-{}", self.product, self.process)
202 }
203}
204
205#[derive(Clone, Debug, Eq, PartialEq)]
207pub struct BuildInfo {
208 version: String,
209 commit: String,
210 rust_version: String,
211}
212
213impl BuildInfo {
214 pub fn new(
216 version: impl Into<String>,
217 commit: impl Into<String>,
218 rust_version: impl Into<String>,
219 ) -> Self {
220 Self {
221 version: version.into(),
222 commit: commit.into(),
223 rust_version: rust_version.into(),
224 }
225 }
226
227 pub fn version(&self) -> &str {
229 &self.version
230 }
231
232 pub fn commit(&self) -> &str {
234 &self.commit
235 }
236
237 pub fn rust_version(&self) -> &str {
239 &self.rust_version
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246
247 #[test]
248 fn shared_vocabulary_follows_the_telemetry_contract() {
249 let identity = ServiceIdentity::new(
250 "orders",
251 ProcessKind::Api,
252 "1.2.3",
253 "abc123",
254 DeploymentEnvironment::Production,
255 );
256
257 assert_eq!(identity.service_name(), "orders-api");
258 assert_eq!(identity.environment().to_string(), "production");
259 assert_eq!(
260 LogFormat::Auto.resolve(DeploymentEnvironment::Production),
261 LogFormat::Json
262 );
263 }
264
265 #[test]
266 fn deployment_environments_parse_display_and_serialize() {
267 for (text, environment) in [
268 ("local", DeploymentEnvironment::Local),
269 ("testing", DeploymentEnvironment::Testing),
270 ("staging", DeploymentEnvironment::Staging),
271 ("production", DeploymentEnvironment::Production),
272 ] {
273 assert_eq!(text.parse::<DeploymentEnvironment>(), Ok(environment));
274 assert_eq!(environment.to_string(), text);
275 assert_eq!(
276 serde_json::to_string(&environment).expect("serialize environment"),
277 format!("\"{text}\"")
278 );
279 assert_eq!(
280 serde_json::from_str::<DeploymentEnvironment>(&format!("\"{text}\""))
281 .expect("deserialize environment"),
282 environment
283 );
284 }
285 }
286}
287
288#[doc = include_str!("../README.md")]
290#[cfg(doctest)]
291struct ReadmeDoctests;