Skip to content
This repository has been archived by the owner on Jan 11, 2023. It is now read-only.

Refactor to plugin based system #22

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
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
1,559 changes: 184 additions & 1,375 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
[workspace]
members = ["actix-swagger", "cargo-swagg", "swagg", "demo"]
# members = ["actix-swagger", "cargo-swagg", "swagg", "demo"]
members = ["alternate", "openapi-hooks", "openapi-actix", "openapi-resolver"]

[patch.crates-io]
swagg = { path = "./swagg" }
# swagg = { path = "./swagg" }
openapi-actix = {path = './openapi-actix'}
openapi-hooks = {path = './openapi-hooks'}
openapi-resolver = {path = './openapi-resolver'}
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# actix swagger

## Ideas

- run function for each path and component with custom mutable state
- run hooks before and after each/all path and components to compute data
- run hooks immediately before rendering to file, to structure and validate data

## Usage

> Not for production use yet
Expand Down
28 changes: 28 additions & 0 deletions alternate/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[package]
authors = ["Sergey Sova <[email protected]>"]
edition = "2018"
name = "alternate"
version = "0.1.0"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
indexmap = {version = "1.0", features = ["serde-1"]}
openapi-actix = {version = "0.1.0", optional = true}
openapi-hooks = "0.1.0"
openapi-resolver = "0.1.0"
openapiv3 = "0.3.2"
proc-macro2 = "1.0"
quote = "1.0"
serde = {version = "1.0", features = ["derive"]}
serde_json = "1.0"
serde_yaml = "0.8"

[dev-dependencies]
insta = {version = "1.6", features = ["ron", "redactions"]}
tempfile = "3.2"

[features]
default = ["actix"]

actix = ["openapi-actix"]
139 changes: 139 additions & 0 deletions alternate/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
use openapiv3::{OpenAPI, ReferenceOr};

#[cfg(feature = "actix")]
use openapi_actix::ActixPlugin;

use openapi_hooks::{Internal, Method, Plugin};
use openapi_resolver::{resolve_reference, RefResolve};
pub use openapiv3 as openapi;

struct DefaultPlugin {}

impl<'a, I: Internal<'a>> Plugin<'a, I> for DefaultPlugin {}

pub struct InternalApi<'a> {
files: std::collections::HashMap<String, String>,
api: &'a OpenAPI,
}

impl<'a> InternalApi<'a> {
fn new(api: &'a OpenAPI) -> Self {
Self {
api,
files: Default::default(),
}
}
}

impl<'a> Internal<'a> for InternalApi<'a> {
fn create_file(&mut self, name: String, content: String) {
self.files.insert(name, content);
}

fn resolve<T>(&'a self, source: &'a ReferenceOr<T>) -> Option<&'a T>
where
&'a T: RefResolve<'a>,
{
resolve_reference(source, &self.api)
}

fn root(&'a self) -> &'a OpenAPI {
self.api
}
}

fn main() {
let schema = std::path::Path::new("./demo/openapi.yaml");
let content = std::fs::read_to_string(&schema).expect("file found");
let root: OpenAPI = serde_yaml::from_str(&content).expect("parsed to struct");

let mut internal = InternalApi::new(&root);

#[cfg(feature = "actix")]
let mut plugin = ActixPlugin::new();

#[cfg(not(feature = "actix"))]
let mut plugin = DefaultPlugin {};

plugin.pre_components(&internal);
if let Some(ref components) = root.components {
for (name, security_scheme) in components.security_schemes.iter() {
match resolve_reference(security_scheme, &root) {
None => panic!("failed to resolve reference for security scheme '{}'", name),
Some(schema) => plugin.on_security_scheme(name, schema, &internal),
}
}

for (name, response) in components.responses.iter() {
match resolve_reference(response, &root) {
None => panic!("failed to resolve reference for response '{}'", name),
Some(schema) => plugin.on_response(name, schema, &internal),
}
}

for (name, parameter) in components.parameters.iter() {
match resolve_reference(parameter, &root) {
None => panic!("failed to resolve reference for parameter '{}'", name),
Some(schema) => plugin.on_parameter(name, schema, &internal),
}
}

for (name, request_body) in components.request_bodies.iter() {
match resolve_reference(request_body, &root) {
None => panic!("failed to resolve reference for request body '{}'", name),
Some(schema) => plugin.on_request_body(name, schema, &internal),
}
}

for (name, header) in components.headers.iter() {
match resolve_reference(header, &root) {
None => panic!("failed to resolve reference for header '{}'", name),
Some(schema) => plugin.on_header(name, schema, &internal),
}
}

for (name, schema) in components.schemas.iter() {
match resolve_reference(schema, &root) {
None => panic!("failed to resolve reference for schema '{}'", name),
Some(schema) => plugin.on_schema(name, schema, &internal),
}
}
}
plugin.post_components(&internal);

plugin.pre_paths(&internal);
for (name, path) in root.paths.iter() {
match resolve_reference(path, &root) {
None => panic!("failed to resolve reference for '{}'", name),
Some(path) => {
if let Some(ref operation) = path.get {
plugin.on_operation(Method::Get, name, &operation, &internal);
}
if let Some(ref operation) = path.put {
plugin.on_operation(Method::Put, name, &operation, &internal);
}
if let Some(ref operation) = path.post {
plugin.on_operation(Method::Post, name, &operation, &internal);
}
if let Some(ref operation) = path.delete {
plugin.on_operation(Method::Delete, name, &operation, &internal);
}
if let Some(ref operation) = path.options {
plugin.on_operation(Method::Options, name, &operation, &internal);
}
if let Some(ref operation) = path.head {
plugin.on_operation(Method::Head, name, &operation, &internal);
}
if let Some(ref operation) = path.patch {
plugin.on_operation(Method::Patch, name, &operation, &internal);
}
if let Some(ref operation) = path.trace {
plugin.on_operation(Method::Trace, name, &operation, &internal);
}
}
}
}
plugin.post_paths(&internal);

plugin.proceed(&mut internal);
}
1 change: 1 addition & 0 deletions demo/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -364,3 +364,4 @@ components:
required:
- baz
- bar

19 changes: 19 additions & 0 deletions openapi-actix/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
authors = ["Sergey Sova <[email protected]>"]
edition = "2018"
name = "openapi-actix"
version = "0.1.0"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
inflections = "1.1.1"
openapi-hooks = "0.1.0"
proc-macro2 = "1.0.8"
quote = "1.0.2"
regex = "1.3.3"
serde = "1.0.123"
tempfile = "3.2.0"

[dev-dependencies]
insta = {version = "1.6", features = ["ron", "redactions"]}
164 changes: 164 additions & 0 deletions openapi-actix/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
use inflections::Inflect;
use openapi_hooks::v3::{
self, Header, Operation, Parameter, ParameterData, ParameterSchemaOrContent, RequestBody,
Response, Schema, SchemaKind,
};
use openapi_hooks::{Internal, Method, Plugin};
use printer::{
components::{
component::Component, parameters::ParametersModule, Field, FieldType, FormatFloat,
FormatInteger, FormatString, NativeType,
},
Printable,
};
use v3::VariantOrUnknownOrEmpty;

mod printer;
#[cfg(test)]
pub mod test;

#[derive(Default)]
pub struct ActixPlugin {
schemas: Vec<(String, String)>,
parameters_module: ParametersModule,
}

impl ActixPlugin {
pub fn new() -> Self {
Default::default()
}

fn add_parameter<'i, I: Internal<'i>>(&'i mut self, parameter: &'i Parameter, internal: &'i I) {
match parameter {
Parameter::Query { parameter_data, .. } => {
let ParameterData {
name,
description,
required,
deprecated,
format,
..
} = parameter_data;

let schema = self
.resolve_schema_from_parameter_format(format, internal)
.expect(
format!(
"Failed to resolve schema reference for parameter '{}'",
name,
)
.as_str(),
);

match &schema.schema_kind {
SchemaKind::OneOf { .. } => {}
SchemaKind::AllOf { .. } => {}
SchemaKind::AnyOf { .. } => {}
SchemaKind::Any(_) => {}
SchemaKind::Type(schema_type) => {
use v3::VariantOrUnknownOrEmpty::{Empty, Item, Unknown};

let content_type = match schema_type {
v3::Type::Number(s) => match s.format {
Empty | Unknown(_) => FieldType::native_float(Default::default()),
Item(v3::NumberFormat::Float) => {
FieldType::native_float(FormatFloat::Float)
}
Item(v3::NumberFormat::Double) => {
FieldType::native_float(FormatFloat::Double)
}
},
v3::Type::Integer(s) => match s.format {
Empty | Unknown(_) => FieldType::native_integer(Default::default()),
Item(v3::IntegerFormat::Int32) => {
FieldType::native_integer(FormatInteger::Int32)
}
Item(v3::IntegerFormat::Int64) => {
FieldType::native_integer(FormatInteger::Int64)
}
},
v3::Type::String(_) => FieldType::native_string(Default::default()),
v3::Type::Boolean {} => FieldType::native_boolean(),
v3::Type::Array(list) => unimplemented!(),
v3::Type::Object(object) => {
unimplemented!()
}
};

let component_name = name.to_pascal_case();
let component = match content_type {
FieldType::Native(_) => Component::Type {
name: component_name.clone(),
description: None,
type_value: content_type,
},
FieldType::Array(_) => Component::Type {
name: component_name.clone(),
description: None,
type_value: content_type,
},
_ => unimplemented!(),
// FieldType::Object(_) => Component::Object {
// name: component_name.clone(),
// description: None,
// fields: vec![]
// }
};

self.parameters_module.list.push(component);

// self.parameters_module.list.push(Component::Type {
// name: name.clone().to_pascal_case(),
// description: None,
// type_value: FieldType::Internal(format!(
// "actix_web::web::Query<{}>",
// component_name
// )),
// });
}
}
}
rest => println!("Parameter type is not supported yet {:#?}", rest),
}
}

fn resolve_schema_from_parameter_format<'i, I: Internal<'i>>(
&self,
param: &'i ParameterSchemaOrContent,
internal: &'i I,
) -> Option<&'i Schema> {
match param {
ParameterSchemaOrContent::Schema(refor) => internal.resolve(refor),
ParameterSchemaOrContent::Content(content_map) => {
let value = content_map.get("application/json")?;
match value.schema {
Some(ref value) => internal.resolve(value),
None => return None,
}
}
}
}
}

impl<'i, I: Internal<'i>> Plugin<'i, I> for ActixPlugin {
fn on_parameter(&'i mut self, _name: &'i str, parameter: &'i Parameter, internal: &'i I) {
self.add_parameter(parameter, internal);
}

fn pre_components(&'i mut self, internal: &'i I) {
println!(
"{} {}",
internal.root().info.version,
internal.root().info.title,
);
}

fn post_components(&'i mut self, internal: &'i I) {
let result = self.parameters_module.print().to_string();
println!("{}", result);
}

fn proceed(&'i mut self, internal: &'i mut I) {
internal.create_file("lib.rs".to_owned(), "".to_owned());
}
}
Loading