Skip to content

Commit

Permalink
test: poc openapi v3 codegen
Browse files Browse the repository at this point in the history
  • Loading branch information
tiagolobocastro committed Jun 30, 2024
1 parent b20eeab commit 591af3a
Show file tree
Hide file tree
Showing 56 changed files with 4,727 additions and 30 deletions.
7 changes: 5 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ required-features = ["cli"]
paperclip-actix = { path = "plugins/actix-web", version = "0.7.0", optional = true }
paperclip-core = { path = "core", version = "0.7.0" }
paperclip-macros = { path = "macros", version = "0.6.3", optional = true }
paperclip-codegen = { path = "codegen", version = "0.1.0", optional = true }

env_logger = { version = "0.8", optional = true }
git2 = { version = "0.15", optional = true }
Expand All @@ -40,7 +41,7 @@ url_dep = { version = ">=1.7,<3", package = "url" }
thiserror = "1.0"
anyhow = "1.0"
once_cell = "1.4"
openapiv3 = { version = "1.0.3", optional = true }
openapiv3 = { version = "2.0.0", optional = true }

[dev-dependencies]
actix-rt1 = { version = "1.0", package = "actix-rt" }
Expand Down Expand Up @@ -83,7 +84,8 @@ codegen = ["heck", "http", "log", "regex", "tinytemplate", "paperclip-core/codeg
v2 = ["paperclip-macros/v2", "paperclip-core/v2"]
# OpenAPI v2 to v3 support
v3 = ["openapiv3", "v2", "paperclip-core/v3", "paperclip-actix/v3"]

# Experimental V3 CodeGen
v3-poc = ["paperclip-codegen", "openapiv3"]

# Features for implementing traits for dependencies.
actix-multipart = ["paperclip-core/actix-multipart"]
Expand All @@ -106,6 +108,7 @@ members = [
"core",
"macros",
"plugins/actix-web",
"codegen"
]

[[test]]
Expand Down
35 changes: 35 additions & 0 deletions codegen/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[package]
name = "paperclip-codegen"
version = "0.1.0"
edition = "2018"
license = "MIT OR Apache-2.0"
keywords = [ "openapi", "openapiv3", "codegen" ]
description = "Experimental OpenAPI V3.0.3 Code Generator"
homepage = "https://github.com/paperclip-rs/paperclip"
repository = "https://github.com/paperclip-rs/paperclip"

[lib]

[[bin]]
name = "paperclip-cli"
path = "src/bin/codegen/main.rs"

[dependencies]
ramhorns = { version = "1.0", default-features = false, features = ["indexes"], optional = true }
ramhorns-derive = { version = "1.0", optional = true }
openapiv3 = { version = "2.0.0", optional = true }
heck = { version = "0.4", optional = true }
itertools = { version = "0.10", optional = true }

env_logger = "0.8"
log = { version = "0.4", features = ["kv_unstable"] }
structopt = { version = "0.3" }
serde_json = "1.0"
serde_yaml = "0.9"
thiserror = "1.0"


[features]
default = [ "poc" ]
ramhorns-feat = [ "ramhorns", "ramhorns-derive" ]
poc = [ "ramhorns-feat", "openapiv3", "heck", "itertools" ]
30 changes: 30 additions & 0 deletions codegen/src/bin/codegen/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
macro_rules! impl_err_from {
($err:ident :: $type:ty > $variant:ident) => {
impl From<$type> for $err {
fn from(s: $type) -> Self {
$err::$variant(s)
}
}
};
}

/// Global error which encapsulates all related errors.
#[derive(Debug, thiserror::Error)]
pub(crate) enum Error {
/// The given directory cannot be used for generating code.
#[error("Cannot generate code in the given directory")]
InvalidCodegenDirectory,
/// I/O errors.
#[error("I/O error: {}", _0)]
Io(std::io::Error),
/// JSON coding errors.
#[error("JSON error: {}", _0)]
Json(serde_json::Error),
/// YAML coding errors.
#[error("YAML error: {}", _0)]
Yaml(serde_yaml::Error),
}

impl_err_from!(Error::std::io::Error > Io);
impl_err_from!(Error::serde_json::Error > Json);
impl_err_from!(Error::serde_yaml::Error > Yaml);
94 changes: 94 additions & 0 deletions codegen/src/bin/codegen/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
use heck::ToSnakeCase;
use std::{
fs::{self, File},
io::Read,
path::PathBuf,
};
use structopt::StructOpt;

mod error;
use error::Error;

/// Deserialize the schema from the given reader. Currently, this only supports
/// JSON and YAML formats.
fn from_reader_v3<R>(mut reader: R) -> Result<openapiv3::OpenAPI, Error>
where
R: Read,
{
let mut buf = [b' '];
while buf[0].is_ascii_whitespace() {
reader.read_exact(&mut buf)?;
}
let reader = buf.as_ref().chain(reader);

Ok(if buf[0] == b'{' {
serde_json::from_reader::<_, openapiv3::OpenAPI>(reader)?
} else {
serde_yaml::from_reader::<_, openapiv3::OpenAPI>(reader)?
})
}
fn parse_spec_v3(s: &str) -> Result<openapiv3::OpenAPI, Error> {
let fd = File::open(s)?;
from_reader_v3(fd)
}

#[derive(Debug, StructOpt)]
struct Opt {
/// Path to OpenAPI spec in JSON/YAML format (also supports publicly accessible URLs).
#[structopt(long)]
spec: std::path::PathBuf,
/// Output directory to write code (default: current working directory).
#[structopt(short = "o", long = "out", parse(from_os_str))]
output: Option<PathBuf>,
/// Don't Render models.
#[structopt(long)]
no_models: bool,
/// Don't Render operations.
#[structopt(long)]
no_ops: bool,
/// Name of the crate. If this is not specified, then the name of the
/// working directory is assumed to be crate name.
#[structopt(long = "name")]
pub name: Option<String>,
/// The Version of the crate.
#[structopt(long = "version", default_value = "0.1.0")]
pub version: String,
/// The Edition of the crate.
#[structopt(long = "edition", default_value = "2018")]
pub edition: String,
}

fn parse_args_and_run() -> Result<(), Error> {
let opt: Opt = Opt::from_args();

if let Some(o) = &opt.output {
fs::create_dir_all(o)?;
}

let spec = parse_spec_v3(opt.spec.to_string_lossy().as_ref())?;
let name = opt.name.map(Ok::<String, Error>).unwrap_or_else(|| {
Ok(fs::canonicalize(std::path::Path::new("."))?
.file_name()
.ok_or(Error::InvalidCodegenDirectory)?
.to_string_lossy()
.into_owned()
.to_snake_case())
})?;
let info = paperclip_codegen::v3_03::PackageInfo {
libname: name.to_snake_case(),
name,
version: opt.version,
edition: opt.edition,
};

paperclip_codegen::v3_03::OpenApiV3::new(spec, opt.output, info)
.run(!opt.no_models, !opt.no_ops)?;
Ok(())
}

fn main() {
env_logger::init();
if let Err(e) = parse_args_and_run() {
eprintln!("{}", e);
}
}
9 changes: 9 additions & 0 deletions codegen/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
mod v3;

pub mod v3_03 {
pub use super::v3::{OpenApiV3, PackageInfo};
}

#[cfg_attr(feature = "ramhorns-feat", macro_use)]
#[cfg(feature = "ramhorns-feat")]
extern crate log;
Loading

0 comments on commit 591af3a

Please sign in to comment.