mirror of
https://gitlab.com/msrd0/gotham-restful.git
synced 2025-02-22 20:52:27 +00:00
basic structure for openapi_type crate
This commit is contained in:
parent
2251c29d7b
commit
90870e3b6a
9 changed files with 261 additions and 1 deletions
|
@ -1,7 +1,7 @@
|
|||
# -*- eval: (cargo-minor-mode 1) -*-
|
||||
|
||||
[workspace]
|
||||
members = [".", "./derive", "./example"]
|
||||
members = [".", "./derive", "./example", "./openapi_type", "./openapi_type_derive"]
|
||||
|
||||
[package]
|
||||
name = "gotham_restful"
|
||||
|
@ -76,3 +76,5 @@ features = ["full"]
|
|||
[patch.crates-io]
|
||||
gotham_restful = { path = "." }
|
||||
gotham_restful_derive = { path = "./derive" }
|
||||
openapi_type = { path = "./openapi_type" }
|
||||
openapi_type_derive = { path = "./openapi_type_derive" }
|
||||
|
|
20
openapi_type/Cargo.toml
Normal file
20
openapi_type/Cargo.toml
Normal file
|
@ -0,0 +1,20 @@
|
|||
# -*- eval: (cargo-minor-mode 1) -*-
|
||||
|
||||
[package]
|
||||
workspace = ".."
|
||||
name = "openapi_type"
|
||||
version = "0.1.0-dev"
|
||||
authors = ["Dominic Meiser <git@msrd0.de>"]
|
||||
edition = "2018"
|
||||
description = "OpenAPI type information for Rust structs and enums"
|
||||
keywords = ["openapi", "type"]
|
||||
license = "Apache-2.0"
|
||||
repository = "https://gitlab.com/msrd0/gotham-restful/-/tree/master/openapi_type"
|
||||
|
||||
[dependencies]
|
||||
indexmap = "1.6"
|
||||
openapi_type_derive = "0.1.0-dev"
|
||||
openapiv3 = "=0.3.2"
|
||||
|
||||
[dev-dependencies]
|
||||
trybuild = "1.0"
|
76
openapi_type/src/lib.rs
Normal file
76
openapi_type/src/lib.rs
Normal file
|
@ -0,0 +1,76 @@
|
|||
#![warn(missing_debug_implementations, rust_2018_idioms)]
|
||||
#![forbid(unsafe_code)]
|
||||
#![cfg_attr(feature = "cargo-clippy", allow(clippy::tabs_in_doc_comments))]
|
||||
/*!
|
||||
TODO
|
||||
*/
|
||||
|
||||
pub use indexmap;
|
||||
pub use openapi_type_derive::OpenapiType;
|
||||
pub use openapiv3 as openapi;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use openapi::{Schema, SchemaData, SchemaKind};
|
||||
|
||||
// TODO update the documentation
|
||||
/**
|
||||
This struct needs to be available for every type that can be part of an OpenAPI Spec. It is
|
||||
already implemented for primitive types, String, Vec, Option and the like. To have it available
|
||||
for your type, simply derive from [OpenapiType].
|
||||
*/
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct OpenapiSchema {
|
||||
/// The name of this schema. If it is None, the schema will be inlined.
|
||||
pub name: Option<String>,
|
||||
/// Whether this particular schema is nullable. Note that there is no guarantee that this will
|
||||
/// make it into the final specification, it might just be interpreted as a hint to make it
|
||||
/// an optional parameter.
|
||||
pub nullable: bool,
|
||||
/// The actual OpenAPI schema.
|
||||
pub schema: SchemaKind,
|
||||
/// Other schemas that this schema depends on. They will be included in the final OpenAPI Spec
|
||||
/// along with this schema.
|
||||
pub dependencies: IndexMap<String, OpenapiSchema>
|
||||
}
|
||||
|
||||
impl OpenapiSchema {
|
||||
/// Create a new schema that has no name.
|
||||
pub fn new(schema: SchemaKind) -> Self {
|
||||
Self {
|
||||
name: None,
|
||||
nullable: false,
|
||||
schema,
|
||||
dependencies: IndexMap::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert this schema to a [Schema] that can be serialized to the OpenAPI Spec.
|
||||
pub fn into_schema(self) -> Schema {
|
||||
Schema {
|
||||
schema_data: SchemaData {
|
||||
nullable: self.nullable,
|
||||
title: self.name,
|
||||
..Default::default()
|
||||
},
|
||||
schema_kind: self.schema
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
This trait needs to be implemented by every type that is being used in the OpenAPI Spec. It gives
|
||||
access to the [OpenapiSchema] of this type. It is provided for primitive types, String and the
|
||||
like. For use on your own types, there is a derive macro:
|
||||
|
||||
```
|
||||
# #[macro_use] extern crate openapi_type_derive;
|
||||
#
|
||||
#[derive(OpenapiType)]
|
||||
struct MyResponse {
|
||||
message: String
|
||||
}
|
||||
```
|
||||
*/
|
||||
pub trait OpenapiType {
|
||||
fn schema() -> OpenapiSchema;
|
||||
}
|
12
openapi_type/tests/fail/generics_not_openapitype.rs
Normal file
12
openapi_type/tests/fail/generics_not_openapitype.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
use openapi_type::OpenapiType;
|
||||
|
||||
#[derive(OpenapiType)]
|
||||
struct Foo<T> {
|
||||
bar: T
|
||||
}
|
||||
|
||||
struct Bar;
|
||||
|
||||
fn main() {
|
||||
<Foo<Bar>>::schema();
|
||||
}
|
21
openapi_type/tests/fail/generics_not_openapitype.stderr
Normal file
21
openapi_type/tests/fail/generics_not_openapitype.stderr
Normal file
|
@ -0,0 +1,21 @@
|
|||
error[E0599]: no function or associated item named `schema` found for struct `Foo<Bar>` in the current scope
|
||||
--> $DIR/generics_not_openapitype.rs:11:14
|
||||
|
|
||||
4 | struct Foo<T> {
|
||||
| -------------
|
||||
| |
|
||||
| function or associated item `schema` not found for this
|
||||
| doesn't satisfy `Foo<Bar>: OpenapiType`
|
||||
...
|
||||
8 | struct Bar;
|
||||
| ----------- doesn't satisfy `Bar: OpenapiType`
|
||||
...
|
||||
11 | <Foo<Bar>>::schema();
|
||||
| ^^^^^^ function or associated item not found in `Foo<Bar>`
|
||||
|
|
||||
= note: the method `schema` exists but the following trait bounds were not satisfied:
|
||||
`Bar: OpenapiType`
|
||||
which is required by `Foo<Bar>: OpenapiType`
|
||||
= help: items from traits can only be used if the trait is implemented and in scope
|
||||
= note: the following trait defines an item `schema`, perhaps you need to implement it:
|
||||
candidate #1: `OpenapiType`
|
6
openapi_type/tests/pass/unit_struct.rs
Normal file
6
openapi_type/tests/pass/unit_struct.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
use openapi_type_derive::OpenapiType;
|
||||
|
||||
#[derive(OpenapiType)]
|
||||
struct Foo;
|
||||
|
||||
fn main() {}
|
8
openapi_type/tests/trybuild.rs
Normal file
8
openapi_type/tests/trybuild.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
use trybuild::TestCases;
|
||||
|
||||
#[test]
|
||||
fn trybuild() {
|
||||
let t = TestCases::new();
|
||||
t.pass("tests/pass/*.rs");
|
||||
t.compile_fail("tests/fail/*.rs");
|
||||
}
|
22
openapi_type_derive/Cargo.toml
Normal file
22
openapi_type_derive/Cargo.toml
Normal file
|
@ -0,0 +1,22 @@
|
|||
# -*- eval: (cargo-minor-mode 1) -*-
|
||||
|
||||
[package]
|
||||
workspace = ".."
|
||||
name = "openapi_type_derive"
|
||||
version = "0.1.0-dev"
|
||||
authors = ["Dominic Meiser <git@msrd0.de>"]
|
||||
edition = "2018"
|
||||
description = "Implementation detail of the openapi_type crate"
|
||||
license = "Apache-2.0"
|
||||
repository = "https://gitlab.com/msrd0/gotham-restful/-/tree/master/openapi_type_derive"
|
||||
|
||||
# tests are done using trybuild exclusively
|
||||
autotests = false
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
syn = "1.0"
|
93
openapi_type_derive/src/lib.rs
Normal file
93
openapi_type_derive/src/lib.rs
Normal file
|
@ -0,0 +1,93 @@
|
|||
#![warn(missing_debug_implementations, rust_2018_idioms)]
|
||||
#![deny(broken_intra_doc_links)]
|
||||
#![forbid(unsafe_code)]
|
||||
//! This crate defines the macros for `#[derive(OpenapiType)]`.
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
parse_macro_input, spanned::Spanned as _, Data, DataEnum, DataStruct, DataUnion, DeriveInput, TraitBound,
|
||||
TraitBoundModifier, TypeParamBound
|
||||
};
|
||||
|
||||
#[proc_macro_derive(OpenapiType)]
|
||||
pub fn derive_openapi_type(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input);
|
||||
expand_openapi_type(input).unwrap_or_else(|err| err.to_compile_error()).into()
|
||||
}
|
||||
|
||||
macro_rules! path {
|
||||
(:: $($segment:ident)::*) => {
|
||||
path!(@private Some(Default::default()), $($segment),*)
|
||||
};
|
||||
($($segment:ident)::*) => {
|
||||
path!(@private None, $($segment),*)
|
||||
};
|
||||
(@private $leading_colon:expr, $($segment:ident),*) => {
|
||||
{
|
||||
#[allow(unused_mut)]
|
||||
let mut segments: ::syn::punctuated::Punctuated<::syn::PathSegment, _> = Default::default();
|
||||
$(
|
||||
segments.push(::syn::PathSegment {
|
||||
ident: ::proc_macro2::Ident::new(stringify!($segment), ::proc_macro2::Span::call_site()),
|
||||
arguments: Default::default()
|
||||
});
|
||||
)*
|
||||
::syn::Path {
|
||||
leading_colon: $leading_colon,
|
||||
segments
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn expand_openapi_type(mut input: DeriveInput) -> syn::Result<TokenStream2> {
|
||||
let ident = &input.ident;
|
||||
|
||||
// prepare the generics - all impl generics will get `OpenapiType` requirement
|
||||
let (impl_generics, ty_generics, where_clause) = {
|
||||
let generics = &mut input.generics;
|
||||
generics.type_params_mut().for_each(|param| {
|
||||
param.colon_token.get_or_insert_with(Default::default);
|
||||
param.bounds.push(TypeParamBound::Trait(TraitBound {
|
||||
paren_token: None,
|
||||
modifier: TraitBoundModifier::None,
|
||||
lifetimes: None,
|
||||
path: path!(::openapi_type::OpenapiType)
|
||||
}))
|
||||
});
|
||||
generics.split_for_impl()
|
||||
};
|
||||
|
||||
// parse the input data
|
||||
match &input.data {
|
||||
Data::Struct(strukt) => parse_struct(strukt)?,
|
||||
Data::Enum(inum) => parse_enum(inum)?,
|
||||
Data::Union(union) => parse_union(union)?
|
||||
};
|
||||
|
||||
// generate the impl code
|
||||
Ok(quote! {
|
||||
impl #impl_generics ::openapi_type::OpenapiType for #ident #ty_generics #where_clause {
|
||||
fn schema() -> ::openapi_type::OpenapiSchema {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_struct(_strukt: &DataStruct) -> syn::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_enum(_inum: &DataEnum) -> syn::Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn parse_union(union: &DataUnion) -> syn::Result<()> {
|
||||
Err(syn::Error::new(
|
||||
union.union_token.span(),
|
||||
"#[derive(OpenapiType)] cannot be used on unions"
|
||||
))
|
||||
}
|
Loading…
Add table
Reference in a new issue