mirror of
https://gitlab.com/msrd0/gotham-restful.git
synced 2025-02-22 20:52:27 +00:00
move openapi operation extraction code into its own mod
This commit is contained in:
parent
01f818e268
commit
9fd0bceaf4
3 changed files with 227 additions and 235 deletions
|
@ -3,5 +3,6 @@ const SECURITY_NAME : &str = "authToken";
|
|||
|
||||
pub mod builder;
|
||||
pub mod handler;
|
||||
pub mod operation;
|
||||
pub mod router;
|
||||
pub mod types;
|
||||
|
|
217
gotham_restful/src/openapi/operation.rs
Normal file
217
gotham_restful/src/openapi/operation.rs
Normal file
|
@ -0,0 +1,217 @@
|
|||
use crate::{
|
||||
resource::*,
|
||||
result::*,
|
||||
OpenapiSchema,
|
||||
OpenapiType,
|
||||
RequestBody
|
||||
};
|
||||
use super::SECURITY_NAME;
|
||||
use indexmap::IndexMap;
|
||||
use mime::{Mime, STAR_STAR};
|
||||
use openapiv3::{
|
||||
MediaType, Operation, Parameter, ParameterData, ParameterSchemaOrContent, ReferenceOr,
|
||||
ReferenceOr::Item, RequestBody as OARequestBody, Response, Responses, Schema, SchemaKind,
|
||||
StatusCode, Type
|
||||
};
|
||||
|
||||
|
||||
#[derive(Default)]
|
||||
struct OperationParams<'a>
|
||||
{
|
||||
path_params : Vec<&'a str>,
|
||||
query_params : Option<OpenapiSchema>
|
||||
}
|
||||
|
||||
impl<'a> OperationParams<'a>
|
||||
{
|
||||
fn add_path_params(&self, params : &mut Vec<ReferenceOr<Parameter>>)
|
||||
{
|
||||
for param in &self.path_params
|
||||
{
|
||||
params.push(Item(Parameter::Path {
|
||||
parameter_data: ParameterData {
|
||||
name: (*param).to_string(),
|
||||
description: None,
|
||||
required: true,
|
||||
deprecated: None,
|
||||
format: ParameterSchemaOrContent::Schema(Item(String::schema().into_schema())),
|
||||
example: None,
|
||||
examples: IndexMap::new()
|
||||
},
|
||||
style: Default::default(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
fn add_query_params(self, params : &mut Vec<ReferenceOr<Parameter>>)
|
||||
{
|
||||
let query_params = match self.query_params {
|
||||
Some(qp) => qp.schema,
|
||||
None => return
|
||||
};
|
||||
let query_params = match query_params {
|
||||
SchemaKind::Type(Type::Object(ty)) => ty,
|
||||
_ => panic!("Query Parameters needs to be a plain struct")
|
||||
};
|
||||
for (name, schema) in query_params.properties
|
||||
{
|
||||
let required = query_params.required.contains(&name);
|
||||
params.push(Item(Parameter::Query {
|
||||
parameter_data: ParameterData {
|
||||
name,
|
||||
description: None,
|
||||
required,
|
||||
deprecated: None,
|
||||
format: ParameterSchemaOrContent::Schema(schema.unbox()),
|
||||
example: None,
|
||||
examples: IndexMap::new()
|
||||
},
|
||||
allow_reserved: false,
|
||||
style: Default::default(),
|
||||
allow_empty_value: None
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
fn into_params(self) -> Vec<ReferenceOr<Parameter>>
|
||||
{
|
||||
let mut params : Vec<ReferenceOr<Parameter>> = Vec::new();
|
||||
self.add_path_params(&mut params);
|
||||
self.add_query_params(&mut params);
|
||||
params
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OperationDescription<'a>
|
||||
{
|
||||
operation_id : Option<String>,
|
||||
default_status : crate::StatusCode,
|
||||
accepted_types : Option<Vec<Mime>>,
|
||||
schema : ReferenceOr<Schema>,
|
||||
params : OperationParams<'a>,
|
||||
body_schema : Option<ReferenceOr<Schema>>,
|
||||
supported_types : Option<Vec<Mime>>,
|
||||
requires_auth : bool
|
||||
}
|
||||
|
||||
impl<'a> OperationDescription<'a>
|
||||
{
|
||||
pub fn new<Handler : ResourceMethod>(schema : ReferenceOr<Schema>) -> Self
|
||||
{
|
||||
Self {
|
||||
operation_id: Handler::operation_id(),
|
||||
default_status: Handler::Res::default_status(),
|
||||
accepted_types: Handler::Res::accepted_types(),
|
||||
schema,
|
||||
params: Default::default(),
|
||||
body_schema: None,
|
||||
supported_types: None,
|
||||
requires_auth: Handler::wants_auth()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_path_params(mut self, params : Vec<&'a str>) -> Self
|
||||
{
|
||||
self.params.path_params = params;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_query_params(mut self, params : OpenapiSchema) -> Self
|
||||
{
|
||||
self.params.query_params = Some(params);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_body<Body : RequestBody>(mut self, schema : ReferenceOr<Schema>) -> Self
|
||||
{
|
||||
self.body_schema = Some(schema);
|
||||
self.supported_types = Body::supported_types();
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
fn schema_to_content(types : Vec<Mime>, schema : ReferenceOr<Schema>) -> IndexMap<String, MediaType>
|
||||
{
|
||||
let mut content : IndexMap<String, MediaType> = IndexMap::new();
|
||||
for ty in types
|
||||
{
|
||||
content.insert(ty.to_string(), MediaType {
|
||||
schema: Some(schema.clone()),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
content
|
||||
}
|
||||
|
||||
pub fn into_operation(self) -> Operation
|
||||
{
|
||||
// this is unfortunately neccessary to prevent rust from complaining about partially moving self
|
||||
let (operation_id, default_status, accepted_types, schema, params, body_schema, supported_types, requires_auth) = (
|
||||
self.operation_id, self.default_status, self.accepted_types, self.schema, self.params, self.body_schema, self.supported_types, self.requires_auth);
|
||||
|
||||
let content = Self::schema_to_content(accepted_types.unwrap_or_else(|| vec![STAR_STAR]), schema);
|
||||
|
||||
let mut responses : IndexMap<StatusCode, ReferenceOr<Response>> = IndexMap::new();
|
||||
responses.insert(StatusCode::Code(default_status.as_u16()), Item(Response {
|
||||
description: default_status.canonical_reason().map(|d| d.to_string()).unwrap_or_default(),
|
||||
content,
|
||||
..Default::default()
|
||||
}));
|
||||
|
||||
let request_body = body_schema.map(|schema| Item(OARequestBody {
|
||||
description: None,
|
||||
content: Self::schema_to_content(supported_types.unwrap_or_else(|| vec![STAR_STAR]), schema),
|
||||
required: true
|
||||
}));
|
||||
|
||||
let mut security = Vec::new();
|
||||
if requires_auth
|
||||
{
|
||||
let mut sec = IndexMap::new();
|
||||
sec.insert(SECURITY_NAME.to_owned(), Vec::new());
|
||||
security.push(sec);
|
||||
}
|
||||
|
||||
Operation {
|
||||
tags: Vec::new(),
|
||||
operation_id,
|
||||
parameters: params.into_params(),
|
||||
request_body,
|
||||
responses: Responses {
|
||||
default: None,
|
||||
responses
|
||||
},
|
||||
deprecated: false,
|
||||
security,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test
|
||||
{
|
||||
use crate::ResourceResult;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn no_content_schema_to_content()
|
||||
{
|
||||
let types = NoContent::accepted_types();
|
||||
let schema = <NoContent as OpenapiType>::schema();
|
||||
let content = OperationDescription::schema_to_content(types.unwrap_or_else(|| vec![STAR_STAR]), Item(schema.into_schema()));
|
||||
assert!(content.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn raw_schema_to_content()
|
||||
{
|
||||
let types = Raw::<&str>::accepted_types();
|
||||
let schema = <Raw<&str> as OpenapiType>::schema();
|
||||
let content = OperationDescription::schema_to_content(types.unwrap_or_else(|| vec![STAR_STAR]), Item(schema.into_schema()));
|
||||
assert_eq!(content.len(), 1);
|
||||
let json = serde_json::to_string(&content.values().nth(0).unwrap()).unwrap();
|
||||
assert_eq!(json, r#"{"schema":{"type":"string","format":"binary"}}"#);
|
||||
}
|
||||
}
|
|
@ -1,23 +1,13 @@
|
|||
use crate::{
|
||||
resource::*,
|
||||
result::*,
|
||||
routing::*,
|
||||
OpenapiSchema,
|
||||
OpenapiType,
|
||||
RequestBody
|
||||
};
|
||||
use super::{builder::OpenapiBuilder, handler::OpenapiHandler, SECURITY_NAME};
|
||||
use super::{builder::OpenapiBuilder, handler::OpenapiHandler, operation::OperationDescription};
|
||||
use gotham::{
|
||||
pipeline::chain::PipelineHandleChain,
|
||||
router::builder::*
|
||||
};
|
||||
use indexmap::IndexMap;
|
||||
use mime::{Mime, STAR_STAR};
|
||||
use openapiv3::{
|
||||
MediaType, Operation, Parameter, ParameterData, ParameterSchemaOrContent, ReferenceOr,
|
||||
ReferenceOr::Item, RequestBody as OARequestBody, Response, Responses, Schema, SchemaKind,
|
||||
StatusCode, Type
|
||||
};
|
||||
use std::panic::RefUnwindSafe;
|
||||
|
||||
/// This trait adds the `get_openapi` method to an OpenAPI-aware router.
|
||||
|
@ -26,150 +16,6 @@ pub trait GetOpenapi
|
|||
fn get_openapi(&mut self, path : &str);
|
||||
}
|
||||
|
||||
fn schema_to_content(types : Vec<Mime>, schema : ReferenceOr<Schema>) -> IndexMap<String, MediaType>
|
||||
{
|
||||
let mut content : IndexMap<String, MediaType> = IndexMap::new();
|
||||
for ty in types
|
||||
{
|
||||
content.insert(ty.to_string(), MediaType {
|
||||
schema: Some(schema.clone()),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
content
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct OperationParams<'a>
|
||||
{
|
||||
path_params : Vec<&'a str>,
|
||||
query_params : Option<OpenapiSchema>
|
||||
}
|
||||
|
||||
impl<'a> OperationParams<'a>
|
||||
{
|
||||
fn new(path_params : Vec<&'a str>, query_params : Option<OpenapiSchema>) -> Self
|
||||
{
|
||||
Self { path_params, query_params }
|
||||
}
|
||||
|
||||
fn from_path_params(path_params : Vec<&'a str>) -> Self
|
||||
{
|
||||
Self::new(path_params, None)
|
||||
}
|
||||
|
||||
fn from_query_params(query_params : OpenapiSchema) -> Self
|
||||
{
|
||||
Self::new(Vec::new(), Some(query_params))
|
||||
}
|
||||
|
||||
fn add_path_params(&self, params : &mut Vec<ReferenceOr<Parameter>>)
|
||||
{
|
||||
for param in &self.path_params
|
||||
{
|
||||
params.push(Item(Parameter::Path {
|
||||
parameter_data: ParameterData {
|
||||
name: (*param).to_string(),
|
||||
description: None,
|
||||
required: true,
|
||||
deprecated: None,
|
||||
format: ParameterSchemaOrContent::Schema(Item(String::schema().into_schema())),
|
||||
example: None,
|
||||
examples: IndexMap::new()
|
||||
},
|
||||
style: Default::default(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
fn add_query_params(self, params : &mut Vec<ReferenceOr<Parameter>>)
|
||||
{
|
||||
let query_params = match self.query_params {
|
||||
Some(qp) => qp.schema,
|
||||
None => return
|
||||
};
|
||||
let query_params = match query_params {
|
||||
SchemaKind::Type(Type::Object(ty)) => ty,
|
||||
_ => panic!("Query Parameters needs to be a plain struct")
|
||||
};
|
||||
for (name, schema) in query_params.properties
|
||||
{
|
||||
let required = query_params.required.contains(&name);
|
||||
params.push(Item(Parameter::Query {
|
||||
parameter_data: ParameterData {
|
||||
name,
|
||||
description: None,
|
||||
required,
|
||||
deprecated: None,
|
||||
format: ParameterSchemaOrContent::Schema(schema.unbox()),
|
||||
example: None,
|
||||
examples: IndexMap::new()
|
||||
},
|
||||
allow_reserved: false,
|
||||
style: Default::default(),
|
||||
allow_empty_value: None
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
fn into_params(self) -> Vec<ReferenceOr<Parameter>>
|
||||
{
|
||||
let mut params : Vec<ReferenceOr<Parameter>> = Vec::new();
|
||||
self.add_path_params(&mut params);
|
||||
self.add_query_params(&mut params);
|
||||
params
|
||||
}
|
||||
}
|
||||
|
||||
fn new_operation(
|
||||
operation_id : Option<String>,
|
||||
default_status : crate::StatusCode,
|
||||
accepted_types : Option<Vec<Mime>>,
|
||||
schema : ReferenceOr<Schema>,
|
||||
params : OperationParams,
|
||||
body_schema : Option<ReferenceOr<Schema>>,
|
||||
supported_types : Option<Vec<Mime>>,
|
||||
requires_auth : bool
|
||||
) -> Operation
|
||||
{
|
||||
let content = schema_to_content(accepted_types.unwrap_or_else(|| vec![STAR_STAR]), schema);
|
||||
|
||||
let mut responses : IndexMap<StatusCode, ReferenceOr<Response>> = IndexMap::new();
|
||||
responses.insert(StatusCode::Code(default_status.as_u16()), Item(Response {
|
||||
description: default_status.canonical_reason().map(|d| d.to_string()).unwrap_or_default(),
|
||||
content,
|
||||
..Default::default()
|
||||
}));
|
||||
|
||||
let request_body = body_schema.map(|schema| Item(OARequestBody {
|
||||
description: None,
|
||||
content: schema_to_content(supported_types.unwrap_or_else(|| vec![STAR_STAR]), schema),
|
||||
required: true
|
||||
}));
|
||||
|
||||
let mut security = Vec::new();
|
||||
if requires_auth
|
||||
{
|
||||
let mut sec = IndexMap::new();
|
||||
sec.insert(SECURITY_NAME.to_owned(), Vec::new());
|
||||
security.push(sec);
|
||||
}
|
||||
|
||||
Operation {
|
||||
tags: Vec::new(),
|
||||
operation_id,
|
||||
parameters: params.into_params(),
|
||||
request_body,
|
||||
responses: Responses {
|
||||
default: None,
|
||||
responses
|
||||
},
|
||||
deprecated: false,
|
||||
security,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! implOpenapiRouter {
|
||||
($implType:ident) => {
|
||||
|
||||
|
@ -206,7 +52,7 @@ macro_rules! implOpenapiRouter {
|
|||
|
||||
let path = format!("/{}", &self.1);
|
||||
let mut item = (self.0).1.remove_path(&path);
|
||||
item.get = Some(new_operation(Handler::operation_id(), Handler::Res::default_status(), Handler::Res::accepted_types(), schema, OperationParams::default(), None, None, Handler::wants_auth()));
|
||||
item.get = Some(OperationDescription::new::<Handler>(schema).into_operation());
|
||||
(self.0).1.add_path(path, item);
|
||||
|
||||
(&mut *(self.0).0, self.1).read_all::<Handler>()
|
||||
|
@ -218,7 +64,7 @@ macro_rules! implOpenapiRouter {
|
|||
|
||||
let path = format!("/{}/{{id}}", &self.1);
|
||||
let mut item = (self.0).1.remove_path(&path);
|
||||
item.get = Some(new_operation(Handler::operation_id(), Handler::Res::default_status(), Handler::Res::accepted_types(), schema, OperationParams::from_path_params(vec!["id"]), None, None, Handler::wants_auth()));
|
||||
item.get = Some(OperationDescription::new::<Handler>(schema).with_path_params(vec!["id"]).into_operation());
|
||||
(self.0).1.add_path(path, item);
|
||||
|
||||
(&mut *(self.0).0, self.1).read::<Handler>()
|
||||
|
@ -230,7 +76,7 @@ macro_rules! implOpenapiRouter {
|
|||
|
||||
let path = format!("/{}/search", &self.1);
|
||||
let mut item = (self.0).1.remove_path(&self.1);
|
||||
item.get = Some(new_operation(Handler::operation_id(), Handler::Res::default_status(), Handler::Res::accepted_types(), schema, OperationParams::from_query_params(Handler::Query::schema()), None, None, Handler::wants_auth()));
|
||||
item.get = Some(OperationDescription::new::<Handler>(schema).with_query_params(Handler::Query::schema()).into_operation());
|
||||
(self.0).1.add_path(path, item);
|
||||
|
||||
(&mut *(self.0).0, self.1).search::<Handler>()
|
||||
|
@ -246,7 +92,7 @@ macro_rules! implOpenapiRouter {
|
|||
|
||||
let path = format!("/{}", &self.1);
|
||||
let mut item = (self.0).1.remove_path(&path);
|
||||
item.post = Some(new_operation(Handler::operation_id(), Handler::Res::default_status(), Handler::Res::accepted_types(), schema, OperationParams::default(), Some(body_schema), Handler::Body::supported_types(), Handler::wants_auth()));
|
||||
item.post = Some(OperationDescription::new::<Handler>(schema).with_body::<Handler::Body>(body_schema).into_operation());
|
||||
(self.0).1.add_path(path, item);
|
||||
|
||||
(&mut *(self.0).0, self.1).create::<Handler>()
|
||||
|
@ -262,7 +108,7 @@ macro_rules! implOpenapiRouter {
|
|||
|
||||
let path = format!("/{}", &self.1);
|
||||
let mut item = (self.0).1.remove_path(&path);
|
||||
item.put = Some(new_operation(Handler::operation_id(), Handler::Res::default_status(), Handler::Res::accepted_types(), schema, OperationParams::default(), Some(body_schema), Handler::Body::supported_types(), Handler::wants_auth()));
|
||||
item.put = Some(OperationDescription::new::<Handler>(schema).with_body::<Handler::Body>(body_schema).into_operation());
|
||||
(self.0).1.add_path(path, item);
|
||||
|
||||
(&mut *(self.0).0, self.1).update_all::<Handler>()
|
||||
|
@ -278,7 +124,7 @@ macro_rules! implOpenapiRouter {
|
|||
|
||||
let path = format!("/{}/{{id}}", &self.1);
|
||||
let mut item = (self.0).1.remove_path(&path);
|
||||
item.put = Some(new_operation(Handler::operation_id(), Handler::Res::default_status(), Handler::Res::accepted_types(), schema, OperationParams::from_path_params(vec!["id"]), Some(body_schema), Handler::Body::supported_types(), Handler::wants_auth()));
|
||||
item.put = Some(OperationDescription::new::<Handler>(schema).with_path_params(vec!["id"]).with_body::<Handler::Body>(body_schema).into_operation());
|
||||
(self.0).1.add_path(path, item);
|
||||
|
||||
(&mut *(self.0).0, self.1).update::<Handler>()
|
||||
|
@ -290,7 +136,7 @@ macro_rules! implOpenapiRouter {
|
|||
|
||||
let path = format!("/{}", &self.1);
|
||||
let mut item = (self.0).1.remove_path(&path);
|
||||
item.delete = Some(new_operation(Handler::operation_id(), Handler::Res::default_status(), Handler::Res::accepted_types(), schema, OperationParams::default(), None, None, Handler::wants_auth()));
|
||||
item.delete = Some(OperationDescription::new::<Handler>(schema).into_operation());
|
||||
(self.0).1.add_path(path, item);
|
||||
|
||||
(&mut *(self.0).0, self.1).delete_all::<Handler>()
|
||||
|
@ -302,7 +148,7 @@ macro_rules! implOpenapiRouter {
|
|||
|
||||
let path = format!("/{}/{{id}}", &self.1);
|
||||
let mut item = (self.0).1.remove_path(&path);
|
||||
item.delete = Some(new_operation(Handler::operation_id(), Handler::Res::default_status(), Handler::Res::accepted_types(), schema, OperationParams::from_path_params(vec!["id"]), None, None, Handler::wants_auth()));
|
||||
item.delete = Some(OperationDescription::new::<Handler>(schema).with_path_params(vec!["id"]).into_operation());
|
||||
(self.0).1.add_path(path, item);
|
||||
|
||||
(&mut *(self.0).0, self.1).delete::<Handler>()
|
||||
|
@ -314,75 +160,3 @@ macro_rules! implOpenapiRouter {
|
|||
|
||||
implOpenapiRouter!(RouterBuilder);
|
||||
implOpenapiRouter!(ScopeBuilder);
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test
|
||||
{
|
||||
use crate::ResourceResult;
|
||||
use super::*;
|
||||
|
||||
#[derive(OpenapiType)]
|
||||
#[allow(dead_code)]
|
||||
struct QueryParams
|
||||
{
|
||||
id : isize
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn params_empty()
|
||||
{
|
||||
let op_params = OperationParams::default();
|
||||
let params = op_params.into_params();
|
||||
assert!(params.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn params_from_path_params()
|
||||
{
|
||||
let name = "id";
|
||||
let op_params = OperationParams::from_path_params(vec![name]);
|
||||
let params = op_params.into_params();
|
||||
let json = serde_json::to_string(¶ms).unwrap();
|
||||
assert_eq!(json, format!(r#"[{{"in":"path","name":"{}","required":true,"schema":{{"type":"string"}},"style":"simple"}}]"#, name));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn params_from_query_params()
|
||||
{
|
||||
let op_params = OperationParams::from_query_params(QueryParams::schema());
|
||||
let params = op_params.into_params();
|
||||
let json = serde_json::to_string(¶ms).unwrap();
|
||||
assert_eq!(json, r#"[{"in":"query","name":"id","required":true,"schema":{"type":"integer"},"style":"form"}]"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn params_both()
|
||||
{
|
||||
let name = "id";
|
||||
let op_params = OperationParams::new(vec![name], Some(QueryParams::schema()));
|
||||
let params = op_params.into_params();
|
||||
let json = serde_json::to_string(¶ms).unwrap();
|
||||
assert_eq!(json, format!(r#"[{{"in":"path","name":"{}","required":true,"schema":{{"type":"string"}},"style":"simple"}},{{"in":"query","name":"id","required":true,"schema":{{"type":"integer"}},"style":"form"}}]"#, name));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_content_schema_to_content()
|
||||
{
|
||||
let types = NoContent::accepted_types();
|
||||
let schema = <NoContent as OpenapiType>::schema();
|
||||
let content = schema_to_content(types.unwrap_or_else(|| vec![STAR_STAR]), Item(schema.into_schema()));
|
||||
assert!(content.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn raw_schema_to_content()
|
||||
{
|
||||
let types = Raw::<&str>::accepted_types();
|
||||
let schema = <Raw<&str> as OpenapiType>::schema();
|
||||
let content = schema_to_content(types.unwrap_or_else(|| vec![STAR_STAR]), Item(schema.into_schema()));
|
||||
assert_eq!(content.len(), 1);
|
||||
let json = serde_json::to_string(&content.values().nth(0).unwrap()).unwrap();
|
||||
assert_eq!(json, r#"{"schema":{"type":"string","format":"binary"}}"#);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue