#![deny(missing_docs)]
pub mod export;
pub mod limits;
#[cfg(feature = "media-grants")]
pub mod media_grant;
#[cfg(feature = "pagination")]
pub mod pagination;
#[cfg(feature = "webhook-signature")]
pub mod webhook_signature;
use std::{fmt, str::FromStr};
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum DeploymentEnvironment {
#[default]
Local,
Testing,
Staging,
Production,
}
impl DeploymentEnvironment {
const fn as_str(self) -> &'static str {
match self {
Self::Local => "local",
Self::Testing => "testing",
Self::Staging => "staging",
Self::Production => "production",
}
}
}
impl fmt::Display for DeploymentEnvironment {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl FromStr for DeploymentEnvironment {
type Err = ParseEnvironmentError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"local" => Ok(Self::Local),
"testing" => Ok(Self::Testing),
"staging" => Ok(Self::Staging),
"production" => Ok(Self::Production),
_ => Err(ParseEnvironmentError(value.to_owned())),
}
}
}
#[derive(Clone, Debug, Error, Eq, PartialEq)]
#[error("unsupported environment `{0}`; expected local, testing, staging, or production")]
pub struct ParseEnvironmentError(String);
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum LogFormat {
#[default]
Auto,
Json,
Pretty,
}
impl LogFormat {
#[must_use]
pub const fn resolve(self, environment: DeploymentEnvironment) -> Self {
match (self, environment) {
(Self::Auto, DeploymentEnvironment::Local) => Self::Pretty,
(
Self::Auto,
DeploymentEnvironment::Testing
| DeploymentEnvironment::Staging
| DeploymentEnvironment::Production,
) => Self::Json,
(explicit, _) => explicit,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ProcessKind {
Api,
Worker,
Migrate,
Seed,
}
impl ProcessKind {
const fn as_str(self) -> &'static str {
match self {
Self::Api => "api",
Self::Worker => "worker",
Self::Migrate => "migrate",
Self::Seed => "seed",
}
}
}
impl fmt::Display for ProcessKind {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ServiceIdentity {
product: String,
process: ProcessKind,
version: String,
commit: String,
environment: DeploymentEnvironment,
}
impl ServiceIdentity {
pub fn new(
product: impl Into<String>,
process: ProcessKind,
version: impl Into<String>,
commit: impl Into<String>,
environment: DeploymentEnvironment,
) -> Self {
Self {
product: product.into(),
process,
version: version.into(),
commit: commit.into(),
environment,
}
}
pub fn product(&self) -> &str {
&self.product
}
pub const fn process(&self) -> ProcessKind {
self.process
}
pub fn version(&self) -> &str {
&self.version
}
pub fn commit(&self) -> &str {
&self.commit
}
pub const fn environment(&self) -> DeploymentEnvironment {
self.environment
}
pub fn service_name(&self) -> String {
format!("{}-{}", self.product, self.process)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct BuildInfo {
version: String,
commit: String,
rust_version: String,
}
impl BuildInfo {
pub fn new(
version: impl Into<String>,
commit: impl Into<String>,
rust_version: impl Into<String>,
) -> Self {
Self {
version: version.into(),
commit: commit.into(),
rust_version: rust_version.into(),
}
}
pub fn version(&self) -> &str {
&self.version
}
pub fn commit(&self) -> &str {
&self.commit
}
pub fn rust_version(&self) -> &str {
&self.rust_version
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn shared_vocabulary_follows_the_telemetry_contract() {
let identity = ServiceIdentity::new(
"orders",
ProcessKind::Api,
"1.2.3",
"abc123",
DeploymentEnvironment::Production,
);
assert_eq!(identity.service_name(), "orders-api");
assert_eq!(identity.environment().to_string(), "production");
assert_eq!(
LogFormat::Auto.resolve(DeploymentEnvironment::Production),
LogFormat::Json
);
}
#[test]
fn deployment_environments_parse_display_and_serialize() {
for (text, environment) in [
("local", DeploymentEnvironment::Local),
("testing", DeploymentEnvironment::Testing),
("staging", DeploymentEnvironment::Staging),
("production", DeploymentEnvironment::Production),
] {
assert_eq!(text.parse::<DeploymentEnvironment>(), Ok(environment));
assert_eq!(environment.to_string(), text);
assert_eq!(
serde_json::to_string(&environment).expect("serialize environment"),
format!("\"{text}\"")
);
assert_eq!(
serde_json::from_str::<DeploymentEnvironment>(&format!("\"{text}\""))
.expect("deserialize environment"),
environment
);
}
}
}
#[doc = include_str!("../README.md")]
#[cfg(doctest)]
struct ReadmeDoctests;