use std::fmt;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
#[derive(
Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize, schemars::JsonSchema,
)]
#[serde(rename_all = "snake_case")]
pub enum FormatKind {
#[default]
Speckit,
Openspec,
Kiro,
}
impl FormatKind {
pub const ALL: [Self; 3] = [Self::Speckit, Self::Openspec, Self::Kiro];
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::Speckit => "speckit",
Self::Openspec => "openspec",
Self::Kiro => "kiro",
}
}
#[must_use]
pub const fn title(self) -> &'static str {
match self {
Self::Speckit => "Spec Kit",
Self::Openspec => "OpenSpec",
Self::Kiro => "Kiro",
}
}
}
impl FormatKind {
#[must_use]
pub fn is_reference(&self) -> bool {
*self == Self::Speckit
}
}
impl fmt::Display for FormatKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
impl FromStr for FormatKind {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::ALL
.into_iter()
.find(|k| k.name() == s)
.ok_or_else(|| format!("`{s}` is not a format (speckit, openspec or kiro)"))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
pub struct SourceRange {
pub path: String,
pub start: usize,
pub end: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
pub struct Scenario {
pub key: String,
pub raw: String,
pub source: SourceRange,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
pub struct ScenarioRef {
pub id: String,
pub text: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
pub struct Requirement {
pub key: String,
pub title: String,
pub text: String,
pub scenarios: Vec<Scenario>,
pub source: SourceRange,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct OpenSpecMeta {
pub schema: String,
pub context: Option<String>,
pub unit_kind: String,
pub requirements: Vec<DeltaRequirement>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct DeltaRequirement {
pub capability: String,
pub operation: String,
pub name: String,
pub scenarios: Vec<String>,
pub path: String,
pub line: u32,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn formats_parse_and_serialize_by_name() {
for k in FormatKind::ALL {
assert_eq!(k.name().parse::<FormatKind>(), Ok(k));
assert_eq!(serde_json::to_value(k).unwrap_or_default(), k.name());
}
let err = "bogus".parse::<FormatKind>().unwrap_err();
assert_eq!(err, "`bogus` is not a format (speckit, openspec or kiro)");
}
}