Skip to content

Commit

Permalink
Implement example for schema
Browse files Browse the repository at this point in the history
  • Loading branch information
kdy1 committed Jan 25, 2020
1 parent ab361be commit 727b3a4
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 18 deletions.
79 changes: 76 additions & 3 deletions macros/src/openapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,66 @@ use syn::{
};

pub fn derive_schema(mut input: DeriveInput) -> TokenStream {
fn extract_example(attrs: &mut Vec<Attribute>) -> Option<TokenStream> {
let mut v = None;

attrs.iter().find(|attr| {
if attr.path.is_ident("example") {
for config in parse2::<Paren<Delimited<Meta>>>(attr.tokens.clone())
.expect("invalid schema config found while extracting example")
.inner
.inner
{
match config {
Meta::Path(v) => unimplemented!("#[schema]: Meta::Path({})", v.dump()),
Meta::List(v) => unimplemented!("#[schema]: Meta::List({})", v.dump()),
Meta::NameValue(n) => {
if n.path.is_ident("example") {
assert!(
v.is_none(),
"duplicate #[schema(example = \"foo\")] detected"
);

v = Some(match n.lit {
Lit::Str(s) => s
.value()
.parse::<TokenStream>()
.expect("expected example to be path"),
l => panic!(
"#[schema(example = \"foo\")]: value of example should be \
a string literal, but got {}",
l.dump()
),
});
}
}
}
}
}

true
});

let v = v?;
match syn::parse2::<Lit>(v.clone()) {
Ok(v) => {
let v = match v {
Lit::Str(v) => q!(Vars { v }, { String(v.into()) }),
Lit::ByteStr(_) => panic!("byte string is not a valid example"),
Lit::Byte(_) => panic!("byte is not a valid example"),
Lit::Char(v) => q!(Vars { v }, { String(v.into()) }),
Lit::Int(v) => q!(Vars { v }, { Number(v.into()) }),
Lit::Float(v) => q!(Vars { v }, { Number(v.into()) }),
Lit::Bool(v) => q!(Vars { v }, { Bool(v) }),
Lit::Verbatim(_) => unimplemented!("Verbatim?"),
};

Some(q!(Vars { v }, (rweb::rt::serde_json::Value::v)).into())
}
Err(..) => Some(v),
}
}

fn extract_doc(attrs: &mut Vec<Attribute>) -> String {
let mut doc = None;
let mut comments = String::new();
Expand Down Expand Up @@ -63,11 +123,14 @@ pub fn derive_schema(mut input: DeriveInput) -> TokenStream {
let i = f.ident.as_ref().unwrap();

let desc = extract_doc(&mut f.attrs);
let example_v = extract_example(&mut f.attrs);

q!(
Vars {
name: i,
desc,
Type: &f.ty
Type: &f.ty,
example_v: quote_option(example_v),
},
{
map.insert(rweb::rt::Cow::Borrowed(stringify!(name)), {
Expand All @@ -78,6 +141,10 @@ pub fn derive_schema(mut input: DeriveInput) -> TokenStream {
if !description.is_empty() {
s.description = rweb::rt::Cow::Borrowed(description);
}
let example = example_v;
if let Some(example) = example {
s.example = Some(example);
}
s
}
});
Expand Down Expand Up @@ -110,17 +177,20 @@ pub fn derive_schema(mut input: DeriveInput) -> TokenStream {
let desc = extract_doc(&mut input.attrs);

let mut component = None;
let example = extract_example(&mut input.attrs);

input.attrs.retain(|attr| {
if attr.path.is_ident("schema") {
for config in parse2::<Paren<Delimited<Meta>>>(attr.tokens.clone())
.expect("schema config is invalid")
.expect("schema config of type is invalid")
.inner
.inner
{
match config {
Meta::Path(..) => unimplemented!("Meta::Path in #[schema]"),
Meta::NameValue(n) => {
//

if n.path.is_ident("component") {
assert!(
component.is_none(),
Expand All @@ -147,6 +217,9 @@ pub fn derive_schema(mut input: DeriveInput) -> TokenStream {
});

let mut fields: Punctuated<FieldValue, Token![,]> = Default::default();
if let Some(tts) = example {
fields.push(q!(Vars { tts }, ({ example: Some(tts) })).parse());
}

match input.data {
Data::Struct(ref mut data) => {
Expand All @@ -159,7 +232,7 @@ pub fn derive_schema(mut input: DeriveInput) -> TokenStream {
_ => {}
}

fields.push(q!({ schema_type: rweb::openapi::Type::Object }).parse());
fields.push(q!({ schema_type: Some(rweb::openapi::Type::Object) }).parse());
}
Data::Enum(ref mut data) => {
let exprs: Punctuated<Expr, Token![,]> = data
Expand Down
16 changes: 8 additions & 8 deletions src/openapi/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ impl Entity for () {
#[inline]
fn describe() -> Schema {
Schema {
schema_type: Type::Object,
schema_type: Some(Type::Object),
..Default::default()
}
}
Expand All @@ -89,7 +89,7 @@ macro_rules! integer {
#[inline]
fn describe() -> Schema {
Schema {
schema_type: Type::Integer,
schema_type: Some(Type::Integer),
..Default::default()
}
}
Expand Down Expand Up @@ -118,7 +118,7 @@ macro_rules! number {
#[inline]
fn describe() -> Schema {
Schema {
schema_type: Type::Number,
schema_type: Some(Type::Number),
..Default::default()
}
}
Expand All @@ -133,7 +133,7 @@ impl Entity for bool {
#[inline]
fn describe() -> Schema {
Schema {
schema_type: Type::Boolean,
schema_type: Some(Type::Boolean),
..Default::default()
}
}
Expand All @@ -143,7 +143,7 @@ impl Entity for char {
#[inline]
fn describe() -> Schema {
Schema {
schema_type: Type::String,
schema_type: Some(Type::String),
..Default::default()
}
}
Expand All @@ -153,7 +153,7 @@ impl Entity for str {
#[inline]
fn describe() -> Schema {
Schema {
schema_type: Type::String,
schema_type: Some(Type::String),
..Default::default()
}
}
Expand Down Expand Up @@ -222,7 +222,7 @@ impl<T: Entity> Entity for Vec<T> {
impl<T: Entity> Entity for [T] {
fn describe() -> Schema {
Schema {
schema_type: Type::Array,
schema_type: Some(Type::Array),
items: Some(Box::new(T::describe())),
..Default::default()
}
Expand All @@ -235,7 +235,7 @@ impl<T: Entity> Entity for [T] {
(
Cow::Owned(format!("{}List", name)),
Schema {
schema_type: Type::Array,
schema_type: Some(Type::Array),
items: Some(Box::new(s)),
..Default::default()
},
Expand Down
19 changes: 12 additions & 7 deletions src/openapi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,16 +304,21 @@ impl Collector {
let s = T::describe();

match s.schema_type {
Type::Object => {
Some(Type::Object) => {
//

for (name, ty) in s.properties {
if ty.properties.is_empty() {
for (name, s) in s.properties {
if s.properties.is_empty() {
op.parameters.push(ObjectOrReference::Object(Parameter {
name,
param_type: ty.schema_type,
param_type: None,
location: Location::Query,
description: ty.description,
description: s.description,
schema: Some(Schema {
example: s.example,
schema_type: s.schema_type,
..Default::default()
}),
..Default::default()
}));
} else {
Expand All @@ -322,8 +327,8 @@ impl Collector {
name,
location: Location::Query,
unique_items: None,
description: ty.description.clone(),
schema: Some(ty),
description: s.description.clone(),
schema: Some(s),
..Default::default()
}));
}
Expand Down
1 change: 1 addition & 0 deletions src/rt.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub use http::StatusCode;
pub use serde_json;
use std::convert::Infallible;
pub use std::{borrow::Cow, clone::Clone, collections::BTreeMap, default::Default};
pub use tokio;
Expand Down
28 changes: 28 additions & 0 deletions tests/openapi_schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,31 @@ fn component_test() {
let yaml = serde_yaml::to_string(&spec).unwrap();
println!("{}", yaml);
}

#[derive(Debug, Deserialize, Schema)]
struct ExampleReq {
#[schema(example = "10")]
limit: usize,
data: String,
}

#[get("/")]
fn example(_: Query<ExampleReq>) -> String {
String::new()
}

#[test]
fn example_test() {
let (spec, _) = openapi::spec().build(|| {
//
example()
});

assert!(spec.paths.get("/").is_some());
assert!(spec.paths.get("/").unwrap().get.is_some());

let yaml = serde_yaml::to_string(&spec).unwrap();
println!("{}", yaml);

panic!()
}

0 comments on commit 727b3a4

Please sign in to comment.