1
0
Fork 0
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:
Dominic 2021-03-07 19:05:25 +01:00
parent 2251c29d7b
commit 90870e3b6a
Signed by: msrd0
GPG key ID: DCC8C247452E98F9
9 changed files with 261 additions and 1 deletions

View file

@ -1,7 +1,7 @@
# -*- eval: (cargo-minor-mode 1) -*- # -*- eval: (cargo-minor-mode 1) -*-
[workspace] [workspace]
members = [".", "./derive", "./example"] members = [".", "./derive", "./example", "./openapi_type", "./openapi_type_derive"]
[package] [package]
name = "gotham_restful" name = "gotham_restful"
@ -76,3 +76,5 @@ features = ["full"]
[patch.crates-io] [patch.crates-io]
gotham_restful = { path = "." } gotham_restful = { path = "." }
gotham_restful_derive = { path = "./derive" } gotham_restful_derive = { path = "./derive" }
openapi_type = { path = "./openapi_type" }
openapi_type_derive = { path = "./openapi_type_derive" }

20
openapi_type/Cargo.toml Normal file
View 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
View 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;
}

View file

@ -0,0 +1,12 @@
use openapi_type::OpenapiType;
#[derive(OpenapiType)]
struct Foo<T> {
bar: T
}
struct Bar;
fn main() {
<Foo<Bar>>::schema();
}

View 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`

View file

@ -0,0 +1,6 @@
use openapi_type_derive::OpenapiType;
#[derive(OpenapiType)]
struct Foo;
fn main() {}

View 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");
}

View 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"

View 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"
))
}