1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
pub mod error;
pub mod schema;
pub mod spec;

mod archive;
mod compiler;

use self::error::{Error, Result};
use self::schema::Schema;
use self::spec::Spec;
use compiler::Compiler;
pub use compiler::Release;
use kct_helper::io;
use serde_json::Value;
use std::convert::TryFrom;
use std::path::{Path, PathBuf};
use tempfile::TempDir;

const SCHEMA_FILE: &str = "schema.json";
const SPEC_FILE: &str = "kcp.json";
const EXAMPLE_FILE: &str = "example.json";
const MAIN_FILE: &str = "templates/main.jsonnet";

#[derive(Debug)]
pub struct Package {
	pub root: PathBuf,
	pub main: PathBuf,
	pub spec: Spec,
	pub schema: Option<Schema>,
	pub example: Option<Value>,
	pub brownfield: Option<TempDir>,
}

impl TryFrom<PathBuf> for Package {
	type Error = Error;

	fn try_from(root: PathBuf) -> Result<Self> {
		let (root, brownfield) = match root.extension() {
			None => (root, None),
			Some(_) => {
				let brownfield = TempDir::new()
					.expect("Unable to create temporary directory to unpack your KCP");
				let unarchived = PathBuf::from(brownfield.path());

				archive::unarchive(&root, &unarchived).map_err(|_err| Error::InvalidFormat)?;

				(unarchived, Some(brownfield))
			}
		};

		let spec = {
			let mut path = root.clone();
			path.push(SPEC_FILE);

			if path.exists() {
				Spec::try_from(path)?
			} else {
				return Err(Error::NoSpec);
			}
		};

		let schema = {
			let mut path = root.clone();
			path.push(SCHEMA_FILE);

			if path.exists() {
				Some(Schema::try_from(path)?)
			} else {
				None
			}
		};

		let example = {
			let mut path = root.clone();
			path.push(EXAMPLE_FILE);

			if path.exists() {
				let contents = io::from_file(&path).map_err(|_err| Error::InvalidExample)?;
				Some(serde_json::from_str(&contents).map_err(|_err| Error::InvalidExample)?)
			} else {
				None
			}
		};

		let main = {
			let mut path = root.clone();
			path.push(MAIN_FILE);

			if path.exists() {
				path
			} else {
				return Err(Error::NoMain);
			}
		};

		validate_input(&schema, &example).map_err(|err| match err {
			Error::InvalidInput => Error::InvalidExample,
			Error::NoInput => Error::NoExample,
			err => err,
		})?;

		Ok(Package {
			root,
			main,
			spec,
			schema,
			example,
			brownfield,
		})
	}
}

/// Methods
impl Package {
	pub fn archive(self, dest: &Path) -> std::result::Result<PathBuf, String> {
		let name = format!("{}_{}", self.spec.name, self.spec.version);
		archive::archive(&name, &self.root, dest)
	}

	pub(crate) fn validate_input(&self, input: &Option<Value>) -> Result<()> {
		validate_input(&self.schema, input)
	}

	pub fn compile(self, input: Option<Value>, release: Option<Release>) -> Result<Value> {
		validate_input(&self.schema, &input)?;

		Compiler::new(&self.root).compile(self, input.unwrap_or(Value::Null), release)
	}
}

fn validate_input(schema: &Option<Schema>, input: &Option<Value>) -> Result<()> {
	let (schema, input) = match (&schema, &input) {
		(None, None) => return Ok(()),
		(None, Some(_)) => return Err(Error::NoSchema),
		(Some(_), None) => return Err(Error::NoInput),
		(Some(schema), Some(input)) => (schema, input),
	};

	if input.is_object() && schema.validate(input) {
		Ok(())
	} else {
		Err(Error::InvalidInput)
	}
}