Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix negative value parsing on schema attributes #1031

Merged
merged 1 commit into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 8 additions & 35 deletions utoipa-gen/src/component/features.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
use std::{fmt::Display, mem, str::FromStr};
use std::{fmt::Display, mem};

use proc_macro2::{Ident, TokenStream};
use quote::{quote, ToTokens};
use syn::{parse::ParseStream, LitFloat, LitInt};
use syn::parse::ParseStream;

use crate::{
as_tokens_or_diagnostics, parse_utils, schema_type::SchemaType, Diagnostics, OptionExt,
ToTokensDiagnostics,
as_tokens_or_diagnostics, schema_type::SchemaType, Diagnostics, OptionExt, ToTokensDiagnostics,
};

use self::validators::{AboveZeroF64, AboveZeroUsize, IsNumber, IsString, IsVec, ValidatorChain};
Expand All @@ -17,32 +16,6 @@ pub mod attributes;
pub mod validation;
pub mod validators;

/// Parse `LitInt` from parse stream
fn parse_integer<T: FromStr + Display>(input: ParseStream) -> syn::Result<T>
where
<T as FromStr>::Err: Display,
{
parse_utils::parse_next(input, || input.parse::<LitInt>()?.base10_parse())
}

/// Parse any `number`. Tries to parse `LitInt` or `LitFloat` from parse stream.
fn parse_number<T>(input: ParseStream) -> syn::Result<T>
where
T: FromStr,
<T as FromStr>::Err: Display,
{
parse_utils::parse_next(input, || {
let lookup = input.lookahead1();
if lookup.peek(LitInt) {
input.parse::<LitInt>()?.base10_parse()
} else if lookup.peek(LitFloat) {
input.parse::<LitFloat>()?.base10_parse()
} else {
Err(lookup.error())
}
})
}

pub trait FeatureLike: Parse {
fn get_name() -> std::borrow::Cow<'static, str>
where
Expand Down Expand Up @@ -145,7 +118,7 @@ impl Feature {
pub fn validate(&self, schema_type: &SchemaType, type_tree: &TypeTree) -> Option<Diagnostics> {
match self {
Feature::MultipleOf(multiple_of) => multiple_of.validate(
ValidatorChain::new(&IsNumber(schema_type)).next(&AboveZeroF64(multiple_of.0)),
ValidatorChain::new(&IsNumber(schema_type)).next(&AboveZeroF64(&multiple_of.0)),
),
Feature::Maximum(maximum) => maximum.validate(IsNumber(schema_type)),
Feature::Minimum(minimum) => minimum.validate(IsNumber(schema_type)),
Expand All @@ -156,17 +129,17 @@ impl Feature {
exclusive_minimum.validate(IsNumber(schema_type))
}
Feature::MaxLength(max_length) => max_length.validate(
ValidatorChain::new(&IsString(schema_type)).next(&AboveZeroUsize(max_length.0)),
ValidatorChain::new(&IsString(schema_type)).next(&AboveZeroUsize(&max_length.0)),
),
Feature::MinLength(min_length) => min_length.validate(
ValidatorChain::new(&IsString(schema_type)).next(&AboveZeroUsize(min_length.0)),
ValidatorChain::new(&IsString(schema_type)).next(&AboveZeroUsize(&min_length.0)),
),
Feature::Pattern(pattern) => pattern.validate(IsString(schema_type)),
Feature::MaxItems(max_items) => max_items.validate(
ValidatorChain::new(&AboveZeroUsize(max_items.0)).next(&IsVec(type_tree)),
ValidatorChain::new(&AboveZeroUsize(&max_items.0)).next(&IsVec(type_tree)),
),
Feature::MinItems(min_items) => min_items.validate(
ValidatorChain::new(&AboveZeroUsize(min_items.0)).next(&IsVec(type_tree)),
ValidatorChain::new(&AboveZeroUsize(&min_items.0)).next(&IsVec(type_tree)),
),
unsupported => {
const SUPPORTED_VARIANTS: [&str; 10] = [
Expand Down
132 changes: 106 additions & 26 deletions utoipa-gen/src/component/features/validation.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,91 @@
use proc_macro2::{Ident, Span, TokenStream};
use quote::ToTokens;
use std::str::FromStr;

use proc_macro2::{Ident, Literal, Span, TokenStream, TokenTree};
use quote::{quote, ToTokens};
use syn::parse::ParseStream;
use syn::LitStr;

use crate::{parse_utils, Diagnostics};

use super::validators::Validator;
use super::{impl_feature, parse_integer, parse_number, Feature, Parse, Validate};
use super::{impl_feature, Feature, Parse, Validate};

#[inline]
fn from_str<T: FromStr>(number: &str, span: Span) -> syn::Result<T>
where
<T as std::str::FromStr>::Err: std::fmt::Display,
{
T::from_str(number).map_err(|error| syn::Error::new(span, error))
}

#[derive(Clone)]
#[cfg_attr(feature = "debug", derive(Debug))]
pub struct NumberValue {
minus: bool,
pub lit: Literal,
}

impl NumberValue {
pub fn try_from_str<T>(&self) -> syn::Result<T>
where
T: FromStr,
<T as std::str::FromStr>::Err: std::fmt::Display,
{
let number = if self.minus {
format!("-{}", &self.lit)
} else {
self.lit.to_string()
};

let parsed = from_str::<T>(&number, self.lit.span())?;
Ok(parsed)
}
}

impl syn::parse::Parse for NumberValue {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut minus = false;
let result = input.step(|cursor| {
let mut rest = *cursor;

while let Some((tt, next)) = rest.token_tree() {
match &tt {
TokenTree::Punct(punct) if punct.as_char() == '-' => {
minus = true;
}
TokenTree::Literal(lit) => return Ok((lit.clone(), next)),
_ => (),
}
rest = next;
}
Err(cursor.error("no `literal` value found after this point"))
})?;

Ok(Self { minus, lit: result })
}
}

impl ToTokens for NumberValue {
fn to_tokens(&self, tokens: &mut TokenStream) {
let punct = if self.minus { Some(quote! {-}) } else { None };
let lit = &self.lit;

tokens.extend(quote! {
#punct #lit
})
}
}

#[inline]
fn parse_next_number_value(input: ParseStream) -> syn::Result<NumberValue> {
use syn::parse::Parse;
parse_utils::parse_next(input, || NumberValue::parse(input))
}

impl_feature! {
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Clone)]
pub struct MultipleOf(pub(super) f64, Ident);
pub struct MultipleOf(pub(super) NumberValue, Ident);
}

impl Validate for MultipleOf {
Expand All @@ -26,7 +100,7 @@ impl Validate for MultipleOf {

impl Parse for MultipleOf {
fn parse(input: ParseStream, ident: Ident) -> syn::Result<Self> {
parse_number(input).map(|multiple_of| Self(multiple_of, ident))
parse_next_number_value(input).map(|number| Self(number, ident))
}
}

Expand All @@ -45,7 +119,7 @@ impl From<MultipleOf> for Feature {
impl_feature! {
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Clone)]
pub struct Maximum(pub(super) f64, Ident);
pub struct Maximum(pub(super) NumberValue, Ident);
}

impl Validate for Maximum {
Expand All @@ -63,7 +137,7 @@ impl Parse for Maximum {
where
Self: Sized,
{
parse_number(input).map(|maximum| Self(maximum, ident))
parse_next_number_value(input).map(|number| Self(number, ident))
}
}

Expand All @@ -82,12 +156,18 @@ impl From<Maximum> for Feature {
impl_feature! {
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Clone)]
pub struct Minimum(f64, Ident);
pub struct Minimum(NumberValue, Ident);
}

impl Minimum {
pub fn new(value: f64, span: Span) -> Self {
Self(value, Ident::new("empty", span))
Self(
NumberValue {
minus: value < 0.0,
lit: Literal::f64_suffixed(value),
},
Ident::new("empty", span),
)
}
}

Expand All @@ -108,7 +188,7 @@ impl Parse for Minimum {
where
Self: Sized,
{
parse_number(input).map(|maximum| Self(maximum, ident))
parse_next_number_value(input).map(|number| Self(number, ident))
}
}

Expand All @@ -127,7 +207,7 @@ impl From<Minimum> for Feature {
impl_feature! {
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Clone)]
pub struct ExclusiveMaximum(f64, Ident);
pub struct ExclusiveMaximum(NumberValue, Ident);
}

impl Validate for ExclusiveMaximum {
Expand All @@ -145,7 +225,7 @@ impl Parse for ExclusiveMaximum {
where
Self: Sized,
{
parse_number(input).map(|max| Self(max, ident))
parse_next_number_value(input).map(|number| Self(number, ident))
}
}

Expand All @@ -164,7 +244,7 @@ impl From<ExclusiveMaximum> for Feature {
impl_feature! {
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Clone)]
pub struct ExclusiveMinimum(f64, Ident);
pub struct ExclusiveMinimum(NumberValue, Ident);
}

impl Validate for ExclusiveMinimum {
Expand All @@ -182,7 +262,7 @@ impl Parse for ExclusiveMinimum {
where
Self: Sized,
{
parse_number(input).map(|min| Self(min, ident))
parse_next_number_value(input).map(|number| Self(number, ident))
}
}

Expand All @@ -201,7 +281,7 @@ impl From<ExclusiveMinimum> for Feature {
impl_feature! {
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Clone)]
pub struct MaxLength(pub(super) usize, Ident);
pub struct MaxLength(pub(super) NumberValue, Ident);
}

impl Validate for MaxLength {
Expand All @@ -219,7 +299,7 @@ impl Parse for MaxLength {
where
Self: Sized,
{
parse_integer(input).map(|max_length| Self(max_length, ident))
parse_next_number_value(input).map(|number| Self(number, ident))
}
}

Expand All @@ -238,7 +318,7 @@ impl From<MaxLength> for Feature {
impl_feature! {
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Clone)]
pub struct MinLength(pub(super) usize, Ident);
pub struct MinLength(pub(super) NumberValue, Ident);
}

impl Validate for MinLength {
Expand All @@ -256,7 +336,7 @@ impl Parse for MinLength {
where
Self: Sized,
{
parse_integer(input).map(|max_length| Self(max_length, ident))
parse_next_number_value(input).map(|number| Self(number, ident))
}
}

Expand Down Expand Up @@ -314,7 +394,7 @@ impl From<Pattern> for Feature {
impl_feature! {
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Clone)]
pub struct MaxItems(pub(super) usize, Ident);
pub struct MaxItems(pub(super) NumberValue, Ident);
}

impl Validate for MaxItems {
Expand All @@ -332,7 +412,7 @@ impl Parse for MaxItems {
where
Self: Sized,
{
parse_number(input).map(|max_items| Self(max_items, ident))
parse_next_number_value(input).map(|number| Self(number, ident))
}
}

Expand All @@ -351,7 +431,7 @@ impl From<MaxItems> for Feature {
impl_feature! {
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Clone)]
pub struct MinItems(pub(super) usize, Ident);
pub struct MinItems(pub(super) NumberValue, Ident);
}

impl Validate for MinItems {
Expand All @@ -369,7 +449,7 @@ impl Parse for MinItems {
where
Self: Sized,
{
parse_number(input).map(|max_items| Self(max_items, ident))
parse_next_number_value(input).map(|number| Self(number, ident))
}
}

Expand All @@ -388,15 +468,15 @@ impl From<MinItems> for Feature {
impl_feature! {
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Clone)]
pub struct MaxProperties(usize, ());
pub struct MaxProperties(NumberValue, ());
}

impl Parse for MaxProperties {
fn parse(input: ParseStream, _ident: Ident) -> syn::Result<Self>
where
Self: Sized,
{
parse_integer(input).map(|max_properties| Self(max_properties, ()))
parse_next_number_value(input).map(|number| Self(number, ()))
}
}

Expand All @@ -415,15 +495,15 @@ impl From<MaxProperties> for Feature {
impl_feature! {
#[cfg_attr(feature = "debug", derive(Debug))]
#[derive(Clone)]
pub struct MinProperties(usize, ());
pub struct MinProperties(NumberValue, ());
}

impl Parse for MinProperties {
fn parse(input: ParseStream, _ident: Ident) -> syn::Result<Self>
where
Self: Sized,
{
parse_integer(input).map(|min_properties| Self(min_properties, ()))
parse_next_number_value(input).map(|number| Self(number, ()))
}
}

Expand Down
Loading
Loading