diff --git a/src/generator/lib/enum_utils.py b/src/generator/lib/enum_utils.py index 2ce1af9d31..95f21c7d8c 100644 --- a/src/generator/lib/enum_utils.py +++ b/src/generator/lib/enum_utils.py @@ -2,7 +2,21 @@ from typing import Any from .rust_type import RustType from .types import Base from .util import Context, UNUSED_TYPE_MARKER, schema_markers, canonical_type_name, remove_invalid_chars_in_ident, \ - singular, activity_split, items + activity_split, items, Enum, EnumVariant + + +def get_enum_from_dict(name: RustType, s: dict) -> Enum: + description: str | None = get_from_enum(s, 'description') + (variants, is_any_variant_deprecated) = _get_enum_variants(s) + default: EnumVariant | str | None = get_enum_default(s) + default_variant: EnumVariant | None = None + if default is not None: + for variant in variants: + if variant.name == default: + default_variant = variant + break + + return Enum(name, description, variants, default_variant, is_any_variant_deprecated) def is_property_enum(s: dict) -> bool: @@ -27,12 +41,33 @@ def get_enum_type(schema_name: str, property_name: str) -> RustType: return Base(name) -def get_enum_variants(enum: dict) -> list[str]: - return get_from_enum(enum, 'enum') +def _get_enum_variants(enum: dict) -> tuple[list[EnumVariant], bool]: + variants = get_from_enum(enum, 'enum') + descriptions = get_from_enum(enum, 'enumDescriptions') + if not descriptions: + descriptions = variants + result = [] + is_any_variant_deprecated = False + for variant in descriptions: + if is_enum_variant_deprecated(variant): + is_any_variant_deprecated = True + break -def get_enum_variants_descriptions(enum: dict) -> list[str]: - return get_from_enum(enum, 'enumDescriptions') + for i in range(len(variants)): + variant = variants[i] + description = None + if descriptions: + description = descriptions[i] + + if variant is None: + continue + + is_deprecated = is_enum_variant_deprecated(description) + name = to_enum_variant_name(variant, not is_any_variant_deprecated) + result.append(EnumVariant(name, variant, description, is_deprecated, description)) + + return result, is_any_variant_deprecated def get_enum_default(enum: dict) -> str | None: @@ -54,16 +89,14 @@ def get_from_enum(enum: dict, key: str) -> list[Any] | None: if nested: return nested - if key != 'default': # just a debugging help - print(f"could not find key '{key}' in enum:", enum) return None -def get_enum_if_is_enum(k, property_name: str, property_value: dict) -> RustType | None: +def get_enum_if_is_enum(k, property_name: str, property_value: dict) -> Enum | None: if property_value is None: return None if is_property_enum(property_value): - return get_enum_type(k, property_name) + return get_enum_from_dict(get_enum_type(k, property_name), property_value) return get_enum_if_is_enum(k, property_name, _get_inner_enum(property_value)) @@ -79,8 +112,8 @@ def _get_inner_enum(pv: dict): return None -def find_enums_in_context(c: Context) -> list[tuple[str, Any, RustType, Any]]: - enums: dict[RustType, tuple[str, Any, RustType, Any]] = {} +def find_enums_in_context(c: Context) -> list[Enum]: + enums: dict[RustType, tuple[str, Any, Enum]] = {} for name, s in items(c.schemas): if UNUSED_TYPE_MARKER in schema_markers(s, c, transitive=True): continue @@ -99,24 +132,42 @@ def find_enums_in_context(c: Context) -> list[tuple[str, Any, RustType, Any]]: for pk, pv in items(parameters): add_to_enums_if_enum(name, pk, pv, enums) - return list(enums.values()) + result = [] + for enum in enums.values(): + result.append(enum[2]) + + return result def add_to_enums_if_enum(schema_name, property_name, property_value, - enums: dict[RustType, tuple[str, Any, RustType, Any]]): - enum = get_enum_if_is_enum(schema_name, property_name, property_value) + enums: dict[RustType, tuple[str, Any, Enum]]): + enum: Enum | None = get_enum_if_is_enum(schema_name, property_name, property_value) if enum: - existing_enum = enums.get(enum) + existing_enum = enums.get(enum.ty) if existing_enum: - if existing_enum[2] != enum: - print('WARNING: duplicate enum entry. ', enum.name, schema_name, property_name, property_value) - print('existing enum: ', existing_enum[2].name, existing_enum[0], existing_enum[1], existing_enum[3]) + if existing_enum[2].ty != enum.ty or existing_enum[2].variants != enum.variants: + print('WARNING: duplicate enum entry. ', enum.ty, schema_name, property_name, property_value) + print('existing enum: ', existing_enum[2].ty, existing_enum[0], existing_enum[1], + existing_enum[2]) return - enums[enum] = (schema_name, property_name, enum, property_value) + enums[enum.ty] = (schema_name, property_name, enum) -def to_enum_variant_name(name: str) -> str: - c_name = canonical_type_name(name) - c_name = remove_invalid_chars_in_ident(c_name) - return c_name +def to_enum_variant_name(name: str, make_camel_case: bool = True) -> str: + if make_camel_case: + name = canonical_type_name(name) + + name = remove_invalid_chars_in_ident(name) + return name + + +def is_enum_variant_deprecated(description: str | None) -> bool: + if description is None: + return False + + s = description.lower() + if 'deprecated' in s: + return True + + return False diff --git a/src/generator/lib/util.py b/src/generator/lib/util.py index 9d3dd58543..d6542b1761 100644 --- a/src/generator/lib/util.py +++ b/src/generator/lib/util.py @@ -24,15 +24,12 @@ re_relative_links = re.compile(r"\]\s*\([^h]") HTTP_METHODS = set(("OPTIONS", "GET", "POST", "PUT", "DELETE", "HEAD", "TRACE", "CONNECT", "PATCH")) - RESERVED_WORDS = set(('abstract', 'alignof', 'as', 'become', 'box', 'break', 'const', 'continue', 'crate', 'do', 'else', 'enum', 'extern', 'false', 'final', 'fn', 'for', 'if', 'impl', 'in', 'let', 'loop', 'macro', 'match', 'mod', 'move', 'mut', 'offsetof', 'override', 'priv', 'pub', 'pure', 'ref', 'return', 'sizeof', 'static', 'self', 'struct', 'super', 'true', 'trait', 'type', 'typeof', 'unsafe', 'unsized', 'use', 'virtual', 'where', 'while', 'yield')) - - TREF = '$ref' IO_RESPONSE = 'response' IO_REQUEST = 'request' @@ -99,10 +96,8 @@ data_unit_multipliers = { '%': 1, } - inflection = inflect.engine() - HUB_TYPE_PARAMETERS = ('S',) @@ -136,6 +131,7 @@ def rust_doc_comment(s): def use_automatic_links_in_rust_doc_comment(s: str) -> str: """Surrounds all links in the text with <>.""" + def replace_links(match): link = match.group() return f"<{link}>" @@ -143,10 +139,12 @@ def use_automatic_links_in_rust_doc_comment(s: str) -> str: # return re_non_hyper_links.sub(replace_links, s) return s + # returns true if there is an indication for something that is interpreted as doc comment by rustdoc def has_markdown_codeblock_with_indentation(s): return re_spaces_after_newline.search(s) != None + def preprocess(base_url, s): if base_url is None: print(f"WARNING {s} has no base_url") @@ -157,16 +155,18 @@ def preprocess(base_url, s): stdout=subprocess.PIPE, env={"URL_BASE": base_url or ""} ) - + res = p.communicate(s.encode('utf-8')) exitcode = p.wait(timeout=1) if exitcode != 0: raise ValueError(f"Child process exited with non-zero code {exitcode}") return res[0].decode('utf-8') + def has_relative_links(s): return re_relative_links.search(s) is not None + # runs the preprocessor in case there is evidence for code blocks using indentation def rust_doc_sanitize(base_url): def fixer(s): @@ -174,6 +174,7 @@ def rust_doc_sanitize(base_url): return preprocess(base_url, s) else: return s + return fixer @@ -800,6 +801,26 @@ def build_all_params(c, m): ## -- End Activity Utilities -- @} +@dataclass +class EnumVariant: + name: str + """ the rust name of the enum variant """ + value: str + """ the value of the enum variant (which is used for the api) """ + description: str | None + deprecated: bool + deprecation_message: str | None + + +@dataclass +class Enum: + ty: RustType + description: str | None + variants: list[EnumVariant] + default: EnumVariant | None + has_deprecated_variants: bool + + @dataclass class Context: sta_map: Dict[str, Any] @@ -807,7 +828,7 @@ class Context: rta_map: Dict[str, Any] rtc_map: Dict[str, Any] schemas: Dict[str, Any] - enums: List[Tuple[str, str, RustType, Dict[str, Any]]] + enums: List[Enum] # return a newly build context from the given data @@ -1221,18 +1242,17 @@ def rnd_arg_val_for_type(tn: str, c: Context = None) -> str: return str(RUST_TYPE_RND_MAP[name]()) if c: - from .enum_utils import get_enum_variants, to_enum_variant_name if tn.startswith("&"): # sometimes the types get passed as ref which doesn't make too much sense here tn = tn[1:] - for (_, _, enum, values) in c.enums: - if tn == enum.name: - variants = get_enum_variants(values) + for enum in c.enums: + if tn == enum.ty: + variants = enum.variants if len(variants) > 0: - variant = to_enum_variant_name(variants[0]) + variant = variants[0].name return f"&{tn}::{variant}" - print('Enum has no variants. This is probably not right...', enum, values) + print('Enum has no variants. This is probably not right...', enum) return "&Default::default()" @@ -1281,44 +1301,3 @@ def unique( if __name__ == '__main__': raise AssertionError('For import only') - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/generator/templates/api/api/enums.rs.mako b/src/generator/templates/api/api/enums.rs.mako index 2ace59ad11..81e78fc78e 100644 --- a/src/generator/templates/api/api/enums.rs.mako +++ b/src/generator/templates/api/api/enums.rs.mako @@ -6,7 +6,6 @@ <%namespace name="schema" file="../lib/schema.mako"/>\ <% from generator.lib.util import (new_context, hub_type, hub_type_params_s) - from generator.lib.enum_utils import (find_enums_in_context) c = new_context(schemas, resources) hub_type = hub_type(c.schemas, util.canonical_name()) @@ -18,7 +17,6 @@ use super::*; - -% for schema_name,property_name,enum_type, e in c.enums: -${enum.new(enum_type, e, c)} +% for e in c.enums: +${enum.new(e, c)} % endfor diff --git a/src/generator/templates/api/lib/enum.mako b/src/generator/templates/api/lib/enum.mako index 2b77663c9c..3559b48f4c 100644 --- a/src/generator/templates/api/lib/enum.mako +++ b/src/generator/templates/api/lib/enum.mako @@ -7,9 +7,7 @@ REQUEST_MARKER_TRAIT, RESPONSE_MARKER_TRAIT, supports_scopes, to_api_version, to_fqan, METHODS_RESOURCE, ADD_PARAM_MEDIA_EXAMPLE, PROTOCOL_TYPE_INFO, enclose_in, upload_action_fn, METHODS_BUILDER_MARKER_TRAIT, DELEGATE_TYPE, - to_extern_crate_name, rust_doc_sanitize) - - from generator.lib.enum_utils import (to_enum_variant_name, get_enum_variants, get_enum_variants_descriptions, get_enum_default) + to_extern_crate_name, rust_doc_sanitize, escape_rust_string) def pretty_name(name): return ' '.join(split_camelcase_s(name).split('.')) @@ -72,68 +70,75 @@ impl Default for Scope { ## Builds any generic enum for the API ############################################################################################### ############################################################################################### -<%def name="new(enum_type, e, c)">\ -// region ${enum_type} -#[derive(Clone, Copy, Eq, Hash, Debug, PartialEq, Serialize, Deserialize)] -% if e.get('description'): -${rust_doc_comment(e.description)} +<%def name="new(enum, c)">\ +// region ${enum.ty} +% if enum.has_deprecated_variants: +#[allow(non_camel_case_types, deprecated)] % endif -pub enum ${enum_type} { +#[derive(Clone, Copy, Eq, Hash, Debug, PartialEq, Serialize, Deserialize)] +% if enum.description: +${rust_doc_comment(enum.description)} +% endif +pub enum ${enum.ty} { <% -enum_variants = get_enum_variants(e) +enum_variants = enum.variants if not enum_variants: - print('enum had no variants', e) - enum_variants = ['NO_VARIANTS_FOUND'] -enum_descriptions = get_enum_variants_descriptions(e) -if not enum_descriptions: - enum_descriptions = ['no description found'] * len(enum_variants) - + print('enum had no variants', enum) + enum_variants = [] %>\ -% for (variant_name,description) in zip(enum_variants, enum_descriptions): - <% #print(variant_name, '=>', description) - %> - % if description: - ${rust_doc_comment(description)} - % endif\ +% for variant in enum_variants: + + % if variant.description: + ${rust_doc_comment(variant.description)} + % endif /// value: - /// "${variant_name}" - #[serde(rename="${variant_name}")] - ${to_enum_variant_name(variant_name)}, + /// "${variant.value}" + #[serde(rename="${variant.value}")] + % if variant.deprecated: + #[deprecated(note="${escape_rust_string(variant.deprecation_message)}")] + % endif + ${variant.name}, % endfor } -impl AsRef for ${enum_type} { +impl AsRef for ${enum.ty} { +% if enum.has_deprecated_variants: + #[allow(deprecated)] +% endif fn as_ref(&self) -> &str { match *self { % for variant in enum_variants: - ${enum_type}::${to_enum_variant_name(variant)} => "${variant}", + ${enum.ty}::${variant.name} => "${escape_rust_string(variant.value)}", % endfor } } } -impl ::std::convert::TryFrom< &str > for ${enum_type} { +impl ::std::convert::TryFrom< &str > for ${enum.ty} { type Error = (); - fn try_from(value: &str) -> ::std::result::Result >::Error> { +% if enum.has_deprecated_variants: + #[allow(deprecated)] +% endif + fn try_from(value: &str) -> ::std::result::Result >::Error> { match value { % for variant in enum_variants: - "${variant}" => ::std::result::Result::Ok(${enum_type}::${to_enum_variant_name(variant)}), + "${variant.value}" => ::std::result::Result::Ok(${enum.ty}::${variant.name}), % endfor - _=> ::std::result::Result::Err(()), + _ => ::std::result::Result::Err(()), } } } -impl<'a> Into<::std::borrow::Cow<'a, str>> for &'a ${enum_type} { - fn into(self) -> ::std::borrow::Cow<'a, str> { - self.as_ref().into() +impl<'a> From < &'a ${enum.ty} > for ::std::borrow::Cow< 'a, str > { + fn from(val: &'a ${enum.ty}) -> Self { + val.as_ref().into() } } -% if get_enum_default(e) is not None: -impl ::core::default::Default for ${enum_type} { - fn default() -> ${enum_type} { - ${enum_type}::${to_enum_variant_name(e.default)} +% if enum.default is not None: +impl ::core::default::Default for ${enum.ty} { + fn default() -> ${enum.ty} { + ${enum.ty}::${enum.default.name} } } % endif