summaryrefslogtreecommitdiffstats
path: root/third_party/python/glean_parser/glean_parser/rust.py
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/python/glean_parser/glean_parser/rust.py')
-rw-r--r--third_party/python/glean_parser/glean_parser/rust.py218
1 files changed, 218 insertions, 0 deletions
diff --git a/third_party/python/glean_parser/glean_parser/rust.py b/third_party/python/glean_parser/glean_parser/rust.py
new file mode 100644
index 0000000000..eb3355e382
--- /dev/null
+++ b/third_party/python/glean_parser/glean_parser/rust.py
@@ -0,0 +1,218 @@
+# -*- coding: utf-8 -*-
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"""
+Outputter to generate Rust code for metrics.
+"""
+
+import enum
+import json
+from pathlib import Path
+from typing import Any, Dict, Optional, Union
+
+from . import __version__
+from . import metrics
+from . import pings
+from . import tags
+from . import util
+
+
+def rust_datatypes_filter(value):
+ """
+ A Jinja2 filter that renders Rust literals.
+
+ Based on Python's JSONEncoder, but overrides:
+ - dicts and sets to raise an error
+ - sets to vec![] (used in labels)
+ - enums to become Class::Value
+ - lists to vec![] (used in send_in_pings)
+ - null to None
+ - strings to "value".into()
+ - Rate objects to a CommonMetricData initializer
+ (for external Denominators' Numerators lists)
+ """
+
+ class RustEncoder(json.JSONEncoder):
+ def iterencode(self, value):
+ if isinstance(value, dict):
+ raise ValueError("RustEncoder doesn't know dicts {}".format(str(value)))
+ elif isinstance(value, enum.Enum):
+ yield (value.__class__.__name__ + "::" + util.Camelize(value.name))
+ elif isinstance(value, set):
+ yield "vec!["
+ first = True
+ for subvalue in sorted(list(value)):
+ if not first:
+ yield ", "
+ yield from self.iterencode(subvalue)
+ first = False
+ yield "]"
+ elif isinstance(value, list):
+ yield "vec!["
+ first = True
+ for subvalue in list(value):
+ if not first:
+ yield ", "
+ yield from self.iterencode(subvalue)
+ first = False
+ yield "]"
+ elif value is None:
+ yield "None"
+ # `CowStr` is a `str`, so needs to be before next case
+ elif isinstance(value, metrics.CowString):
+ yield f'::std::borrow::Cow::from("{value.inner}")'
+ elif isinstance(value, str):
+ yield f'"{value}".into()'
+ elif isinstance(value, metrics.Rate):
+ yield "CommonMetricData("
+ first = True
+ for arg_name in util.common_metric_args:
+ if hasattr(value, arg_name):
+ if not first:
+ yield ", "
+ yield f"{util.camelize(arg_name)} = "
+ yield from self.iterencode(getattr(value, arg_name))
+ first = False
+ yield ")"
+ else:
+ yield from super().iterencode(value)
+
+ return "".join(RustEncoder().iterencode(value))
+
+
+def ctor(obj):
+ """
+ Returns the scope and name of the constructor to use for a metric object.
+ Necessary because LabeledMetric<T> is constructed using LabeledMetric::new
+ not LabeledMetric<T>::new
+ """
+ if getattr(obj, "labeled", False):
+ return "LabeledMetric::new"
+ return class_name(obj.type) + "::new"
+
+
+def type_name(obj):
+ """
+ Returns the Rust type to use for a given metric or ping object.
+ """
+
+ if getattr(obj, "labeled", False):
+ return "LabeledMetric<{}>".format(class_name(obj.type))
+ generate_enums = getattr(obj, "_generate_enums", []) # Extra Keys? Reasons?
+ if len(generate_enums):
+ generic = None
+ for name, suffix in generate_enums:
+ if len(getattr(obj, name)):
+ generic = util.Camelize(obj.name) + suffix
+ else:
+ if isinstance(obj, metrics.Event):
+ generic = "NoExtra"
+ else:
+ generic = "No" + suffix
+
+ return "{}<{}>".format(class_name(obj.type), generic)
+
+ return class_name(obj.type)
+
+
+def extra_type_name(typ: str) -> str:
+ """
+ Returns the corresponding Rust type for event's extra key types.
+ """
+
+ if typ == "boolean":
+ return "bool"
+ elif typ == "string":
+ return "String"
+ elif typ == "quantity":
+ return "u32"
+ else:
+ return "UNSUPPORTED"
+
+
+def class_name(obj_type):
+ """
+ Returns the Rust class name for a given metric or ping type.
+ """
+ if obj_type == "ping":
+ return "Ping"
+ if obj_type.startswith("labeled_"):
+ obj_type = obj_type[8:]
+ return util.Camelize(obj_type) + "Metric"
+
+
+def extra_keys(allowed_extra_keys):
+ """
+ Returns the &'static [&'static str] ALLOWED_EXTRA_KEYS for impl ExtraKeys
+ """
+ return "&[" + ", ".join([f'"{key}"' for key in allowed_extra_keys]) + "]"
+
+
+class Category:
+ """
+ Data struct holding information about a metric to be used in the template.
+ """
+
+ def __init__(
+ self,
+ name: str,
+ objs: Dict[str, Union[metrics.Metric, pings.Ping, tags.Tag]],
+ contains_pings: bool,
+ ):
+ self.name = name
+ self.objs = objs
+ self.contains_pings = contains_pings
+
+
+def output_rust(
+ objs: metrics.ObjectTree, output_dir: Path, options: Optional[Dict[str, Any]] = None
+) -> None:
+ """
+ Given a tree of objects, output Rust code to `output_dir`.
+
+ :param objs: A tree of objects (metrics and pings) as returned from
+ `parser.parse_objects`.
+ :param output_dir: Path to an output directory to write to.
+ :param options: options dictionary, not currently used for Rust
+ """
+
+ if options is None:
+ options = {}
+
+ template = util.get_jinja2_template(
+ "rust.jinja2",
+ filters=(
+ ("rust", rust_datatypes_filter),
+ ("snake_case", util.snake_case),
+ ("camelize", util.camelize),
+ ("type_name", type_name),
+ ("extra_type_name", extra_type_name),
+ ("ctor", ctor),
+ ("extra_keys", extra_keys),
+ ),
+ )
+
+ filename = "glean_metrics.rs"
+ filepath = output_dir / filename
+ categories = []
+
+ for category_key, category_val in objs.items():
+ contains_pings = any(
+ isinstance(obj, pings.Ping) for obj in category_val.values()
+ )
+
+ cat = Category(category_key, category_val, contains_pings)
+ categories.append(cat)
+
+ with filepath.open("w", encoding="utf-8") as fd:
+ fd.write(
+ template.render(
+ parser_version=__version__,
+ categories=categories,
+ extra_metric_args=util.extra_metric_args,
+ common_metric_args=util.common_metric_args,
+ )
+ )