From dd0b74c43bb2ff56f61ae404c0066076e0f134c9 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Tue, 18 Oct 2022 17:47:26 +0300 Subject: [PATCH] versioned_output.rs: add scaffolding for versioned JSON JSON and other structured output needs to be versioned so that consumers can rely on it long term. Add a module for specifying output format and version, as well as traits for implementing things. This doesn't do anything on its own, but future changes will build on it. Sponsored-by: NLnet Foundation; NGI Assure --- tools/src/bin/opgpcard/versioned_output.rs | 81 ++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 tools/src/bin/opgpcard/versioned_output.rs diff --git a/tools/src/bin/opgpcard/versioned_output.rs b/tools/src/bin/opgpcard/versioned_output.rs new file mode 100644 index 0000000..c8078b6 --- /dev/null +++ b/tools/src/bin/opgpcard/versioned_output.rs @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: 2022 Lars Wirzenius +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use clap::ValueEnum; +use semver::Version; +use serde::{Serialize, Serializer}; +use std::str::FromStr; + +#[derive(Debug, Copy, Clone, Eq, PartialEq, ValueEnum)] +pub enum OutputFormat { + Json, + Text, + Yaml, +} + +#[derive(Debug, Clone)] +pub struct OutputVersion { + version: Version, +} + +impl OutputVersion { + pub const fn new(major: u64, minor: u64, patch: u64) -> Self { + Self { + version: Version::new(major, minor, patch), + } + } + + /// Does this version fulfill the needs of the version that is requested? + pub fn is_acceptable_for(&self, wanted: &Self) -> bool { + self.version.major == wanted.version.major + && (self.version.minor > wanted.version.minor + || (self.version.minor == wanted.version.minor + && self.version.patch >= wanted.version.patch)) + } +} + +impl std::fmt::Display for OutputVersion { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.version) + } +} + +impl FromStr for OutputVersion { + type Err = semver::Error; + + fn from_str(s: &str) -> Result { + let v = Version::parse(s)?; + Ok(Self::new(v.major, v.minor, v.patch)) + } +} + +impl PartialEq for &OutputVersion { + fn eq(&self, other: &Self) -> bool { + self.version == other.version + } +} + +impl Serialize for OutputVersion { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +pub trait OutputBuilder { + type Err; + + fn print(&self, format: OutputFormat, version: OutputVersion) -> Result; +} + +pub trait OutputVariant: Serialize { + const VERSION: OutputVersion; + fn json(&self) -> Result { + serde_json::to_string_pretty(self) + } + fn yaml(&self) -> Result { + serde_yaml::to_string(self) + } +}