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

feat: keep struct fields order in schema #539

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ actix-base = ["v2", "paperclip-macros/actix"]
swagger-ui = ["paperclip-actix/swagger-ui"]
rapidoc = ["paperclip-actix/rapidoc"]
path-in-definition = ["paperclip-macros/path-in-definition"]
preserve_order = ["paperclip-core/preserve_order"]

# OpenAPI support (v2 and codegen)
cli = ["env_logger", "structopt", "git2", "v2", "codegen"]
Expand Down
2 changes: 2 additions & 0 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ actix-session = { version = "0", optional = true }
actix-identity = { version = "0", optional = true }
actix-files = { version = "0", optional = true }
chrono = { version = "0.4", optional = true }
indexmap = { version = "2", optional = true }
jiff = { version = "0.1.5", optional = true }
heck = { version = "0.4", optional = true }
once_cell = "1.4"
Expand Down Expand Up @@ -57,6 +58,7 @@ nightly = ["paperclip-macros/nightly"]
v2 = ["paperclip-macros/v2"]
v3 = ["v2", "openapiv3"]
codegen = ["v2", "heck", "log"]
preserve_order = ["dep:indexmap", "indexmap/serde", "serde_json/preserve_order"]
uuid = ["uuid0"]
uuid0 = ["uuid0_dep"]
uuid1 = ["uuid1_dep"]
5 changes: 5 additions & 0 deletions core/src/v2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ use self::resolver::Resolver;
#[cfg(feature = "codegen")]
use crate::error::ValidationError;

#[cfg(feature = "preserve_order")]
pub type PropertiesMap<K, V> = indexmap::IndexMap<K, V>;
#[cfg(not(feature = "preserve_order"))]
pub type PropertiesMap<K, V> = std::collections::BTreeMap<K, V>;

#[cfg(feature = "codegen")]
impl<S: Schema + Default> ResolvableApi<S> {
/// Consumes this API schema, resolves the references and returns
Expand Down
13 changes: 8 additions & 5 deletions core/src/v2/schema.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
//! Traits used for code and spec generation.
use super::models::{
DataType, DataTypeFormat, DefaultOperationRaw, DefaultSchemaRaw, Either, Resolvable,
SecurityScheme,
use super::{
models::{
DataType, DataTypeFormat, DefaultOperationRaw, DefaultSchemaRaw, Either, Resolvable,
SecurityScheme,
},
PropertiesMap,
};

use std::collections::{BTreeMap, BTreeSet};
Expand Down Expand Up @@ -39,10 +42,10 @@ pub trait Schema: Sized {
fn additional_properties_mut(&mut self) -> Option<&mut Either<bool, Resolvable<Self>>>;

/// Map of names and schema for properties, if it's an object (`properties` field)
fn properties(&self) -> Option<&BTreeMap<String, Resolvable<Self>>>;
fn properties(&self) -> Option<&PropertiesMap<String, Resolvable<Self>>>;

/// Mutable access to `properties` field.
fn properties_mut(&mut self) -> Option<&mut BTreeMap<String, Resolvable<Self>>>;
fn properties_mut(&mut self) -> Option<&mut PropertiesMap<String, Resolvable<Self>>>;

/// Returns the required properties (if any) for this object.
fn required_properties(&self) -> Option<&BTreeSet<String>>;
Expand Down
4 changes: 2 additions & 2 deletions core/src/v3/schema.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::v2::models::Either;
use crate::v2::{models::Either, PropertiesMap};

use super::{invalid_referenceor, v2};
use std::ops::Deref;
Expand Down Expand Up @@ -65,7 +65,7 @@ fn v2_data_type_to_v3(
format: &Option<v2::DataTypeFormat>,
enum_: &[serde_json::Value],
items: &Option<Box<v2::DefaultSchemaRaw>>,
properties: &std::collections::BTreeMap<String, Box<v2::DefaultSchemaRaw>>,
properties: &PropertiesMap<String, Box<v2::DefaultSchemaRaw>>,
extra_properties: &Option<Either<bool, Box<v2::DefaultSchemaRaw>>>,
required: &std::collections::BTreeSet<String>,
) -> openapiv3::SchemaKind {
Expand Down
8 changes: 4 additions & 4 deletions macros/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ pub fn emit_v2_schema_struct(input: TokenStream) -> TokenStream {
}

#[inline]
fn properties(&self) -> Option<&std::collections::BTreeMap<String, paperclip::v2::models::Resolvable<Self>>> {
fn properties(&self) -> Option<&paperclip::v2::PropertiesMap<String, paperclip::v2::models::Resolvable<Self>>> {
if self.properties.is_empty() {
None
} else {
Expand All @@ -157,7 +157,7 @@ pub fn emit_v2_schema_struct(input: TokenStream) -> TokenStream {
}

#[inline]
fn properties_mut(&mut self) -> Option<&mut std::collections::BTreeMap<String, paperclip::v2::models::Resolvable<Self>>> {
fn properties_mut(&mut self) -> Option<&mut paperclip::v2::PropertiesMap<String, paperclip::v2::models::Resolvable<Self>>> {
if self.properties.is_empty() {
None
} else {
Expand Down Expand Up @@ -298,8 +298,8 @@ fn schema_fields(name: &Ident, is_ref: bool) -> proc_macro2::TokenStream {
));

gen.extend(quote!(
#[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
pub properties: std::collections::BTreeMap<String,
#[serde(default, skip_serializing_if = "paperclip::v2::PropertiesMap::is_empty")]
pub properties: paperclip::v2::PropertiesMap<String,
));
add_self(&mut gen);
gen.extend(quote!(>,));
Expand Down
6 changes: 3 additions & 3 deletions src/v2/codegen/emitter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ where
self.emit_struct(def, ctx)
}

/// Checks if the given definition is a simple map and returns the corresponding `BTreeMap`.
/// Checks if the given definition is a simple map and returns the corresponding `PropertiesMap`.
fn try_emit_map(
&self,
def: &E::Definition,
Expand All @@ -565,7 +565,7 @@ where
let ty = self
.build_def(&schema, ctx.clone().define(false))?
.known_type();
let map = format!("std::collections::BTreeMap<String, {}>", ty);
let map = format!("paperclip::v2::PropertiesMap<String, {}>", ty);
Ok(EmittedUnit::Known(map))
}
_ => Ok(EmittedUnit::None),
Expand Down Expand Up @@ -673,7 +673,7 @@ where
if let Some(Either::Left(true)) = def.additional_properties() {
obj.fields_mut().push(ObjectField {
name: EXTRA_PROPS_FIELD.into(),
ty_path: "std::collections::BTreeMap<String, Any>".into(),
ty_path: "paperclip::v2::PropertiesMap<String, Any>".into(),
description: None,
is_required: false,
needs_any: true,
Expand Down
8 changes: 4 additions & 4 deletions src/v2/codegen/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ where
/// Builds the method parameter type using the actual field type.
///
/// For example, if a field is `Vec<T>`, then we replace it (in builder method)
/// with `impl Iterator<Item=Into<T>>`, and if we had `BTreeMap<String, T>`,
/// with `impl Iterator<Item=Into<T>>`, and if we had `PropertiesMap<String, T>`,
/// then we replace it with `impl Iterator<Item = (String, T)>` and
/// we do this... recursively.
// FIXME: Investigate if there's a better way.
Expand All @@ -489,7 +489,7 @@ where
f.write_str("impl Iterator<Item = ")?;
self.write_builder_ty(&ty[i + 1..ty.len() - 1], req, needs_any, f)?;
f.write_str(">")?;
} else if ty[..i].ends_with("std::collections::BTreeMap") {
} else if ty[..i].ends_with("paperclip::v2::PropertiesMap") {
f.write_str("impl Iterator<Item = (String, ")?;
self.write_builder_ty(&ty[i + 9..ty.len() - 1], req, needs_any, f)?;
f.write_str(")>")?;
Expand Down Expand Up @@ -546,10 +546,10 @@ where
f.write_str("value.map(|value| ")?;
Self::write_value_map(&ty[i + 1..ty.len() - 1], f)?;
f.write_str(").collect::<Vec<_>>()")?;
} else if ty[..i].ends_with("std::collections::BTreeMap") {
} else if ty[..i].ends_with("paperclip::v2::PropertiesMap") {
f.write_str("value.map(|(key, value)| (key, ")?;
Self::write_value_map(&ty[i + 9..ty.len() - 1], f)?;
f.write_str(")).collect::<std::collections::BTreeMap<_, _>>()")?;
f.write_str(")).collect::<paperclip::v2::PropertiesMap<_, _>>()")?;
}
} else {
f.write_str("value")?;
Expand Down
6 changes: 3 additions & 3 deletions src/v2/codegen/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,8 @@ pub struct ObjectField {
/// Required fields of the "deepest" child type in the given definition.
///
/// Now, what do I mean by "deepest"? For example, if we had `Vec<Vec<Vec<T>>>`
/// or `Vec<BTreeMap<String, Vec<BTreeMap<String, T>>>>`, then "deepest" child
/// type is T (as long as it's not a `Vec` or `BTreeMap`).
/// or `Vec<PropertiesMap<String, Vec<PropertiesMap<String, T>>>>`, then "deepest" child
/// type is T (as long as it's not a `Vec` or `PropertiesMap`).
///
/// To understand why we're doing this, see `ApiObjectBuilderImpl::write_builder_ty`
/// and `ApiObjectBuilderImpl::write_value_map` functions.
Expand Down Expand Up @@ -315,7 +315,7 @@ impl ApiObject {
if ty[..i].ends_with("Vec") {
f.write_str(&ty[..=i])?;
Self::write_field_with_any(&ty[i + 1..ty.len() - 1], f)?;
} else if ty[..i].ends_with("std::collections::BTreeMap") {
} else if ty[..i].ends_with("paperclip::v2::PropertiesMap") {
f.write_str(&ty[..i + 9])?;
Self::write_field_with_any(&ty[i + 9..ty.len() - 1], f)?;
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/v2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ pub use paperclip_core::{
v2::{
models::{self, DefaultSchema, ResolvableApi},
schema::{self, Schema},
serde_json,
serde_json, PropertiesMap,
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ expression: data
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct CertificateSigningRequestSpec {
/// Extra information about the requesting user. See user.Info interface for details.
pub extra: Option<std::collections::BTreeMap<String, Vec<String>>>,
pub extra: Option<paperclip::v2::PropertiesMap<String, Vec<String>>>,
/// Group information about the requesting user. See user.Info interface for details.
pub groups: Option<Vec<String>>,
/// Base64-encoded PKCS#10 CSR data
Expand Down Expand Up @@ -49,7 +49,7 @@ impl<Request> CertificateSigningRequestSpecBuilder<Request> {
/// Extra information about the requesting user. See user.Info interface for details.
#[inline]
pub fn extra(mut self, value: impl Iterator<Item = (String, impl Iterator<Item = impl Into<String>>)>) -> Self {
self.body.extra = Some(value.map(|(key, value)| (key, value.map(|value| value.into()).collect::<Vec<_>>().into())).collect::<std::collections::BTreeMap<_, _>>().into());
self.body.extra = Some(value.map(|(key, value)| (key, value.map(|value| value.into()).collect::<Vec<_>>().into())).collect::<paperclip::v2::PropertiesMap<_, _>>().into());
self
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ pub struct ConfigMap {
pub api_version: Option<String>,
/// BinaryData contains the binary data. Each key must consist of alphanumeric characters, '-', '_' or '.'. BinaryData can contain byte sequences that are not in the UTF-8 range. The keys stored in BinaryData must not overlap with the ones in the Data field, this is enforced during validation process. Using this field will require 1.10+ apiserver and kubelet.
#[serde(rename = "binaryData")]
pub binary_data: Option<std::collections::BTreeMap<String, String>>,
pub binary_data: Option<paperclip::v2::PropertiesMap<String, String>>,
/// Data contains the configuration data. Each key must consist of alphanumeric characters, '-', '_' or '.'. Values with non-UTF-8 byte sequences must use the BinaryData field. The keys stored in Data must not overlap with the keys in the BinaryData field, this is enforced during validation process.
pub data: Option<std::collections::BTreeMap<String, String>>,
pub data: Option<paperclip::v2::PropertiesMap<String, String>>,
/// Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds
pub kind: Option<String>,
/// Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
Expand Down Expand Up @@ -94,14 +94,14 @@ impl ConfigMapBuilder {
/// BinaryData contains the binary data. Each key must consist of alphanumeric characters, '-', '_' or '.'. BinaryData can contain byte sequences that are not in the UTF-8 range. The keys stored in BinaryData must not overlap with the ones in the Data field, this is enforced during validation process. Using this field will require 1.10+ apiserver and kubelet.
#[inline]
pub fn binary_data(mut self, value: impl Iterator<Item = (String, impl Into<String>)>) -> Self {
self.body.binary_data = Some(value.map(|(key, value)| (key, value.into())).collect::<std::collections::BTreeMap<_, _>>().into());
self.body.binary_data = Some(value.map(|(key, value)| (key, value.into())).collect::<paperclip::v2::PropertiesMap<_, _>>().into());
self
}

/// Data contains the configuration data. Each key must consist of alphanumeric characters, '-', '_' or '.'. Values with non-UTF-8 byte sequences must use the BinaryData field. The keys stored in Data must not overlap with the keys in the BinaryData field, this is enforced during validation process.
#[inline]
pub fn data(mut self, value: impl Iterator<Item = (String, impl Into<String>)>) -> Self {
self.body.data = Some(value.map(|(key, value)| (key, value.into())).collect::<std::collections::BTreeMap<_, _>>().into());
self.body.data = Some(value.map(|(key, value)| (key, value.into())).collect::<paperclip::v2::PropertiesMap<_, _>>().into());
self
}

Expand Down Expand Up @@ -176,14 +176,14 @@ impl<Namespace> ConfigMapPostBuilder<Namespace> {
/// BinaryData contains the binary data. Each key must consist of alphanumeric characters, '-', '_' or '.'. BinaryData can contain byte sequences that are not in the UTF-8 range. The keys stored in BinaryData must not overlap with the ones in the Data field, this is enforced during validation process. Using this field will require 1.10+ apiserver and kubelet.
#[inline]
pub fn binary_data(mut self, value: impl Iterator<Item = (String, impl Into<String>)>) -> Self {
self.inner.body.binary_data = Some(value.map(|(key, value)| (key, value.into())).collect::<std::collections::BTreeMap<_, _>>().into());
self.inner.body.binary_data = Some(value.map(|(key, value)| (key, value.into())).collect::<paperclip::v2::PropertiesMap<_, _>>().into());
self
}

/// Data contains the configuration data. Each key must consist of alphanumeric characters, '-', '_' or '.'. Values with non-UTF-8 byte sequences must use the BinaryData field. The keys stored in Data must not overlap with the keys in the BinaryData field, this is enforced during validation process.
#[inline]
pub fn data(mut self, value: impl Iterator<Item = (String, impl Into<String>)>) -> Self {
self.inner.body.data = Some(value.map(|(key, value)| (key, value.into())).collect::<std::collections::BTreeMap<_, _>>().into());
self.inner.body.data = Some(value.map(|(key, value)| (key, value.into())).collect::<paperclip::v2::PropertiesMap<_, _>>().into());
self
}

Expand Down Expand Up @@ -363,14 +363,14 @@ impl<Name, Namespace> ConfigMapPutBuilder1<Name, Namespace> {
/// BinaryData contains the binary data. Each key must consist of alphanumeric characters, '-', '_' or '.'. BinaryData can contain byte sequences that are not in the UTF-8 range. The keys stored in BinaryData must not overlap with the ones in the Data field, this is enforced during validation process. Using this field will require 1.10+ apiserver and kubelet.
#[inline]
pub fn binary_data(mut self, value: impl Iterator<Item = (String, impl Into<String>)>) -> Self {
self.inner.body.binary_data = Some(value.map(|(key, value)| (key, value.into())).collect::<std::collections::BTreeMap<_, _>>().into());
self.inner.body.binary_data = Some(value.map(|(key, value)| (key, value.into())).collect::<paperclip::v2::PropertiesMap<_, _>>().into());
self
}

/// Data contains the configuration data. Each key must consist of alphanumeric characters, '-', '_' or '.'. Values with non-UTF-8 byte sequences must use the BinaryData field. The keys stored in Data must not overlap with the keys in the BinaryData field, this is enforced during validation process.
#[inline]
pub fn data(mut self, value: impl Iterator<Item = (String, impl Into<String>)>) -> Self {
self.inner.body.data = Some(value.map(|(key, value)| (key, value.into())).collect::<std::collections::BTreeMap<_, _>>().into());
self.inner.body.data = Some(value.map(|(key, value)| (key, value.into())).collect::<paperclip::v2::PropertiesMap<_, _>>().into());
self
}

Expand Down
Loading
Loading