diff --git a/Cargo.lock b/Cargo.lock index 1dc4b80dc133c..7de06e37dc30f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1729,6 +1729,7 @@ dependencies = [ "value", "vector-common", "vector-config", + "vector-config-common", "vector-config-macros", "vector-core", ] @@ -9105,6 +9106,7 @@ dependencies = [ "vector-buffers", "vector-common", "vector-config", + "vector-config-common", "vector-config-macros", "vrl", ] @@ -9192,6 +9194,7 @@ dependencies = [ "value", "vector-common", "vector-config", + "vector-config-common", "vector-config-macros", "vrl-core", "vrl-diagnostic", diff --git a/lib/codecs/Cargo.toml b/lib/codecs/Cargo.toml index 1975986e4b94c..ea423838fb3b1 100644 --- a/lib/codecs/Cargo.toml +++ b/lib/codecs/Cargo.toml @@ -27,6 +27,7 @@ tracing = { version = "0.1", default-features = false } value = { path = "../value", default-features = false } vector-common = { path = "../vector-common", default-features = false } vector-config = { path = "../vector-config", default-features = false } +vector-config-common = { path = "../vector-config-common", default-features = false } vector-config-macros = { path = "../vector-config-macros", default-features = false } vector-core = { path = "../vector-core", default-features = false } diff --git a/lib/vector-common/src/datetime.rs b/lib/vector-common/src/datetime.rs index a3a03717869ed..cf068de947720 100644 --- a/lib/vector-common/src/datetime.rs +++ b/lib/vector-common/src/datetime.rs @@ -3,10 +3,27 @@ use std::fmt::Debug; use chrono::{DateTime, Local, ParseError, TimeZone as _, Utc}; use chrono_tz::Tz; use derivative::Derivative; -use vector_config::configurable_component; +use vector_config::{ + schema::{ + apply_metadata, generate_const_string_schema, generate_one_of_schema, + get_or_generate_schema, + }, + schemars::{gen::SchemaGenerator, schema::SchemaObject}, + Configurable, GenerateError, Metadata, +}; +use vector_config_common::attributes::CustomAttribute; /// Timezone reference. -#[configurable_component(no_deser, no_ser)] +/// +/// This can refer to any valid timezone as defined in the [TZ database][tzdb], or "local" which +/// refers to the system local timezone. +/// +/// [tzdb]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones +#[cfg_attr( + feature = "serde", + derive(::serde::Deserialize, ::serde::Serialize), + serde(try_from = "String", into = "String") +)] #[derive(Clone, Copy, Debug, Derivative, Eq, PartialEq)] #[derivative(Default)] pub enum TimeZone { @@ -19,7 +36,7 @@ pub enum TimeZone { /// Must be a valid name in the [TZ database][tzdb]. /// /// [tzdb]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones - Named(#[configurable(transparent)] Tz), + Named(Tz), } /// This is a wrapper trait to allow `TimeZone` types to be passed generically. @@ -54,41 +71,63 @@ pub(super) fn datetime_to_utc(ts: &DateTime) -> DateTi Utc.timestamp(ts.timestamp(), ts.timestamp_subsec_nanos()) } -#[cfg(feature = "serde")] -pub mod ser_de { - use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +impl From for String { + fn from(tz: TimeZone) -> Self { + match tz { + TimeZone::Local => "local".to_string(), + TimeZone::Named(tz) => tz.name().to_string(), + } + } +} - use super::TimeZone; +impl TryFrom for TimeZone { + type Error = String; - impl Serialize for TimeZone { - fn serialize(&self, serializer: S) -> Result { - match self { - Self::Local => serializer.serialize_str("local"), - Self::Named(tz) => serializer.serialize_str(tz.name()), - } + fn try_from(value: String) -> Result { + match TimeZone::parse(&value) { + Some(tz) => Ok(tz), + None => Err("No such time zone".to_string()), } } +} - impl<'de> Deserialize<'de> for TimeZone { - fn deserialize>(deserializer: D) -> Result { - deserializer.deserialize_str(TimeZoneVisitor) - } +// TODO: Consider an approach for generating schema of "fixed string value, or remainder" structure +// used by this type. +impl Configurable for TimeZone { + fn referenceable_name() -> Option<&'static str> { + Some(std::any::type_name::()) } - struct TimeZoneVisitor; + fn metadata() -> vector_config::Metadata { + let mut metadata = vector_config::Metadata::default(); + metadata.set_title("Timezone reference."); + metadata.set_description(r#"This can refer to any valid timezone as defined in the [TZ database][tzdb], or "local" which refers to the system local timezone. - impl<'de> de::Visitor<'de> for TimeZoneVisitor { - type Value = TimeZone; +[tzdb]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones"#); + metadata + } - fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "a time zone name") - } + fn generate_schema(gen: &mut SchemaGenerator) -> Result { + let mut local_schema = generate_const_string_schema("local".to_string()); + let mut local_metadata = Metadata::<()>::with_description("System local timezone."); + local_metadata.add_custom_attribute(CustomAttribute::KeyValue { + key: "logical_name".to_string(), + value: "Local".to_string(), + }); + apply_metadata(&mut local_schema, local_metadata); - fn visit_str(self, s: &str) -> Result { - match TimeZone::parse(s) { - Some(tz) => Ok(tz), - None => Err(de::Error::custom("No such time zone")), - } - } + let mut tz_metadata = Metadata::with_title("A named timezone."); + tz_metadata.set_description( + r#"Must be a valid name in the [TZ database][tzdb]. + +[tzdb]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones"#, + ); + tz_metadata.add_custom_attribute(CustomAttribute::KeyValue { + key: "logical_name".to_string(), + value: "Named".to_string(), + }); + let tz_schema = get_or_generate_schema::(gen, tz_metadata)?; + + Ok(generate_one_of_schema(&[local_schema, tz_schema])) } } diff --git a/lib/vector-config-macros/src/ast/variant.rs b/lib/vector-config-macros/src/ast/variant.rs index a45b71bb06c86..97623e5dc554f 100644 --- a/lib/vector-config-macros/src/ast/variant.rs +++ b/lib/vector-config-macros/src/ast/variant.rs @@ -1,4 +1,5 @@ use darling::{error::Accumulator, util::Flag, FromAttributes}; +use proc_macro2::Ident; use serde_derive_internals::ast as serde_ast; use syn::spanned::Spanned; use vector_config_common::attributes::CustomAttribute; @@ -50,6 +51,11 @@ impl<'a> Variant<'a> { accumulator.finish_with(variant) } + /// Ident of the variant. + pub fn ident(&self) -> &Ident { + &self.original.ident + } + /// Style of the variant. /// /// This comes directly from `serde`, but effectively represents common terminology used outside diff --git a/lib/vector-config-macros/src/configurable.rs b/lib/vector-config-macros/src/configurable.rs index 1bf5bad4d8836..ae001a38003d8 100644 --- a/lib/vector-config-macros/src/configurable.rs +++ b/lib/vector-config-macros/src/configurable.rs @@ -141,7 +141,7 @@ fn build_enum_generate_schema_fn(variants: &[Variant<'_>]) -> proc_macro2::Token let schema_metadata = ::metadata(); #(#mapped_variants)* - let mut schema = ::vector_config::schema::generate_composite_schema(&subschemas); + let mut schema = ::vector_config::schema::generate_one_of_schema(&subschemas); ::vector_config::schema::apply_metadata(&mut schema, schema_metadata); Ok(schema) @@ -402,6 +402,20 @@ fn generate_variant_metadata( get_metadata_transparent(meta_ident, variant.tagging() == &Tagging::None); let maybe_custom_attributes = get_metadata_custom_attributes(meta_ident, variant.metadata()); + // We add a special metadata key (`logical_name`) that informs consumers of the schema what the + // variant name is for this variant's subschema. While the doc comments being coerced into title + // and/or description are typically good enough, sometimes we need a more mechanical mapping of + // the variant's name since shoving it into the title would mean doc comments with redundant + // information. + // + // You can think of this as an enum-specific additional title. + let logical_name_attrs = vec![CustomAttribute::KeyValue { + key: "logical_name".to_string(), + value: variant.ident().to_string(), + }]; + let variant_logical_name = + get_metadata_custom_attributes(meta_ident, logical_name_attrs.into_iter()); + // We specifically use `()` as the type here because we need to generate the metadata for this // variant, but there's no unique concrete type for a variant, only the type of the enum // container it exists within. We also don't want to use the metadata of the enum container, as @@ -413,6 +427,7 @@ fn generate_variant_metadata( #maybe_deprecated #maybe_transparent #maybe_custom_attributes + #variant_logical_name } } diff --git a/lib/vector-config/src/schema.rs b/lib/vector-config/src/schema.rs index aa65edfa70003..a18cc7aa3c1d0 100644 --- a/lib/vector-config/src/schema.rs +++ b/lib/vector-config/src/schema.rs @@ -75,10 +75,6 @@ where } pub fn convert_to_flattened_schema(primary: &mut SchemaObject, mut subschemas: Vec) { - // Now we need to extract our object validation portion into a new schema object, add it to the list of subschemas, - // and then update the primary schema to use `allOf`. It is not valid to "extend" a schema via `allOf`, hence why we - // have to extract the primary schema object validation first. - // First, we replace the primary schema with an empty schema, because we need to push it the actual primary schema // into the list of `allOf` schemas. This is due to the fact that it's not valid to "extend" a schema using `allOf`, // so everything has to be in there. @@ -274,7 +270,7 @@ where // outer field wrapping this schema, and it looks wonky to have it nested within the composite schema. schema.metadata().description = None; - return Ok(generate_composite_schema(&[null, schema])) + return Ok(generate_one_of_schema(&[null, schema])) } }, Some(sov) => match sov { @@ -291,7 +287,7 @@ where Ok(schema) } -pub fn generate_composite_schema(subschemas: &[SchemaObject]) -> SchemaObject { +pub fn generate_one_of_schema(subschemas: &[SchemaObject]) -> SchemaObject { let subschemas = subschemas .iter() .map(|s| Schema::Object(s.clone())) @@ -306,6 +302,21 @@ pub fn generate_composite_schema(subschemas: &[SchemaObject]) -> SchemaObject { } } +pub fn generate_all_of_schema(subschemas: &[SchemaObject]) -> SchemaObject { + let subschemas = subschemas + .iter() + .map(|s| Schema::Object(s.clone())) + .collect::>(); + + SchemaObject { + subschemas: Some(Box::new(SubschemaValidation { + all_of: Some(subschemas), + ..Default::default() + })), + ..Default::default() + } +} + pub fn generate_tuple_schema(subschemas: &[SchemaObject]) -> SchemaObject { let subschemas = subschemas .iter() @@ -325,6 +336,13 @@ pub fn generate_tuple_schema(subschemas: &[SchemaObject]) -> SchemaObject { } } +pub fn generate_enum_schema(values: Vec) -> SchemaObject { + SchemaObject { + enum_values: Some(values), + ..Default::default() + } +} + pub fn generate_const_string_schema(value: String) -> SchemaObject { SchemaObject { const_value: Some(Value::String(value)), diff --git a/lib/vector-core/Cargo.toml b/lib/vector-core/Cargo.toml index 9194429c294d1..7f7c422343f49 100644 --- a/lib/vector-core/Cargo.toml +++ b/lib/vector-core/Cargo.toml @@ -62,6 +62,7 @@ value = { path = "../value", default-features = false, features = ["lua", "toml" vector-buffers = { path = "../vector-buffers", default-features = false } vector-common = { path = "../vector-common" } vector-config = { path = "../vector-config" } +vector-config-common = { path = "../vector-config-common" } vector-config-macros = { path = "../vector-config-macros" } # Rename to "vrl" once we use a release with stable `-Z namespaced-features`: # https://doc.rust-lang.org/cargo/reference/unstable.html#namespaced-features diff --git a/lib/vrl/compiler/Cargo.toml b/lib/vrl/compiler/Cargo.toml index 47c088173965b..da970c670fb34 100644 --- a/lib/vrl/compiler/Cargo.toml +++ b/lib/vrl/compiler/Cargo.toml @@ -34,6 +34,7 @@ parser = { package = "vrl-parser", path = "../parser" } lookup = { path = "../../lookup" } vector-common = { path = "../../vector-common", default-features = false, features = ["conversion", "serde"] } vector-config = { path = "../../vector-config" } +vector-config-common = { path = "../../vector-config-common" } vector-config-macros = { path = "../../vector-config-macros" } value = { path = "../../value" } diff --git a/src/aws/auth.rs b/src/aws/auth.rs index caa90ad6581ed..af16298d1b6a5 100644 --- a/src/aws/auth.rs +++ b/src/aws/auth.rs @@ -18,7 +18,7 @@ const DEFAULT_LOAD_TIMEOUT: Duration = Duration::from_secs(5); #[serde(deny_unknown_fields, untagged)] pub enum AwsAuthentication { /// Authenticate using a fixed access key and secret pair. - Static { + AccessKey { /// The AWS access key ID. access_key_id: SensitiveString, @@ -66,7 +66,7 @@ impl AwsAuthentication { service_region: Region, ) -> crate::Result { match self { - Self::Static { + Self::AccessKey { access_key_id, secret_access_key, } => Ok(SharedCredentialsProvider::new(Credentials::from_keys( @@ -97,7 +97,7 @@ impl AwsAuthentication { #[cfg(test)] pub fn test_auth() -> AwsAuthentication { - AwsAuthentication::Static { + AwsAuthentication::AccessKey { access_key_id: "dummy".to_string().into(), secret_access_key: "dummy".to_string().into(), } @@ -220,7 +220,7 @@ mod tests { ) .unwrap(); - assert!(matches!(config.auth, AwsAuthentication::Static { .. })); + assert!(matches!(config.auth, AwsAuthentication::AccessKey { .. })); } #[test] diff --git a/src/sinks/util/buffer/compression.rs b/src/sinks/util/buffer/compression.rs index c19c942b2444d..2a473b27fda95 100644 --- a/src/sinks/util/buffer/compression.rs +++ b/src/sinks/util/buffer/compression.rs @@ -1,34 +1,19 @@ -use std::fmt; +use std::{collections::BTreeSet, fmt}; +use indexmap::IndexMap; use serde::{de, ser}; use vector_config::{ - configurable_component, - schema::{generate_composite_schema, generate_number_schema, generate_string_schema}, + schema::{ + apply_metadata, generate_const_string_schema, generate_enum_schema, + generate_internal_tagged_variant_schema, generate_one_of_schema, generate_struct_schema, + get_or_generate_schema, + }, schemars::{gen::SchemaGenerator, schema::SchemaObject}, - validation::Validation, - Configurable, GenerateError, + Configurable, GenerateError, Metadata, }; - -// NOTE: The resulting schema for `Compression` is not going to look right, because of how we do the customized "string -// or map" stuff. We've done a bunch of work here to get us half-way towards generating a valid schema for it, including -// add in the `CompressionLevel` type and doing a custom `Configurable` implementation for it... but the real meat of -// the change would be doing so for `Compression` because of it being an enum and all the boilerplate involved there. -// -// It'd be nice to find a succinct way to allow the expression of these constraints, possibly through a declarative -// macro that helps build the manual `Configurable` implementation such that, while still leaving the developer -// responsible for generating a correct schema, it would be far closer to a human-readable description of the schema -// i.e. "this schema can be X or Y, and if X, it can only be these values, or if Y, the value must fall within this -// specific range" and so on, with methods for describing enum variants, etc. -// -// Another complication is that we can _almost_ sort of get there with the existing primitives but `serde` itself has no -// way to express an enum that functions like what we're doing here i.e. the variant name is parsed from the value, -// without needing to be a tag field, with the ability to fallback to a newtype variant, etc. -// -// Long story short, these custom (de)serialization strategies are hard to encode in a procedural way because they are -// in fact not themselves declared procedurally. +use vector_config_common::attributes::CustomAttribute; /// Compression configuration. -#[configurable_component(no_ser, no_deser)] #[derive(Copy, Clone, Debug, Derivative, Eq, PartialEq)] #[derivative(Default)] pub enum Compression { @@ -39,12 +24,12 @@ pub enum Compression { /// [Gzip][gzip] compression. /// /// [gzip]: https://en.wikipedia.org/wiki/Gzip - Gzip(#[configurable(derived)] CompressionLevel), + Gzip(CompressionLevel), /// [Zlib][zlib] compression. /// /// [zlib]: https://en.wikipedia.org/wiki/Zlib - Zlib(#[configurable(derived)] CompressionLevel), + Zlib(CompressionLevel), } impl Compression { @@ -213,6 +198,96 @@ impl ser::Serialize for Compression { } } +// TODO: Consider an approach for generating schema of "string or object" structure used by this type. +impl Configurable for Compression { + fn referenceable_name() -> Option<&'static str> { + Some(std::any::type_name::()) + } + + fn description() -> Option<&'static str> { + Some("Compression configuration.") + } + + fn generate_schema(gen: &mut SchemaGenerator) -> Result { + const ALGORITHM_NAME: &str = "algorithm"; + const LEVEL_NAME: &str = "level"; + const NONE_NAME: &str = "none"; + const GZIP_NAME: &str = "gzip"; + const ZLIB_NAME: &str = "zlib"; + + // First, we need to be able to handle all of the string-only variants. + let const_values = [NONE_NAME, GZIP_NAME, ZLIB_NAME] + .iter() + .map(|s| serde_json::Value::from(*s)) + .collect(); + + // Now we need to handle when the user specifies the full object-based notation in order to + // specify the compression level, and so on. + let mut compression_level_metadata = Metadata::default(); + compression_level_metadata.set_transparent(); + let compression_level_schema = + get_or_generate_schema::(gen, compression_level_metadata)?; + + let mut required = BTreeSet::new(); + required.insert(ALGORITHM_NAME.to_string()); + + // Build the None schema. + let mut none_schema = generate_internal_tagged_variant_schema( + ALGORITHM_NAME.to_string(), + NONE_NAME.to_string(), + ); + let mut none_metadata = Metadata::<()>::with_description("No compression."); + none_metadata.add_custom_attribute(CustomAttribute::KeyValue { + key: "logical_name".to_string(), + value: "None".to_string(), + }); + apply_metadata(&mut none_schema, none_metadata); + + // Build the Gzip schema. + let mut gzip_properties = IndexMap::new(); + gzip_properties.insert( + ALGORITHM_NAME.to_string(), + generate_const_string_schema(GZIP_NAME.to_string()), + ); + gzip_properties.insert(LEVEL_NAME.to_string(), compression_level_schema.clone()); + + let mut gzip_schema = generate_struct_schema(gzip_properties, required.clone(), None); + let mut gzip_metadata = Metadata::<()>::with_title("[Gzip][gzip] compression."); + gzip_metadata.set_description("[gzip]: https://en.wikipedia.org/wiki/Gzip"); + gzip_metadata.add_custom_attribute(CustomAttribute::KeyValue { + key: "logical_name".to_string(), + value: "Gzip".to_string(), + }); + apply_metadata(&mut gzip_schema, gzip_metadata); + + // Build the Zlib schema. + let mut zlib_properties = IndexMap::new(); + zlib_properties.insert( + ALGORITHM_NAME.to_string(), + generate_const_string_schema(ZLIB_NAME.to_string()), + ); + zlib_properties.insert(LEVEL_NAME.to_string(), compression_level_schema); + + let mut zlib_schema = generate_struct_schema(zlib_properties, required, None); + let mut zlib_metadata = Metadata::<()>::with_title("[Zlib]][zlib] compression."); + zlib_metadata.set_description("[zlib]: https://en.wikipedia.org/wiki/Zlib"); + zlib_metadata.add_custom_attribute(CustomAttribute::KeyValue { + key: "logical_name".to_string(), + value: "Zlib".to_string(), + }); + apply_metadata(&mut zlib_schema, zlib_metadata); + + Ok(generate_one_of_schema(&[ + // Handle the condensed string form. + generate_enum_schema(const_values), + // Handle the expanded object form. + none_schema, + gzip_schema, + zlib_schema, + ])) + } +} + /// Compression level. #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct CompressionLevel(flate2::Compression); @@ -323,37 +398,25 @@ impl ser::Serialize for CompressionLevel { } } +// TODO: Consider an approach for generating schema of "string or number" structure used by this type. impl Configurable for CompressionLevel { - fn generate_schema(_: &mut SchemaGenerator) -> Result { - let as_number = generate_number_schema::(); - let as_string = generate_string_schema(); - - Ok(generate_composite_schema(&[as_number, as_string])) + fn referenceable_name() -> Option<&'static str> { + Some(std::any::type_name::()) } fn description() -> Option<&'static str> { Some("Compression level.") } - fn metadata() -> vector_config::Metadata { - let mut metadata = vector_config::Metadata::default(); - if let Some(description) = Self::description() { - metadata.set_description(description); - } + fn generate_schema(_: &mut SchemaGenerator) -> Result { + let string_consts = ["none", "fast", "best", "default"] + .iter() + .map(|s| serde_json::Value::from(*s)); - // Allows the user to specify any number from 0 to 9, or the constants "none", "fast", or "best". - // - // TODO: Technically, we can define `integer` or `number` for a schema's instance type, which would make the - // validation do the right thing, since as-is, while our implicit casting, everything in the schema ends up - // looking like it can be a floating-point number. We should add support for generating `integer` schemas, and - // then add validator support to do ranges specifically for integers vs numbers (floating-point). - metadata.add_validation(Validation::Range { - minimum: Some(0.0), - maximum: Some(9.0), - }); - metadata.add_validation(Validation::Pattern(String::from("none|fast|best|default"))); + let level_consts = (0u32..=9).map(serde_json::Value::from); - metadata + let valid_values = string_consts.chain(level_consts).collect(); + Ok(generate_enum_schema(valid_values)) } } diff --git a/src/sinks/util/service/concurrency.rs b/src/sinks/util/service/concurrency.rs index 6f1a7ca902e2d..eac81ec92cb2e 100644 --- a/src/sinks/util/service/concurrency.rs +++ b/src/sinks/util/service/concurrency.rs @@ -1,6 +1,14 @@ use serde::Serializer; use std::fmt; -use vector_config::configurable_component; +use vector_config::{ + schema::{ + apply_metadata, generate_const_string_schema, generate_number_schema, + generate_one_of_schema, + }, + schemars::{gen::SchemaGenerator, schema::SchemaObject}, + Configurable, GenerateError, Metadata, +}; +use vector_config_common::attributes::CustomAttribute; use serde::{ de::{self, Unexpected, Visitor}, @@ -8,21 +16,20 @@ use serde::{ }; /// Configuration for outbound request concurrency. -#[configurable_component(no_ser, no_deser)] #[derive(Clone, Copy, Debug, Derivative, Eq, PartialEq)] pub enum Concurrency { /// A fixed concurrency of 1. /// - /// In other words, only one request can be outstanding at any given time. + /// Only one request can be outstanding at any given time. None, - /// Concurrency will be managed by Vector's [adaptive concurrency][arc] feature. + /// Concurrency will be managed by Vector's [Adaptive Request Concurrency][arc] feature. /// /// [arc]: https://vector.dev/docs/about/under-the-hood/networking/arc/ Adaptive, /// A fixed amount of concurrency will be allowed. - Fixed(#[configurable(transparent)] usize), + Fixed(usize), } impl Serialize for Concurrency { @@ -116,6 +123,56 @@ impl<'de> Deserialize<'de> for Concurrency { } } +// TODO: Consider an approach for generating schema of "string or number" structure used by this type. +impl Configurable for Concurrency { + fn referenceable_name() -> Option<&'static str> { + Some(std::any::type_name::()) + } + + fn description() -> Option<&'static str> { + Some("Configuration for outbound request concurrency.") + } + + fn generate_schema(_: &mut SchemaGenerator) -> Result { + let mut none_schema = generate_const_string_schema("none".to_string()); + let mut none_metadata = Metadata::<()>::with_title("A fixed concurrency of 1."); + none_metadata.set_description("Only one request can be outstanding at any given time."); + none_metadata.add_custom_attribute(CustomAttribute::KeyValue { + key: "logical_name".to_string(), + value: "None".to_string(), + }); + apply_metadata(&mut none_schema, none_metadata); + + let mut adaptive_schema = generate_const_string_schema("adaptive".to_string()); + let mut adaptive_metadata = Metadata::<()>::with_title( + "Concurrency will be managed by Vector's [Adaptive Request Concurrency][arc] feature.", + ); + adaptive_metadata + .set_description("[arc]: https://vector.dev/docs/about/under-the-hood/networking/arc/"); + adaptive_metadata.add_custom_attribute(CustomAttribute::KeyValue { + key: "logical_name".to_string(), + value: "Adaptive".to_string(), + }); + apply_metadata(&mut adaptive_schema, adaptive_metadata); + + let mut fixed_schema = generate_number_schema::(); + let mut fixed_metadata = + Metadata::<()>::with_description("A fixed amount of concurrency will be allowed."); + fixed_metadata.set_transparent(); + fixed_metadata.add_custom_attribute(CustomAttribute::KeyValue { + key: "logical_name".to_string(), + value: "Fixed".to_string(), + }); + apply_metadata(&mut fixed_schema, fixed_metadata); + + Ok(generate_one_of_schema(&[ + none_schema, + adaptive_schema, + fixed_schema, + ])) + } +} + #[test] fn is_serialization_reversible() { let variants = [