Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: openapiv3 cli-ng codegen #506

Merged
merged 4 commits into from
Oct 21, 2024
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
8 changes: 4 additions & 4 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,14 @@ jobs:
timeout-minutes: 10
with:
command: build
args: --all --features "actix4 cli chrono url uuid swagger-ui rapidoc v3 actix4-validator"
args: --all --features "actix4 cli chrono url uuid swagger-ui rapidoc v3 actix4-validator cli-ng"

- name: Build actix3 features
uses: actions-rs/cargo@v1
timeout-minutes: 10
with:
command: build
args: --all --features "actix3 cli chrono url uuid swagger-ui rapidoc v3 actix3-validator"
args: --all --features "actix3 cli chrono url uuid swagger-ui rapidoc v3 actix3-validator cli-ng"

# - name: Build actix2 features
# uses: actions-rs/cargo@v1
Expand All @@ -82,14 +82,14 @@ jobs:
timeout-minutes: 20
with:
command: test
args: --all --features "actix4 cli chrono url uuid swagger-ui rapidoc v3 actix4-validator"
args: --all --features "actix4 cli chrono url uuid swagger-ui rapidoc v3 actix4-validator cli-ng"

- name: Run actix3 tests
uses: actions-rs/cargo@v1
timeout-minutes: 20
with:
command: test
args: --all --features "actix3 cli chrono url uuid swagger-ui rapidoc v3 actix3-validator"
args: --all --features "actix3 cli chrono url uuid swagger-ui rapidoc v3 actix3-validator cli-ng"

# - name: Run actix2 tests
# uses: actions-rs/cargo@v1
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
- uses: actions-rs/cargo@v1
with:
command: doc
args: --all --features "actix4 cli chrono url uuid swagger-ui v3" --no-deps
args: --all --features "actix4 cli chrono url uuid swagger-ui v3 cli-ng" --no-deps

- name: Setup GitBook
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/linter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
continue-on-error: ${{ matrix.experimental }}
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all --features "actix4 cli chrono url uuid swagger-ui v3" -- -D clippy::all ${{ matrix.exclude }}
args: --all --features "actix4 cli chrono url uuid swagger-ui v3 cli-ng" -- -D clippy::all ${{ matrix.exclude }}

fmt:
name: rustfmt
Expand Down
42 changes: 42 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Publish Bins
on:
release:
types: [published]
jobs:
paperclip-bins:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-musl
- os: ubuntu-latest
target: aarch64-unknown-linux-musl
- os: macos-14
target: x86_64-apple-darwin
- os: macos-13
target: aarch64-apple-darwin
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Build
uses: actions-rs/cargo@v1
with:
use-cross: true
command: build
args: --bins --verbose --release --features "cli" -p paperclip -p paperclip-ng --target ${{ matrix.target }}
- name: Archive
shell: bash
run: |
tar -czf paperclip-${{ matrix.target }}.tar.gz LICENSE-APACHE LICENSE-MIT -C ./target/${{ matrix.target }}/release/ paperclip paperclip-ng
- name: Publish
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release upload "${{ github.event.release.tag_name }}" --clobber *.tar.gz
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.9.3] - 2024-10-21
### Added
- Experimental openapiv3 cli codegen. [PR#506](https://github.com/paperclip-rs/paperclip/pull/506)

## [0.9.2] - 2024-10-13
### Fixed
- Switch to pro-macro-error2. [PR#545](https://github.com/paperclip-rs/paperclip/pull/545)
Expand Down
9 changes: 6 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "paperclip"
version = "0.9.2"
version = "0.9.3"
edition = "2018"
description = "OpenAPI tooling library for type-safe compile-time checked HTTP APIs"
documentation = "https://paperclip-rs.github.io/paperclip/paperclip"
Expand All @@ -20,9 +20,10 @@ required-features = ["cli"]
paperclip-actix = { path = "plugins/actix-web", version = "0.7.2", optional = true }
paperclip-core = { path = "core", version = "0.7.2" }
paperclip-macros = { path = "macros", version = "0.7.0", optional = true }
paperclip-ng = { path = "cli-ng", version = "0.1.0", optional = true }

env_logger = { version = "0.8", optional = true }
git2 = { version = "0.15", optional = true }
git2 = { version = "0.15", default-features = false, optional = true }
heck = { version = "0.4", optional = true }
http = { version = "0.2", optional = true }
itertools = "0.10"
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-paper", "v2", "paperclip-core/v3", "paperclip-actix/v3"]

# Experimental V3 CodeGen
cli-ng = ["cli", "paperclip-ng", "openapiv3-paper"]

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

[[test]]
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ doc:
build:
cargo build
cargo build --features actix4
cargo build --features cli
cargo build --features "cli cli-ng"

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

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

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

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"
30 changes: 30 additions & 0 deletions cli-ng/src/bin/cli/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);
99 changes: 99 additions & 0 deletions cli-ng/src/bin/cli/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
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,
/// Use custom templates (mustache) files, rather than the builtin ones.
/// The root dir in this path must be the template name, example: default.
#[structopt(short = "t", long = "templates", parse(from_os_str))]
templates: Option<PathBuf>,
}

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_ng::v3_03::PackageInfo {
libname: name.to_snake_case(),
name,
version: opt.version,
edition: opt.edition,
};

paperclip_ng::v3_03::OpenApiV3::new(spec, opt.templates, 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);
std::process::exit(1);
}
}
5 changes: 5 additions & 0 deletions cli-ng/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod v3;

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