Skip to content

Commit a76ebf1

Browse files
authored
Support enum modelAsString (#769)
Co-authored-by: John Batty <[email protected]>
1 parent 174cae8 commit a76ebf1

File tree

740 files changed

+412988
-786
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

740 files changed

+412988
-786
lines changed

services/autorust/codegen/src/codegen_models.rs

Lines changed: 105 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ impl SchemaGen {
6161
!self.schema.common.enum_.is_empty()
6262
}
6363

64+
fn is_model_as_string_enum(&self) -> bool {
65+
match &self.schema.common.x_ms_enum {
66+
Some(x_ms_enum) => x_ms_enum.model_as_string == Some(true),
67+
None => false,
68+
}
69+
}
70+
6471
fn is_local_struct(&self) -> bool {
6572
!self.schema.properties.is_empty()
6673
}
@@ -292,7 +299,9 @@ pub fn create_models(cg: &CodeGen) -> Result<TokenStream, Error> {
292299
file.extend(quote! {
293300
#![allow(non_camel_case_types)]
294301
#![allow(unused_imports)]
295-
use serde::{Deserialize, Serialize};
302+
use std::str::FromStr;
303+
use serde::{Serialize, Deserialize, Serializer};
304+
use serde::de::{value, Deserializer, IntoDeserializer};
296305
});
297306
if has_case_workaround {
298307
file.extend(quote! {
@@ -401,14 +410,15 @@ fn create_enum(
401410
source,
402411
property: property_name.to_owned(),
403412
})?;
413+
404414
let mut values = TokenStream::new();
405-
for enum_value in enum_values {
415+
for enum_value in &enum_values {
406416
let value = &enum_value.value;
407417
let nm = value.to_camel_case_ident().map_err(|source| Error::EnumName {
408418
source,
409419
property: property_name.to_owned(),
410420
})?;
411-
let doc_comment = match enum_value.description {
421+
let doc_comment = match &enum_value.description {
412422
Some(description) => {
413423
quote! { #[doc = #description] }
414424
}
@@ -430,6 +440,96 @@ fn create_enum(
430440
values.extend(value_token);
431441
}
432442

443+
// The x-ms-enum modelAsString enum field indicates that the enum is
444+
// subject to change, so should be treated as extensible. The document
445+
// says that if this field is set, "the enum will be modeled as a
446+
// string. No validation will happen."
447+
// https://azure.github.io/autorest/extensions/#x-ms-enum
448+
//
449+
// With Rust enums we can do better than that - use enum variants
450+
// for the known values but with an additional `UnknownValue(String)`
451+
// that can capture and store an unknown value as a `String`.
452+
// Unfortunately the standard `serde` attributes do not support this,
453+
// but it can be implemented via a custom deserializer using the
454+
// workaround suggested in this issue:
455+
// https://github.com/serde-rs/serde/issues/912
456+
457+
// If `model_as_string` then add the `UnknownValue(String)` field to the enum variants
458+
if property.is_model_as_string_enum() {
459+
let value_token = quote! {
460+
#[serde(skip_deserializing)]
461+
UnknownValue(String)
462+
};
463+
values.extend(value_token);
464+
}
465+
466+
// Need the id as a string as it needs to be quoted in some places in the
467+
// generated code.
468+
let id_str = id.to_string();
469+
470+
// If `model_as_string` then set the `serde` `remote` attribute to indicate
471+
// that the Serializer/Deserializer will be defined elsewhere.
472+
let maybe_remote_attr = if property.is_model_as_string_enum() {
473+
quote! {
474+
#[serde(remote = #id_str)]
475+
}
476+
} else {
477+
quote! {}
478+
};
479+
480+
// If `model_as_string` then provide custom `Deserialize` and `Serialize`
481+
// implementations.
482+
let custom_serde_code = if property.is_model_as_string_enum() {
483+
let mut serialize_fields = TokenStream::new();
484+
for (index, enum_value) in enum_values.iter().enumerate() {
485+
let value = &enum_value.value;
486+
let nm = value.to_camel_case_ident().map_err(|source| Error::EnumName {
487+
source,
488+
property: property_name.to_owned(),
489+
})?;
490+
let variant_index = index as u32;
491+
serialize_fields.extend(quote! {
492+
Self::#nm => serializer.serialize_unit_variant(#id_str, #variant_index, #value),
493+
});
494+
}
495+
496+
quote! {
497+
impl FromStr for #id {
498+
type Err = value::Error;
499+
500+
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
501+
Self::deserialize(s.into_deserializer())
502+
}
503+
}
504+
505+
impl<'de> Deserialize<'de> for #id {
506+
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
507+
where D: Deserializer<'de>
508+
{
509+
let s = String::deserialize(deserializer)?;
510+
let deserialized = Self::from_str(&s).unwrap_or(
511+
Self::UnknownValue(s)
512+
);
513+
Ok(deserialized)
514+
}
515+
}
516+
517+
impl Serialize for #id {
518+
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
519+
where
520+
S: Serializer,
521+
{
522+
match self {
523+
#serialize_fields
524+
Self::UnknownValue(s) => serializer.serialize_str(s.as_str()),
525+
}
526+
}
527+
}
528+
}
529+
} else {
530+
quote! {}
531+
};
532+
433533
let nm = property_name.to_camel_case_ident().map_err(|source| Error::EnumName {
434534
source,
435535
property: property_name.to_owned(),
@@ -460,9 +560,11 @@ fn create_enum(
460560
let code = quote! {
461561
#doc_comment
462562
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
563+
#maybe_remote_attr
463564
pub enum #nm {
464565
#values
465566
}
567+
#custom_serde_code
466568
#default_code
467569
};
468570
let type_name = TypeNameCode::from(vec![namespace, Some(id)]);

services/mgmt/activedirectory/src/package_2017_04_01/models.rs

Lines changed: 64 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

services/mgmt/activedirectory/src/package_2020_03/models.rs

Lines changed: 68 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)