use std::borrow::Cow;
use schemars::{JsonSchema, Schema, SchemaGenerator, json_schema};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::names::{EnvironmentName, Version};
use crate::template::Template;
use crate::{Error, Result};
pub const SUPPORTED_VERSION: u64 = 0;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct ManifestVersion;
impl Serialize for ManifestVersion {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_u64(SUPPORTED_VERSION)
}
}
impl<'de> Deserialize<'de> for ManifestVersion {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<ManifestVersion, D::Error> {
let found = serde_json::Value::deserialize(d)?;
if found == SUPPORTED_VERSION {
Ok(ManifestVersion)
} else {
Err(serde::de::Error::custom(Error::Version { found: found.to_string() }))
}
}
}
impl JsonSchema for ManifestVersion {
fn schema_name() -> Cow<'static, str> {
"ManifestVersion".into()
}
fn json_schema(_: &mut SchemaGenerator) -> Schema {
json_schema!({ "description": "The contract's version. 0: unstable, read only by a runner built for exactly 0.", "const": SUPPORTED_VERSION })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "lowercase")]
pub enum GeneratorName {
Isoloom,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct Generator {
pub name: GeneratorName,
pub version: Version,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Instance(u8);
impl Instance {
pub fn new(n: u8) -> Result<Instance> {
if (1..=99).contains(&n) {
Ok(Instance(n))
} else {
Err(Error::OutOfRange {
what: "an instance",
value: n.into(),
min: 1,
max: 99,
})
}
}
#[must_use]
pub fn get(self) -> u8 {
self.0
}
}
impl TryFrom<u8> for Instance {
type Error = Error;
fn try_from(n: u8) -> Result<Instance> {
Instance::new(n)
}
}
impl From<Instance> for u8 {
fn from(i: Instance) -> u8 {
i.0
}
}
impl Serialize for Instance {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_u8(self.0)
}
}
impl<'de> Deserialize<'de> for Instance {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Instance, D::Error> {
Instance::new(u8::deserialize(d)?).map_err(serde::de::Error::custom)
}
}
impl JsonSchema for Instance {
fn schema_name() -> Cow<'static, str> {
"Instance".into()
}
fn json_schema(_: &mut SchemaGenerator) -> Schema {
json_schema!({ "type": "integer", "minimum": 1, "maximum": 99 })
}
}
fn nullable_instance(generator: &mut SchemaGenerator) -> Schema {
json_schema!({ "anyOf": [generator.subschema_for::<Instance>(), { "type": "null" }] })
}
fn present<'de, D: Deserializer<'de>, T: Deserialize<'de>>(d: D) -> Result<Option<T>, D::Error> {
Option::deserialize(d)
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct EnvironmentRef {
pub name: EnvironmentName,
#[serde(deserialize_with = "present")]
#[schemars(required, schema_with = "nullable_instance")]
pub instance: Option<Instance>,
#[serde(default)]
pub message: Option<Template>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn only_version_0_is_read() {
assert_eq!(serde_json::from_str::<ManifestVersion>("0").unwrap(), ManifestVersion);
let other = serde_json::from_str::<ManifestVersion>("1").unwrap_err().to_string();
assert!(other.starts_with("manifest_version 1: this reader understands version 0 only"), "{other}");
assert_eq!(serde_json::to_string(&ManifestVersion).unwrap(), "0");
}
#[test]
fn an_instance_is_1_to_99() {
assert_eq!(Instance::new(7).map(Instance::get), Ok(7));
assert_eq!(Instance::try_from(99).map(u8::from), Ok(99));
for out in [0, 100] {
assert_eq!(Instance::new(out).unwrap_err().code(), "out_of_range", "{out}");
}
assert!(serde_json::from_str::<Instance>("0").is_err());
assert_eq!(serde_json::to_string(&Instance::new(3).unwrap()).unwrap(), "3");
}
#[test]
fn an_environment_names_its_instance_even_when_it_has_none() {
let env: EnvironmentRef = serde_json::from_str(r#"{ "name": "shop-2", "instance": 2 }"#).unwrap();
assert_eq!(env.instance.map(Instance::get), Some(2));
assert!(
serde_json::from_str::<EnvironmentRef>(r#"{ "name": "shop" }"#)
.unwrap_err()
.to_string()
.contains("instance")
);
}
}