Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/common/src/api/papi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::fs::File;
use self::client::{ApiError, ApiErrorKind, ApiResult, HandleResponse};
use crate::function::{
CreateFunctionResponse, Function, FunctionDeployment, FunctionDeploymentCredentials,
FunctionDeploymentStatus, GetFunctionResponse,
GetFunctionResponse,
};
use crate::relay::{CreateRelay, Relay};
use serde_json::json;
Expand Down
125 changes: 125 additions & 0 deletions crates/ev-cli/src/commands/function/create_toml.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
use std::{fs, path::PathBuf, str::FromStr};

use clap::Parser;
use serde::Serialize;
use thiserror::Error;

use crate::{
commands::interact::{input, preset_input, select, validated_input, validators},
CmdOutput,
};

#[derive(Serialize)]
struct FunctionConfig {
name: String,
language: String,
handler: String,
}

#[derive(Serialize)]
struct FunctionToml {
function: FunctionConfig,
}

/// Generate a toml configuration file for your Function
#[derive(Parser, Debug)]
pub struct CreateTomlArgs {}

#[derive(strum_macros::Display, Debug)]
pub enum CreateTomlPrompt {
#[strum(to_string = "Give your Function a name:")]
Name,
#[strum(to_string = "Select your Function's language:")]
Language,
#[strum(to_string = "What is the entry point to your function?:")]
Handler,
}

#[derive(strum_macros::Display, Debug)]
pub enum CreateTomlMessage {
#[strum(to_string = "Function configuration saved to function.toml.")]
Success,
}

impl CmdOutput for CreateTomlMessage {
fn exitcode(&self) -> crate::errors::ExitCode {
crate::errors::OK
}

fn code(&self) -> String {
match self {
CreateTomlMessage::Success => "function-create-toml-success",
}
.to_string()
}
}

#[derive(Error, Debug)]
pub enum CreateTomlError {
#[error("An IO error occurred: {0}")]
Io(#[from] std::io::Error),
#[error("A function.toml file already exists in the current directory")]
AlreadyExists,
}

impl CmdOutput for CreateTomlError {
fn exitcode(&self) -> crate::errors::ExitCode {
match self {
CreateTomlError::Io(_) => crate::errors::IOERR,
CreateTomlError::AlreadyExists => crate::errors::SOFTWARE,
}
}

fn code(&self) -> String {
match self {
CreateTomlError::Io(_) => "function-create-toml-io-error",
CreateTomlError::AlreadyExists => "function-create-toml-already-exists",
}
.to_string()
}
}

pub async fn run(_: CreateTomlArgs) -> Result<CreateTomlMessage, CreateTomlError> {
if PathBuf::from_str("./function.toml")
.expect("infallible")
.exists()
{
return Err(CreateTomlError::AlreadyExists);
}

let valid_languages: [&str; 5] = [
"node@18",
"node@20",
"python@3.9",
"python@3.10",
"python@3.11",
];

let name = validated_input(
CreateTomlPrompt::Name,
false,
Box::new(validators::validate_function_name),
)?;

let langs = valid_languages
.iter()
.map(|lang| lang.to_string())
.collect::<Vec<String>>();

let language = select(&langs, 0, CreateTomlPrompt::Language).unwrap();

let handler = preset_input(CreateTomlPrompt::Handler, "index.handler".to_string()).unwrap();

let config = FunctionToml {
function: FunctionConfig {
name,
language: valid_languages[language].to_string(),
handler: handler.to_string(),
},
};

let toml = toml::to_string(&config).unwrap();
fs::write("function.toml", toml)?;

return Ok(CreateTomlMessage::Success);
}
9 changes: 3 additions & 6 deletions crates/ev-cli/src/commands/function/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ impl CmdOutput for InitMessage {
pub enum InitPrompt {
#[strum(to_string = "Give your function a name:")]
Name,
#[strum(to_string = "Select your function's language:")]
Language,
}

pub async fn run(args: InitArgs, auth: BasicAuth) -> Result<InitMessage, InitError> {
Expand All @@ -90,12 +92,7 @@ pub async fn run(args: InitArgs, auth: BasicAuth) -> Result<InitMessage, InitErr
.iter()
.map(|lang| lang.to_string())
.collect::<Vec<String>>();
let language = interact::select(
&langs,
0,
Some("Select your Function's language:".to_string()),
)
.unwrap();
let language = interact::select(&langs, 0, InitPrompt::Language).unwrap();
let lang = valid_languages[language].to_string();

let file = api_client.get_hello_function_template(lang.clone()).await?;
Expand Down
5 changes: 5 additions & 0 deletions crates/ev-cli/src/commands/function/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::run_cmd;
use clap::Parser;

mod create_toml;
mod deploy;
mod init;

Expand All @@ -16,6 +17,7 @@ pub struct FunctionArgs {
pub enum FunctionCommand {
Init(init::InitArgs),
Deploy(deploy::DeployArgs),
CreateToml(create_toml::CreateTomlArgs),
}

pub async fn run(args: FunctionArgs) {
Expand All @@ -24,5 +26,8 @@ pub async fn run(args: FunctionArgs) {
match args.action {
FunctionCommand::Init(init_args) => run_cmd(init::run(init_args, auth).await),
FunctionCommand::Deploy(deploy_args) => run_cmd(deploy::run(deploy_args, auth).await),
FunctionCommand::CreateToml(create_toml_args) => {
run_cmd(create_toml::run(create_toml_args).await)
}
}
}
33 changes: 25 additions & 8 deletions crates/ev-cli/src/commands/interact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use crate::theme::CliTheme;
use dialoguer::{Input, Select};
use indicatif::{ProgressBar, ProgressStyle};

use self::validators::ValidationError;

pub mod validators {
use lazy_static;
use regex::Regex;
Expand Down Expand Up @@ -61,7 +63,7 @@ pub mod validators {
}
}

pub fn validate_function_name(name: &str) -> Result<(), ValidationError> {
pub fn validate_function_name(name: &String) -> Result<(), ValidationError> {
lazy_static::lazy_static!(
static ref NAME_REGEX: Regex = Regex::new(r"^[A-Za-z0-9]([-_]?[A-Za-z0-9])*$").unwrap();
);
Expand All @@ -73,7 +75,7 @@ pub mod validators {
Ok(())
}

pub fn validate_function_language(language: &str) -> Result<(), ValidationError> {
pub fn validate_function_language(language: &String) -> Result<(), ValidationError> {
lazy_static::lazy_static!(
static ref LANGUAGE_REGEX: Regex = Regex::new(r"\b(?:node|python)@\d+(\.\d+)?\b").unwrap();
);
Expand Down Expand Up @@ -110,7 +112,7 @@ pub fn validated_input<T>(
prompt: T,
allow_empty: bool,
validator: Box<validators::GenericValidator>,
) -> Option<String>
) -> Result<String, std::io::Error>
where
T: std::fmt::Display,
{
Expand All @@ -122,18 +124,33 @@ where
.allow_empty(allow_empty)
.validate_with(validator)
.interact()
.ok()
}

pub fn select(options: &Vec<String>, default: usize, prompt: Option<String>) -> Option<usize> {
pub fn select<T>(options: &Vec<String>, default: usize, prompt: T) -> Option<usize>
where
T: std::fmt::Display,
{
let theme = CliTheme::default();
let mut select_obj = Select::with_theme(&theme);
if let Some(prompt) = prompt {
select_obj.with_prompt(prompt);
}
select_obj.with_prompt(prompt.to_string());
select_obj.items(options).default(default).interact().ok()
}

pub fn preset_input<S, T>(prompt: S, preset: T) -> Option<String>
where
S: std::fmt::Display,
T: std::fmt::Display,
{
let theme = CliTheme::default();
let mut input: Input<String> = Input::with_theme(&theme);

input
.with_prompt(prompt.to_string())
.default(preset.to_string())
.interact()
.ok()
}

/// To make quiet mode integration more simple
/// OptionalProgressBar can be used - all functions called
/// as normal, but will result in No-Ops during quiet mode
Expand Down
19 changes: 9 additions & 10 deletions crates/ev-cli/src/commands/relay/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,33 +29,33 @@ pub enum CreateError {
"A Relay configuration file already exists at the path: {0}, use the --force parameter to overwrite the existing file"
)]
FileAlreadyExists(String),
#[error("An error occured while writing the Relay configuration file: {0}]")]
WriteError(#[from] std::io::Error),
#[error("An IO error occurred: {0}")]
Io(#[from] std::io::Error),
#[error(
"A domain must be chosen to create a Relay. Use the --domain flag to provide one ahead of time."
)]
NoDomain,
#[error("An error occurred while creating the relay: {0}")]
ApiError(#[from] ApiError),
Api(#[from] ApiError),
#[error("An error occured while parsing the relay configuration: {0}")]
ParseError(#[from] serde_json::Error),
Parse(#[from] serde_json::Error),
}

impl CmdOutput for CreateError {
fn code(&self) -> String {
match self {
CreateError::FileAlreadyExists(_) => "relay-file-already-exists",
CreateError::WriteError(_) => "relay-write-error",
CreateError::Io(_) => "relay-write-error",
CreateError::NoDomain => "relay-no-domain",
CreateError::ApiError(_) => "relay-api-error",
CreateError::ParseError(_) => "relay-parse-error",
CreateError::Api(_) => "relay-api-error",
CreateError::Parse(_) => "relay-parse-error",
}
.to_string()
}

fn exitcode(&self) -> crate::errors::ExitCode {
match self {
CreateError::WriteError(_) => crate::errors::CANTCREAT,
CreateError::Io(_) => crate::errors::IOERR,
_ => crate::errors::GENERAL,
}
}
Expand Down Expand Up @@ -100,8 +100,7 @@ pub async fn run(args: CreateArgs, auth: BasicAuth) -> Result<CreateMessage, Cre
CreatePrompt::WhichDomain,
false,
Box::new(interact::validators::validate_destination_domain),
)
.ok_or(CreateError::NoDomain)?;
)?;

let relay_req_body = Relay {
id: None,
Expand Down
55 changes: 55 additions & 0 deletions crates/ev-cli/src/function/mod.rs
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not used yet

Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use serde::{Deserialize, Serialize};
use std::{
fs::File,
io::{self, BufReader, Read},
};
use thiserror::Error;

#[derive(Debug, Error)]
pub enum FunctionTomlError {
#[error(
"Relay configuration could not be found at {0}, specify a relay config file \
with the --file flag. Or create a relay with ev relay create."
)]
ConfigNotFound(String),
#[error("Error reading relay config file: {0}")]
IoError(#[from] io::Error),
#[error("Error parsing relay config file: {0}")]
ParseError(#[from] toml::de::Error),
}

#[derive(Debug, Deserialize, Serialize)]
struct FunctionToml {
function: FunctionProps,
}

#[derive(Debug, Deserialize, Serialize)]
struct FunctionProps {
name: String,
language: String,
#[serde(default = "default_handler")]
handler: String,
}

fn default_handler() -> String {
"index.handler".to_string()
}

impl TryFrom<&std::path::PathBuf> for FunctionToml {
type Error = FunctionTomlError;

fn try_from(path: &std::path::PathBuf) -> Result<Self, Self::Error> {
if !path.try_exists()? {
return Err(FunctionTomlError::ConfigNotFound(
path.to_string_lossy().into(),
));
}

let file = File::open(&path)?;
let mut buf_reader = BufReader::new(file);
let mut contents = String::new();
buf_reader.read_to_string(&mut contents)?;

Ok(toml::from_str(&contents)?)
}
}
1 change: 1 addition & 0 deletions crates/ev-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod auth;
mod commands;
mod errors;
mod fs;
mod function;
mod relay;
mod theme;
mod tty;
Expand Down