use crate::error::SsgError;
use std::path::Path;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Target {
Netlify,
Vercel,
CloudflarePages,
GithubPages,
S3,
None,
}
impl Target {
pub fn from_cli(name: &str) -> Result<Self, SsgError> {
match name {
"netlify" => Ok(Self::Netlify),
"vercel" => Ok(Self::Vercel),
"cloudflare-pages" => Ok(Self::CloudflarePages),
"github-pages" => Ok(Self::GithubPages),
"s3" => Ok(Self::S3),
"none" => Ok(Self::None),
other => Err(SsgError::Validation {
field: "deploy.target".to_string(),
message: format!("unknown deploy target: {other}"),
}),
}
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Netlify => "netlify",
Self::Vercel => "vercel",
Self::CloudflarePages => "cloudflare-pages",
Self::GithubPages => "github-pages",
Self::S3 => "s3",
Self::None => "none",
}
}
}
pub trait DeployAdapter: Send + Sync {
fn name(&self) -> &'static str;
fn deploy(&self, site_dir: &Path) -> Result<(), SsgError>;
}
#[must_use]
pub fn adapter_for(target: Target) -> Box<dyn DeployAdapter> {
match target {
Target::Netlify => Box::new(NetlifyAdapter),
Target::Vercel => Box::new(VercelAdapter),
Target::CloudflarePages => Box::new(CloudflarePagesAdapter),
Target::GithubPages => Box::new(GithubPagesAdapter),
Target::S3 => Box::new(S3Adapter),
Target::None => Box::new(NoneAdapter),
}
}
fn stub_message(target: &str) {
eprintln!("deploy adapter for {target}: not yet implemented; see #527");
}
#[derive(Debug, Clone, Copy)]
pub struct NetlifyAdapter;
impl DeployAdapter for NetlifyAdapter {
fn name(&self) -> &'static str {
"netlify"
}
fn deploy(&self, _site_dir: &Path) -> Result<(), SsgError> {
stub_message(self.name());
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
pub struct VercelAdapter;
impl DeployAdapter for VercelAdapter {
fn name(&self) -> &'static str {
"vercel"
}
fn deploy(&self, _site_dir: &Path) -> Result<(), SsgError> {
stub_message(self.name());
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
pub struct CloudflarePagesAdapter;
impl DeployAdapter for CloudflarePagesAdapter {
fn name(&self) -> &'static str {
"cloudflare-pages"
}
fn deploy(&self, _site_dir: &Path) -> Result<(), SsgError> {
stub_message(self.name());
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
pub struct GithubPagesAdapter;
impl DeployAdapter for GithubPagesAdapter {
fn name(&self) -> &'static str {
"github-pages"
}
fn deploy(&self, _site_dir: &Path) -> Result<(), SsgError> {
stub_message(self.name());
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
pub struct S3Adapter;
impl DeployAdapter for S3Adapter {
fn name(&self) -> &'static str {
"s3"
}
fn deploy(&self, _site_dir: &Path) -> Result<(), SsgError> {
stub_message(self.name());
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
pub struct NoneAdapter;
impl DeployAdapter for NoneAdapter {
fn name(&self) -> &'static str {
"none"
}
fn deploy(&self, _site_dir: &Path) -> Result<(), SsgError> {
Ok(())
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn target_from_cli_accepts_all_six() {
for (name, expected) in [
("netlify", Target::Netlify),
("vercel", Target::Vercel),
("cloudflare-pages", Target::CloudflarePages),
("github-pages", Target::GithubPages),
("s3", Target::S3),
("none", Target::None),
] {
assert_eq!(Target::from_cli(name).unwrap(), expected);
}
}
#[test]
fn target_from_cli_rejects_unknown() {
assert!(Target::from_cli("moon").is_err());
}
#[test]
fn target_as_str_round_trips() {
for t in [
Target::Netlify,
Target::Vercel,
Target::CloudflarePages,
Target::GithubPages,
Target::S3,
Target::None,
] {
let s = t.as_str();
assert_eq!(Target::from_cli(s).unwrap(), t);
}
}
#[test]
fn adapter_for_returns_correct_name() {
let pairs = [
(Target::Netlify, "netlify"),
(Target::Vercel, "vercel"),
(Target::CloudflarePages, "cloudflare-pages"),
(Target::GithubPages, "github-pages"),
(Target::S3, "s3"),
(Target::None, "none"),
];
for (t, expected) in pairs {
let a = adapter_for(t);
assert_eq!(a.name(), expected);
}
}
#[test]
fn stub_adapters_succeed() {
let dir = PathBuf::from("/tmp/ssg-test-site");
for t in [
Target::Netlify,
Target::Vercel,
Target::CloudflarePages,
Target::GithubPages,
Target::S3,
Target::None,
] {
let a = adapter_for(t);
let name = a.name();
let result = a.deploy(&dir);
assert!(result.is_ok(), "adapter {name} failed");
}
}
}