use crate::resources::{NewStep, NewVariable, Step, Variable, VariableValue};
use crate::schema::pipelines;
use crate::Database;
use diesel::prelude::*;
use serde::{Deserialize, Serialize};
use std::convert::{TryFrom, TryInto};
use std::error;
#[derive(Clone, Debug, Deserialize, Serialize, Identifiable, Queryable)]
#[table_name = "pipelines"]
pub(crate) struct Pipeline {
pub(crate) id: i32,
pub(crate) name: String,
pub(crate) description: Option<String>,
}
impl Pipeline {
pub(crate) fn steps(&self, conn: &Database) -> QueryResult<Vec<Step>> {
use crate::schema::steps::dsl::*;
Step::belonging_to(self)
.order((position.asc(), id.asc()))
.load(&**conn)
}
pub(crate) fn variables(&self, conn: &Database) -> QueryResult<Vec<Variable>> {
use crate::schema::variables::dsl::*;
Variable::belonging_to(self).order(id.desc()).load(&**conn)
}
pub(crate) fn get_missing_variable(
&self,
conn: &Database,
variable_values: &[VariableValue],
) -> QueryResult<Option<Variable>> {
let result = self.variables(conn)?.into_iter().find_map(|v| {
if variable_values.iter().any(|vv| vv.key == v.key) {
return None;
}
Some(v)
});
Ok(result)
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub(crate) struct NewPipeline<'a> {
name: &'a str,
description: Option<&'a str>,
variables: Vec<NewVariable<'a>>,
steps: Vec<NewStep<'a>>,
}
impl<'a> NewPipeline<'a> {
pub(crate) fn new(name: &'a str, description: Option<&'a str>) -> Self {
Self {
name,
description,
variables: vec![],
steps: vec![],
}
}
pub(crate) fn with_variables(&mut self, mut variables: Vec<NewVariable<'a>>) {
self.variables.append(&mut variables)
}
pub(crate) fn with_steps(&mut self, mut steps: Vec<NewStep<'a>>) {
self.steps.append(&mut steps)
}
pub(crate) fn create(self, conn: &Database) -> Result<Pipeline, Box<dyn error::Error>> {
conn.transaction(|| {
use crate::schema::pipelines::dsl::*;
let values = (name.eq(&self.name), description.eq(&self.description));
let pipeline = diesel::insert_into(pipelines)
.values(values)
.get_result(&**conn)?;
self.variables
.into_iter()
.try_for_each(|variable| variable.add_to_pipeline(conn, &pipeline))?;
self.steps
.into_iter()
.try_for_each(|step| step.add_to_pipeline(conn, &pipeline))?;
Ok(pipeline)
})
}
}
pub(crate) mod graphql {
use super::*;
use crate::resources::{CreateStepInput, CreateVariableInput, Step, Variable};
use juniper::{object, FieldResult, GraphQLInputObject, ID};
#[derive(Clone, Debug, Deserialize, Serialize, GraphQLInputObject)]
pub(crate) struct CreatePipelineInput {
pub(crate) name: String,
pub(crate) description: Option<String>,
pub(crate) variables: Vec<CreateVariableInput>,
pub(crate) steps: Vec<CreateStepInput>,
}
#[derive(Clone, Debug, Deserialize, Serialize, GraphQLInputObject)]
pub(crate) struct SearchPipelineInput {
pub(crate) name: Option<String>,
pub(crate) description: Option<String>,
}
#[object(Context = Database)]
impl Pipeline {
fn id() -> ID {
ID::new(self.id.to_string())
}
fn name() -> &str {
self.name.as_ref()
}
fn description() -> Option<&str> {
self.description.as_ref().map(String::as_ref)
}
fn variables(context: &Database) -> FieldResult<Option<Vec<Variable>>> {
self.variables(context).map(Some).map_err(Into::into)
}
fn steps(context: &Database) -> FieldResult<Option<Vec<Step>>> {
self.steps(context).map(Some).map_err(Into::into)
}
}
}
impl<'a> TryFrom<&'a graphql::CreatePipelineInput> for NewPipeline<'a> {
type Error = String;
fn try_from(input: &'a graphql::CreatePipelineInput) -> Result<Self, Self::Error> {
let mut pipeline = Self::new(&input.name, input.description.as_ref().map(String::as_ref));
let variables = input.variables.iter().map(Into::into).collect();
let steps = input
.steps
.iter()
.enumerate()
.map(TryInto::try_into)
.collect::<Result<Vec<_>, Self::Error>>()?;
pipeline.with_variables(variables);
pipeline.with_steps(steps);
Ok(pipeline)
}
}