Skip to content

Commit

Permalink
Feature: syntax to represent enums in openapi raw type object notation (
Browse files Browse the repository at this point in the history
#228)

* Added syntax to represent enums in openapi raw return type notation

* cargo fmt

* cargo clippy

* cargo fmt

* PR review changes

* PR review changes
  • Loading branch information
Samuel-B-D authored May 14, 2024
1 parent f01f880 commit d5a6724
Show file tree
Hide file tree
Showing 11 changed files with 107 additions and 59 deletions.
1 change: 1 addition & 0 deletions saphir/examples/macro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ impl UserController {
}
}

#[allow(dead_code)]
struct ApiKeyMiddleware(String);

#[middleware]
Expand Down
22 changes: 11 additions & 11 deletions saphir/src/file/content_range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// source: https://github.com/dekellum/hyperx/blob/master/src/header/common/content_range.rs

use crate::error::SaphirError;
use std::str::FromStr;
use std::{fmt::Display, str::FromStr};

/// Content-Range, described in [RFC7233](https://tools.ietf.org/html/rfc7233#section-4.2)
///
Expand Down Expand Up @@ -98,29 +98,29 @@ impl FromStr for ContentRange {
}
}

impl ToString for ContentRange {
fn to_string(&self) -> String {
impl Display for ContentRange {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
ContentRange::Bytes { range, instance_length } => {
let mut string = "bytes ".to_owned();
write!(f, "bytes ")?;
match range {
Some((first_byte, last_byte)) => {
string.push_str(format!("{}-{}", first_byte, last_byte).as_str());
write!(f, "{first_byte}-{last_byte}")?;
}
None => {
string.push('*');
write!(f, "*")?;
}
};
string.push('/');
write!(f, "/")?;
if let Some(v) = instance_length {
string.push_str(v.to_string().as_str());
write!(f, "{v}")?;
} else {
string.push('*')
write!(f, "*")?;
}

string
Ok(())
}
ContentRange::Unregistered { ref unit, ref resp } => format!("{} {}", unit, resp),
ContentRange::Unregistered { ref unit, ref resp } => write!(f, "{unit} {resp}"),
}
}
}
13 changes: 7 additions & 6 deletions saphir/src/file/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use flate2::write::{DeflateEncoder, GzEncoder};
use futures::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt, Cursor};
use mime::Mime;
use std::{
fmt::Display,
io::{Cursor as CursorSync, Write},
str::FromStr,
};
Expand Down Expand Up @@ -212,13 +213,13 @@ impl FromStr for Compression {
}
}

impl ToString for Compression {
fn to_string(&self) -> String {
impl Display for Compression {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Compression::Deflate => "deflate".to_string(),
Compression::Gzip => "gzip".to_string(),
Compression::Brotli => "br".to_string(),
_ => "".to_string(),
Compression::Deflate => write!(f, "deflate"),
Compression::Gzip => write!(f, "gzip"),
Compression::Brotli => write!(f, "br"),
_ => write!(f, ""),
}
}
}
Expand Down
26 changes: 13 additions & 13 deletions saphir/src/file/range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// source: https://github.com/dekellum/hyperx/blob/master/src/header/common/range.rs

use crate::error::SaphirError;
use std::str::FromStr;
use std::{fmt::Display, str::FromStr};

/// `Range` header, defined in [RFC7233](https://tools.ietf.org/html/rfc7233#section-3.1)
///
Expand Down Expand Up @@ -104,32 +104,32 @@ impl ByteRangeSpec {
}
}

impl ToString for ByteRangeSpec {
fn to_string(&self) -> String {
impl Display for ByteRangeSpec {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
ByteRangeSpec::FromTo(from, to) => format!("{}-{}", from, to),
ByteRangeSpec::Last(pos) => format!("-{}", pos),
ByteRangeSpec::AllFrom(pos) => format!("{}-", pos),
ByteRangeSpec::FromTo(from, to) => write!(f, "{from}-{to}"),
ByteRangeSpec::Last(pos) => write!(f, "-{pos}"),
ByteRangeSpec::AllFrom(pos) => write!(f, "{pos}-"),
}
}
}

impl ToString for Range {
fn to_string(&self) -> String {
impl Display for Range {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
Range::Bytes(ref ranges) => {
let mut string = "bytes=".to_owned();
write!(f, "bytes=")?;

for (i, range) in ranges.iter().enumerate() {
if i != 0 {
string.push(',');
write!(f, ",")?;
}
string.push_str(&range.to_string())
write!(f, "{range}")?;
}

string
Ok(())
}
Range::Unregistered(ref unit, ref range_str) => format!("{}={}", unit, range_str),
Range::Unregistered(ref unit, ref range_str) => write!(f, "{unit}={range_str}"),
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion saphir/src/http_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ impl HttpContext {
.get(OPERATION_ID_HEADER)
.and_then(|h| h.to_str().ok())
.and_then(|op_id_str| operation::OperationId::from_str(op_id_str).ok())
.unwrap_or_else(operation::OperationId::new);
.unwrap_or_default();
*request.operation_id_mut() = operation_id;
let state = State::Before(Box::new(request));
let router = Some(router);
Expand Down
1 change: 1 addition & 0 deletions saphir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
//! *_More feature will be added in the future_*
#![allow(clippy::match_like_matches_macro)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![allow(clippy::empty_docs)]

#[macro_use]
extern crate log;
Expand Down
2 changes: 1 addition & 1 deletion saphir/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ where

pub fn build(self) -> Server {
Server {
listener_config: self.listener.unwrap_or_else(ListenerBuilder::new).build(),
listener_config: self.listener.unwrap_or_default().build(),
stack: Stack {
router: self.router.build(),
middlewares: self.middlewares.build(),
Expand Down
6 changes: 5 additions & 1 deletion saphir/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use regex::Regex;
use std::{
cmp::{min, Ordering},
collections::{HashMap, VecDeque},
fmt::Write,
iter::FromIterator,
str::FromStr,
sync::atomic::AtomicU64,
Expand Down Expand Up @@ -392,7 +393,10 @@ impl UriPathMatcher {
let mut segments = path_segments.clone();
if Self::match_start(start, &mut segments) && Self::match_end(end, &mut segments) {
if let Some(name) = wildcard_capture_name {
let value = segments.iter().map(|&s| format!("/{}", s)).collect();
let value = segments.iter().fold(String::new(), |mut o, &s| {
let _ = write!(o, "/{s}");
o
});
captures.insert(name.clone(), value);
}
} else {
Expand Down
4 changes: 2 additions & 2 deletions saphir_cli/src/openapi/generate/crate_syn_browser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ mod target;
pub use self::{
browser::Browser,
file::File,
item::{Enum, Impl, ImplItem, ImplItemKind, Item, ItemKind, Method, Struct, Use},
module::{CrateModule, FileModule, InlineModule, Module, ModuleKind},
item::{Impl, ImplItemKind, Item, ItemKind, Method},
module::Module,
package::Package,
target::Target,
};
Expand Down
18 changes: 18 additions & 0 deletions saphir_cli/src/openapi/generate/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,7 @@ by using the --package flag."
let mut chars = raw.chars();
let first_char = chars.next()?;
match first_char {
// Parse RAW Object notation
'{' => {
let mut cur_key: Option<&str> = None;
let mut properties = BTreeMap::new();
Expand Down Expand Up @@ -701,6 +702,7 @@ by using the --package flag."
}
None
}
// Parse RAW Array notation
'[' => {
if chars.last()? != ']' {
return None;
Expand All @@ -717,6 +719,18 @@ by using the --package flag."
)
})
}
// Parse RAW String Enum notation
'e' if raw.starts_with("enum(") => {
if chars.last()? != ')' {
return None;
}
let values = raw[5..(len - 1)]
.split(&[',', '|'][..])
.map(|v| v.trim().trim_start_matches(char_is_quote).trim_end_matches(char_is_quote).to_string())
.collect::<Vec<_>>();
let values_len = values.len();
Some((OpenApiSchema::Inline(OpenApiType::enums(values)), None, values_len))
}
_ => syn::parse_str::<syn::Path>(raw)
.ok()
.and_then(|p| TypeInfo::new_from_path(scope, &p))
Expand All @@ -729,6 +743,10 @@ by using the --package flag."
}
}

fn char_is_quote(c: char) -> bool {
matches!(c, '\'' | '"')
}

#[derive(Clone, Debug)]
pub(crate) struct BodyParamInfo {
openapi_type: OpenApiMimeType,
Expand Down
71 changes: 47 additions & 24 deletions saphir_cli/src/openapi/generate/response_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ impl Gen {

if !vec.is_empty() {
for meta in &openapi_metas {
self.override_response_info_from_openapi_meta(meta, &mut vec);
self.override_response_info_from_openapi_meta(method, meta, &mut vec);
}
} else {
vec.push((
Expand Down Expand Up @@ -138,7 +138,7 @@ impl Gen {
for (code, type_name) in pairs {
if !type_name.is_empty() {
if let Some(mut anonymous_type) = self.openapitype_from_raw(method.impl_item.im.item.scope, type_name.as_str()) {
anonymous_type.name = name.clone();
anonymous_type.name.clone_from(&name);
vec.push((
Some(code),
ResponseInfo {
Expand Down Expand Up @@ -186,7 +186,7 @@ impl Gen {
vec
}

fn override_response_info_from_openapi_meta(&self, meta: &MetaList, responses: &mut Vec<(Option<u16>, ResponseInfo)>) {
fn override_response_info_from_openapi_meta<'b>(&mut self, method: &'b Method<'b>, meta: &MetaList, responses: &mut Vec<(Option<u16>, ResponseInfo)>) {
let mut extra_responses: Vec<(Option<u16>, ResponseInfo)> = Vec::new();
for openapi_paths in &meta.nested {
match openapi_paths {
Expand All @@ -197,6 +197,7 @@ impl Gen {
let mut codes: Vec<u16> = Vec::new();
let mut type_path: Option<String> = None;
let mut mime: Option<String> = None;
let mut name: Option<String> = None;
if nl.nested.is_empty() {
continue;
}
Expand Down Expand Up @@ -226,40 +227,62 @@ impl Gen {
mime = Some(s.value());
}
}
Some("name") => {
if let Lit::Str(s) = &nv.lit {
name = Some(s.value());
}
}
_ => {}
}
}
}

let type_path = match type_path {
Some(t) => t,
None => return,
};

let mime = mime.map(OpenApiMimeType::from);
let res = responses.iter_mut().find(|(_, ri)| {
if let Some(ti) = &ri.type_info {
// TODO: clever-er match
return ti.name == type_path;
}
false
});

// Match by code first, then by type
let mut matched_on_code = false;
let mut res = None;
if codes.len() == 1 {
res = responses.iter_mut().find(|(_, ri)| codes[0] == ri.code);
matched_on_code = res.is_some();
}
if let (Some(type_path), None) = (&type_path, &res) {
res = responses.iter_mut().find(|(_, ri)| {
if let Some(ti) = &ri.type_info {
// TODO: clever-er match
return ti.name == *type_path;
}
false
});
}

if let Some(res) = res {
if let Some(mime) = mime {
res.1.mime = mime;
}

if let Some(first_code) = codes.first() {
res.1.code = *first_code;
res.0 = Some(*first_code);
if !matched_on_code {
if let Some(first_code) = codes.first() {
res.1.code = *first_code;
res.0 = Some(*first_code);

if codes.len() > 1 {
for code in codes.iter().skip(1) {
let mut new_res = (Some(*code), res.1.clone());
new_res.1.code = *code;
extra_responses.push(new_res);
if codes.len() > 1 {
for code in codes.iter().skip(1) {
let mut new_res = (Some(*code), res.1.clone());
new_res.1.code = *code;
extra_responses.push(new_res);
}
}
}
} else if let Some(type_path) = type_path {
let anonymous_type = self.openapitype_from_raw(method.impl_item.im.item.scope, type_path.as_str());
if let Some(mut anonymous_type) = anonymous_type {
if let Some(name) = name {
anonymous_type.name = Some(name);
}

res.1.type_info = None;
res.1.anonymous_type = Some(anonymous_type);
}
}
}
Expand Down Expand Up @@ -331,7 +354,7 @@ impl Gen {
let mut result = self.extract_arguments(method, &last.arguments);
if result.len() == 1 {
for (_, mut success_response) in result.remove(0) {
if let Some(mut type_info) = success_response.type_info.as_mut() {
if let Some(type_info) = success_response.type_info.as_mut() {
type_info.is_array = true;
}
vec.push((None, success_response));
Expand Down

0 comments on commit d5a6724

Please sign in to comment.