Skip to content

Commit 135c8b5

Browse files
committed
Generate beta and experimental APIs, guarded by Cargo features
1 parent ed0f2cb commit 135c8b5

File tree

8 files changed

+124
-25
lines changed

8 files changed

+124
-25
lines changed

api_generator/src/generator/code_gen/namespace_clients.rs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,15 @@ use std::path::PathBuf;
2828
pub fn generate(api: &Api, docs_dir: &PathBuf) -> Result<Vec<(String, String)>, failure::Error> {
2929
let mut output = Vec::new();
3030

31-
for (namespace, namespace_methods) in &api.namespaces {
31+
for (namespace_name, namespace) in &api.namespaces {
3232
let mut tokens = Tokens::new();
33+
if let Some(attr) = namespace.stability.inner_cfg_attr() {
34+
tokens.append(attr);
35+
}
3336
tokens.append(use_declarations());
3437

35-
let namespace_pascal_case = namespace.to_pascal_case();
36-
let namespace_replaced_pascal_case = namespace.replace("_", " ").to_pascal_case();
38+
let namespace_pascal_case = namespace_name.to_pascal_case();
39+
let namespace_replaced_pascal_case = namespace_name.replace("_", " ").to_pascal_case();
3740
let namespace_client_name = ident(&namespace_pascal_case);
3841
let name_for_docs = match namespace_replaced_pascal_case.as_ref() {
3942
"Ccr" => "Cross Cluster Replication",
@@ -53,9 +56,10 @@ pub fn generate(api: &Api, docs_dir: &PathBuf) -> Result<Vec<(String, String)>,
5356
"Creates a new instance of [{}]",
5457
&namespace_pascal_case
5558
));
56-
let namespace_name = ident(namespace.to_string());
59+
let namespace_name = ident(namespace_name.to_string());
5760

58-
let (builders, methods): (Vec<Tokens>, Vec<Tokens>) = namespace_methods
61+
let (builders, methods): (Vec<Tokens>, Vec<Tokens>) = namespace
62+
.endpoints()
5963
.iter()
6064
.map(|(name, endpoint)| {
6165
let builder_name = format!("{}{}", &namespace_pascal_case, name.to_pascal_case());
@@ -72,14 +76,17 @@ pub fn generate(api: &Api, docs_dir: &PathBuf) -> Result<Vec<(String, String)>,
7276
})
7377
.unzip();
7478

79+
let cfg_attr = namespace.stability.outer_cfg_attr();
7580
tokens.append(quote!(
7681
#(#builders)*
7782

7883
#namespace_doc
84+
#cfg_attr
7985
pub struct #namespace_client_name<'a> {
8086
transport: &'a Transport
8187
}
8288

89+
#cfg_attr
8390
impl<'a> #namespace_client_name<'a> {
8491
#new_namespace_client_doc
8592
pub fn new(transport: &'a Transport) -> Self {
@@ -95,6 +102,7 @@ pub fn generate(api: &Api, docs_dir: &PathBuf) -> Result<Vec<(String, String)>,
95102
#(#methods)*
96103
}
97104

105+
#cfg_attr
98106
impl Elasticsearch {
99107
#namespace_fn_doc
100108
pub fn #namespace_name(&self) -> #namespace_client_name {
@@ -104,7 +112,7 @@ pub fn generate(api: &Api, docs_dir: &PathBuf) -> Result<Vec<(String, String)>,
104112
));
105113

106114
let generated = tokens.to_string();
107-
output.push((namespace.to_string(), generated));
115+
output.push((namespace_name.to_string(), generated));
108116
}
109117

110118
Ok(output)

api_generator/src/generator/code_gen/params.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,10 @@ fn generate_param(tokens: &mut Tokens, e: &ApiEnum) {
6464
None => None,
6565
};
6666

67+
let cfg_attr = e.stability.outer_cfg_attr();
68+
6769
let generated_enum_tokens = quote!(
70+
#cfg_attr
6871
#[derive(Debug, PartialEq, Deserialize, Serialize, Clone, Copy)]
6972
#doc
7073
pub enum #name {

api_generator/src/generator/code_gen/request/request_builder.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,19 +626,25 @@ impl<'a> RequestBuilder<'a> {
626626
api_name_for_docs
627627
));
628628

629+
let cfg_attr = endpoint.stability.outer_cfg_attr();
630+
629631
quote! {
632+
#cfg_attr
630633
#enum_struct
631634

635+
#cfg_attr
632636
#enum_impl
633637

634638
#[derive(Clone, Debug)]
635639
#[doc = #builder_doc]
640+
#cfg_attr
636641
pub struct #builder_expr {
637642
transport: &'a Transport,
638643
parts: #enum_ty,
639644
#(#fields),*,
640645
}
641646

647+
#cfg_attr
642648
#builder_impl {
643649
#new_fn
644650
#(#builder_fns)*
@@ -669,6 +675,8 @@ impl<'a> RequestBuilder<'a> {
669675
is_root_method: bool,
670676
enum_builder: &EnumBuilder,
671677
) -> Tokens {
678+
let cfg_attr = endpoint.stability.outer_cfg_attr();
679+
672680
let builder_ident = ident(builder_name);
673681

674682
let (fn_name, builder_ident_ret) = {
@@ -726,6 +734,7 @@ impl<'a> RequestBuilder<'a> {
726734
if enum_builder.contains_single_parameterless_part() {
727735
quote!(
728736
#method_doc
737+
#cfg_attr
729738
pub fn #fn_name(&'a self) -> #builder_ident_ret {
730739
#builder_ident::new(#clone_expr)
731740
}
@@ -734,6 +743,7 @@ impl<'a> RequestBuilder<'a> {
734743
let (enum_ty, _, _) = enum_builder.clone().build();
735744
quote!(
736745
#method_doc
746+
#cfg_attr
737747
pub fn #fn_name(&'a self, parts: #enum_ty) -> #builder_ident_ret {
738748
#builder_ident::new(#clone_expr, parts)
739749
}

api_generator/src/generator/code_gen/root.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ pub fn generate(api: &Api, docs_dir: &PathBuf) -> Result<String, failure::Error>
3232
// AST for builder structs and methods
3333
let (builders, methods): (Vec<Tokens>, Vec<Tokens>) = api
3434
.root
35+
.endpoints()
3536
.iter()
3637
.map(|(name, endpoint)| {
3738
let builder_name = name.to_pascal_case();

api_generator/src/generator/code_gen/url/enum_builder.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ mod tests {
311311
#![cfg_attr(rustfmt, rustfmt_skip)]
312312

313313
use super::*;
314-
use crate::generator::{Url, Path, HttpMethod, Body, Deprecated, Type, TypeKind, Documentation, ast_eq};
314+
use crate::generator::{Url, Path, HttpMethod, Body, Deprecated, Type, TypeKind, Documentation, ast_eq, Stability};
315315
use std::collections::BTreeMap;
316316
use crate::generator::code_gen::url::url_builder::PathString;
317317

@@ -326,7 +326,7 @@ mod tests {
326326
description: None,
327327
url: None,
328328
},
329-
stability: "stable".to_string(),
329+
stability: Stability::Stable,
330330
url: Url {
331331
paths: vec![
332332
Path {

api_generator/src/generator/mod.rs

Lines changed: 89 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ use std::{
3434
};
3535

3636
#[cfg(test)]
37-
use quote::{ToTokens, Tokens};
37+
use quote::ToTokens;
38+
use quote::Tokens;
3839
use semver::Version;
3940
use void::Void;
4041

@@ -63,9 +64,9 @@ pub struct Api {
6364
/// parameters that are common to all API methods
6465
pub common_params: BTreeMap<String, Type>,
6566
/// root API methods e.g. Search, Index
66-
pub root: BTreeMap<String, ApiEndpoint>,
67+
pub root: ApiNamespace,
6768
/// namespace client methods e.g. Indices.Create, Ml.PutJob
68-
pub namespaces: BTreeMap<String, BTreeMap<String, ApiEndpoint>>,
69+
pub namespaces: BTreeMap<String, ApiNamespace>,
6970
/// enums in parameters
7071
pub enums: Vec<ApiEnum>,
7172
}
@@ -79,9 +80,9 @@ impl Api {
7980
pub fn endpoint_for_api_call(&self, api_call: &str) -> Option<&ApiEndpoint> {
8081
let api_call_path: Vec<&str> = api_call.split('.').collect();
8182
match api_call_path.len() {
82-
1 => self.root.get(api_call_path[0]),
83+
1 => self.root.endpoints().get(api_call_path[0]),
8384
_ => match self.namespaces.get(api_call_path[0]) {
84-
Some(namespace) => namespace.get(api_call_path[1]),
85+
Some(namespace) => namespace.endpoints().get(api_call_path[1]),
8586
None => None,
8687
},
8788
}
@@ -347,13 +348,49 @@ where
347348
deserializer.deserialize_any(StringOrStruct(PhantomData))
348349
}
349350

351+
/// Stability level of an API endpoint. Ordering defines increasing stability level, i.e.
352+
/// `beta` is "more stable" than `experimental`.
353+
#[derive(Debug, Eq, PartialEq, Deserialize, Clone, Copy, Ord, PartialOrd)]
354+
pub enum Stability {
355+
#[serde(rename = "experimental")]
356+
Experimental,
357+
#[serde(rename = "beta")]
358+
Beta,
359+
#[serde(rename = "stable")]
360+
Stable,
361+
}
362+
363+
impl Stability {
364+
pub fn feature_name(self) -> Option<&'static str> {
365+
match self {
366+
Stability::Experimental => Some("experimental-apis"),
367+
Stability::Beta => Some("beta-apis"),
368+
Stability::Stable => None,
369+
}
370+
}
371+
372+
/// Returns the (optional) feature configuration for this stability level as an outer
373+
/// attribute, for use e.g. on function definitions.
374+
pub fn outer_cfg_attr(self) -> Option<Tokens> {
375+
let feature_name = self.feature_name();
376+
feature_name.map(|name| quote!(#[cfg(feature = #name)]))
377+
}
378+
379+
/// Returns the (optional) feature configuration for this stability level as an inner
380+
/// attribute, for use e.g. at the top of a module source file
381+
pub fn inner_cfg_attr(self) -> Option<Tokens> {
382+
let feature_name = self.feature_name();
383+
feature_name.map(|name| quote!(#![cfg(feature = #name)]))
384+
}
385+
}
386+
350387
/// An API endpoint defined in the REST API specs
351388
#[derive(Debug, PartialEq, Deserialize, Clone)]
352389
pub struct ApiEndpoint {
353390
pub full_name: Option<String>,
354391
#[serde(deserialize_with = "string_or_struct")]
355392
documentation: Documentation,
356-
pub stability: String,
393+
pub stability: Stability,
357394
pub url: Url,
358395
#[serde(default = "BTreeMap::new")]
359396
pub params: BTreeMap<String, Type>,
@@ -382,6 +419,34 @@ impl ApiEndpoint {
382419
}
383420
}
384421

422+
pub struct ApiNamespace {
423+
stability: Stability,
424+
endpoints: BTreeMap<String, ApiEndpoint>,
425+
}
426+
427+
impl ApiNamespace {
428+
pub fn new() -> Self {
429+
ApiNamespace {
430+
stability: Stability::Experimental, // will grow in stability as we add endpoints
431+
endpoints: BTreeMap::new(),
432+
}
433+
}
434+
435+
pub fn add(&mut self, name: String, endpoint: ApiEndpoint) {
436+
// Stability of a namespace is that of the most stable of its endpoints
437+
self.stability = Stability::max(self.stability, endpoint.stability);
438+
self.endpoints.insert(name, endpoint);
439+
}
440+
441+
pub fn stability(&self) -> Stability {
442+
self.stability
443+
}
444+
445+
pub fn endpoints(&self) -> &BTreeMap<String, ApiEndpoint> {
446+
&self.endpoints
447+
}
448+
}
449+
385450
/// Common parameters accepted by all API endpoints
386451
#[derive(Debug, PartialEq, Deserialize, Clone)]
387452
pub struct Common {
@@ -396,6 +461,7 @@ pub struct ApiEnum {
396461
pub name: String,
397462
pub description: Option<String>,
398463
pub values: Vec<String>,
464+
pub stability: Stability, // inherited from the declaring API
399465
}
400466

401467
impl Hash for ApiEnum {
@@ -499,7 +565,7 @@ pub use bulk::*;
499565
/// Reads Api from a directory of REST Api specs
500566
pub fn read_api(branch: &str, download_dir: &PathBuf) -> Result<Api, failure::Error> {
501567
let paths = fs::read_dir(download_dir)?;
502-
let mut namespaces = BTreeMap::new();
568+
let mut namespaces = BTreeMap::<String, ApiNamespace>::new();
503569
let mut enums: HashSet<ApiEnum> = HashSet::new();
504570
let mut common_params = BTreeMap::new();
505571
let root_key = "root";
@@ -517,11 +583,6 @@ pub fn read_api(branch: &str, download_dir: &PathBuf) -> Result<Api, failure::Er
517583
let mut file = File::open(&path)?;
518584
let (name, api_endpoint) = endpoint_from_file(display, &mut file)?;
519585

520-
// Only generate builders and methods for stable APIs, not experimental or beta
521-
if &api_endpoint.stability != "stable" {
522-
continue;
523-
}
524-
525586
let name_parts: Vec<&str> = name.splitn(2, '.').collect();
526587
let (namespace, method_name) = match name_parts.len() {
527588
len if len > 1 => (name_parts[0].to_string(), name_parts[1].to_string()),
@@ -545,19 +606,20 @@ pub fn read_api(branch: &str, download_dir: &PathBuf) -> Result<Api, failure::Er
545606
name: param.0.to_string(),
546607
description: param.1.description.clone(),
547608
values: options,
609+
stability: api_endpoint.stability,
548610
});
549611
}
550612

551613
// collect api endpoints into namespaces
552614
if !namespaces.contains_key(&namespace) {
553-
let mut api_endpoints = BTreeMap::new();
554-
api_endpoints.insert(method_name, api_endpoint);
555-
namespaces.insert(namespace.to_string(), api_endpoints);
615+
let mut api_namespace = ApiNamespace::new();
616+
api_namespace.add(method_name, api_endpoint);
617+
namespaces.insert(namespace.to_string(), api_namespace);
556618
} else {
557619
namespaces
558620
.get_mut(&namespace)
559621
.unwrap()
560-
.insert(method_name, api_endpoint);
622+
.add(method_name, api_endpoint);
561623
}
562624
} else if name
563625
.map(|name| name == Some("_common.json"))
@@ -627,3 +689,14 @@ where
627689
pub fn ast_eq<T: ToTokens>(expected: Tokens, actual: T) {
628690
assert_eq!(expected, quote!(#actual));
629691
}
692+
693+
#[cfg(test)]
694+
mod tests {
695+
use super::*;
696+
697+
#[test]
698+
fn stability_ordering() {
699+
assert!(Stability::Beta > Stability::Experimental);
700+
assert!(Stability::Stable > Stability::Beta);
701+
}
702+
}

elasticsearch/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ all-features = true
1717
[features]
1818
default = ["native-tls"]
1919

20+
# beta and experimental APIs
21+
beta-apis = []
22+
experimental-apis = ["beta-apis"]
23+
2024
# optional TLS
2125
native-tls = ["reqwest/native-tls"]
2226
rustls-tls = ["reqwest/rustls-tls"]

yaml_test_runner/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ repository = "https://github.com/elastic/elasticsearch-rs"
99
license = "Apache-2.0"
1010

1111
[dependencies]
12-
elasticsearch = { path = "./../elasticsearch" }
12+
elasticsearch = { path = "./../elasticsearch", features = ["experimental-apis"]}
1313
api_generator = { path = "./../api_generator" }
1414

1515
base64 = "^0.11"

0 commit comments

Comments
 (0)