1use jsonschema::JSONSchema;
2use serde_json::Value;
3use std::fs;
4use std::path::Path;
5
6const GITHUB_WORKFLOW_SCHEMA: &str = include_str!("github-workflow.json");
7const GITLAB_CI_SCHEMA: &str = include_str!("gitlab-ci.json");
8
9#[derive(Debug, Clone, Copy)]
10pub enum SchemaType {
11 GitHub,
12 GitLab,
13}
14
15pub struct SchemaValidator {
16 github_schema: JSONSchema,
17 gitlab_schema: JSONSchema,
18}
19
20impl SchemaValidator {
21 pub fn new() -> Result<Self, String> {
22 let github_schema_json: Value = serde_json::from_str(GITHUB_WORKFLOW_SCHEMA)
23 .map_err(|e| format!("Failed to parse GitHub workflow schema: {}", e))?;
24
25 let gitlab_schema_json: Value = serde_json::from_str(GITLAB_CI_SCHEMA)
26 .map_err(|e| format!("Failed to parse GitLab CI schema: {}", e))?;
27
28 let github_schema = JSONSchema::compile(&github_schema_json)
29 .map_err(|e| format!("Failed to compile GitHub JSON schema: {}", e))?;
30
31 let gitlab_schema = JSONSchema::compile(&gitlab_schema_json)
32 .map_err(|e| format!("Failed to compile GitLab JSON schema: {}", e))?;
33
34 Ok(Self {
35 github_schema,
36 gitlab_schema,
37 })
38 }
39
40 pub fn validate_workflow(&self, workflow_path: &Path) -> Result<(), String> {
41 let schema_type = if workflow_path.file_name().is_some_and(|name| {
43 let name_str = name.to_string_lossy();
44 name_str.ends_with(".gitlab-ci.yml") || name_str.ends_with(".gitlab-ci.yaml")
45 }) {
46 SchemaType::GitLab
47 } else {
48 SchemaType::GitHub
49 };
50
51 let content = fs::read_to_string(workflow_path)
53 .map_err(|e| format!("Failed to read workflow file: {}", e))?;
54
55 let workflow_json: Value = serde_yaml::from_str(&content)
57 .map_err(|e| format!("Failed to parse workflow YAML: {}", e))?;
58
59 let validation_result = match schema_type {
61 SchemaType::GitHub => self.github_schema.validate(&workflow_json),
62 SchemaType::GitLab => self.gitlab_schema.validate(&workflow_json),
63 };
64
65 if let Err(errors) = validation_result {
67 let schema_name = match schema_type {
68 SchemaType::GitHub => "GitHub workflow",
69 SchemaType::GitLab => "GitLab CI",
70 };
71 let mut error_msg = format!("{} validation failed:\n", schema_name);
72 for error in errors {
73 error_msg.push_str(&format!("- {}\n", error));
74 }
75 return Err(error_msg);
76 }
77
78 Ok(())
79 }
80
81 pub fn validate_with_specific_schema(
82 &self,
83 content: &str,
84 schema_type: SchemaType,
85 ) -> Result<(), String> {
86 let workflow_json: Value =
88 serde_yaml::from_str(content).map_err(|e| format!("Failed to parse YAML: {}", e))?;
89
90 let validation_result = match schema_type {
92 SchemaType::GitHub => self.github_schema.validate(&workflow_json),
93 SchemaType::GitLab => self.gitlab_schema.validate(&workflow_json),
94 };
95
96 if let Err(errors) = validation_result {
98 let schema_name = match schema_type {
99 SchemaType::GitHub => "GitHub workflow",
100 SchemaType::GitLab => "GitLab CI",
101 };
102 let mut error_msg = format!("{} validation failed:\n", schema_name);
103 for error in errors {
104 error_msg.push_str(&format!("- {}\n", error));
105 }
106 return Err(error_msg);
107 }
108
109 Ok(())
110 }
111}