#!/usr/bin/env python3 # SPDX-FileCopyrightText: 2026 Coffey Labs LLC # SPDX-License-Identifier: AGPL-3.0-only """ Generate INBUXA's registry code from the public schema file. python3 generate.py --edition agpl --out out/ python3 generate.py --edition full --out out/ Reads resources/schema/schema.json.gz (and, for the agpl edition, resources/schema/schema_oss.json.gz, which is what tells open enum variants from Enterprise ones) and writes: crates/registry/src/schema/{enums,enums_impl,properties,properties_impl, structs,structs_impl}.rs Everything the schema does not carry (field declaration order, index definitions, expression contexts, and so on) lives in special_cases.py. The output is formatted with `rustfmt --edition 2024` (rustfmt 1.x), which must be on PATH. In the agpl edition, Enterprise content is first emitted between marker comments, formatted together with the open code, and then removed line by line; this is the same process that produced the reference files, so the layout artifacts it leaves behind come out identical. """ import argparse import gzip import json import os import re import subprocess import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent)) import special_cases as SC # noqa: E402 HEADER = """/* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ // This file is auto-generated. Do not edit directly. """ # The full edition is ours: it carries the fork's notice line as well. MODIFIED = """ * * Modified by Coffey Labs in 2026 for INBUXA. """ # Marker comments around Enterprise-only lines (agpl edition only). MARK_BEGIN = '// @@ENTERPRISE-BEGIN' MARK_END = '// @@ENTERPRISE-END' CFG_ENT = '#[cfg(feature = "enterprise")]' CFG_NOT_ENT = '#[cfg(not(feature = "enterprise"))]' # --------------------------------------------------------------------------- # Naming # --------------------------------------------------------------------------- def variant_ident(name): """Schema enum value / variant name -> Rust identifier.""" name = SC.IDENT_SOURCE_NAMES.get(name, name) name = name.replace('+', 'Plus') name = re.sub(r'(?<=GMT)-(?=\d)', 'Minus', name) parts = [p for p in re.split(r'[^A-Za-z0-9]+', name) if p] ident = ''.join(p[0].upper() + p[1:] for p in parts) if ident[:1].isdigit(): ident = 'V' + ident return ident RUST_KEYWORDS = set('''as async await break const continue crate dyn else enum extern false fn for if impl in let loop match mod move mut pub ref return self static struct super trait true type unsafe use where while abstract become box do final macro override priv typeof unsized virtual yield try gen'''.split()) def snake(name): """camelCase property name -> snake_case Rust field name.""" s = re.sub(r'([A-Z])', r'_\1', name).lower() return s + '_' if s in RUST_KEYWORDS else s def pascal(name): """camelCase property name -> PascalCase `Property` variant.""" name = name.lstrip('@') return SC.PROPERTY_IDENTS.get(name, name[:1].upper() + name[1:]) def strip_x(name): return name[2:] if name.startswith('x:') else name # --------------------------------------------------------------------------- # Schema model # --------------------------------------------------------------------------- def load_gz_json(path): return json.loads(gzip.decompress(Path(path).read_bytes())) class Schema: def __init__(self, full, oss, edition): self.d = full self.oss = oss self.edition = edition self.agpl = edition == 'agpl' # ---- Enterprise classification (agpl only) ---- def ent_enum(self, name): return self.oss is not None and name not in self.oss['enums'] def oss_variant_names(self, enum): names = [v['name'] for v in self.oss['enums'][enum]] return {SC.OSS_NAME_RENAMES.get(n, n) for n in names} def ent_variant(self, enum, name): if self.oss is None: return False if enum not in self.oss['enums']: return True return name not in self.oss_variant_names(enum) def ent_schema(self, name): return self.oss is not None and name not in self.oss['schemas'] def ent_object(self, name): return self.oss is not None and name not in self.oss['objects'] def prepare(self): d = self.d # x: schemas, alphabetical (schema.json order) self.schemas = [k for k in d['schemas'] if k.startswith('x:')] self.fields = {} for s in self.schemas: if s not in d['fields']: continue props = d['fields'][s]['properties'] order = SC.FIELD_ORDER.get(s) if order is None: order = list(props) print(f'warning: {s} has no FIELD_ORDER entry; using schema order', file=sys.stderr) missing = [p for p in props if p not in order] if missing: print(f'warning: {s}: {missing} not in FIELD_ORDER; appended', file=sys.stderr) order = list(order) + missing defaults = d['fields'][s].get('defaults', {}) virtual = SC.VIRTUAL_FIELDS.get(s, []) self.fields[s] = [Field(self, s, n, props[n], defaults, n in virtual) for n in order if n in props] # Property ids: first sighting, walking schemas alphabetically. self.prop_ids = {'@type': 0, 'id': 1, 'text': 2} for s in sorted(self.fields): for f in self.fields[s]: self.prop_ids.setdefault(f.name, len(self.prop_ids)) # Properties used only by Enterprise fields. used_open = {'@type', 'id', 'text'} for s, fs in self.fields.items(): if self.ent_schema(s): continue for f in fs: if not f.ent: used_open.add(f.name) self.ent_props = {n for n in self.prop_ids if n not in used_open} # Registry objects: x: objects and singletons, alphabetical. self.objects = [k for k, v in d['objects'].items() if k.startswith('x:') and v['type'] in ('object', 'singleton')] def schema_fields(self, s, include_ent=None): """Fields of a schema for this edition (all fields in full mode).""" return self.fields.get(s, []) def ent_field(self, schema, field): if self.oss is None: return False if self.d['fields'][schema]['properties'][field].get('enterprise'): return True of = self.oss['fields'].get(schema) return of is None or field not in of['properties'] class Field: def __init__(self, schema, sname, name, prop, defaults, virtual): self.s = schema self.schema = sname self.name = name self.prop = prop self.t = prop['type'] self.update = prop['update'] self.virtual = virtual self.has_default = name in defaults self.default = defaults.get(name) self.ent = schema.ent_field(sname, name) self.rust = snake(name) self.pident = pascal(name) self.nullable = bool(self.t.get('nullable')) # --------------------------------------------------------------------------- # Line buffer with Enterprise regions # --------------------------------------------------------------------------- class Out: def __init__(self, schema): self.s = schema self.lines = [] def __call__(self, *lines): for l in lines: self.lines.extend(l.split('\n')) def ent(self, lines): """Lines that only exist in the Enterprise build.""" if isinstance(lines, str): lines = lines.split('\n') if self.s.agpl: self.lines.append(MARK_BEGIN) self.lines.extend(lines) self.lines.append(MARK_END) else: self.lines.extend(lines) def agpl_only(self, lines): """Lines that only exist in the open build (cfg(not(enterprise)) remnants).""" if isinstance(lines, str): lines = lines.split('\n') if self.s.agpl: self.lines.extend(lines) def text(self): return '\n'.join(self.lines) + '\n' def strip_enterprise(text): out, depth = [], 0 for line in text.split('\n'): if MARK_BEGIN in line: depth += 1 continue if MARK_END in line: depth -= 1 continue if not depth: out.append(line) return '\n'.join(out) def rustfmt(text): r = subprocess.run(['rustfmt', '--edition', '2024', '--emit', 'stdout'], input=text, capture_output=True, text=True, cwd='/') if r.returncode != 0: sys.stderr.write(r.stderr) raise SystemExit('rustfmt failed') return r.stdout def finish(schema, body, fmt=True): header = HEADER if not schema.agpl: header = header.replace(' */\n', MODIFIED + ' */\n', 1) text = header + body if os.environ.get('REGISTRY_GEN_RAW'): Path(os.environ['REGISTRY_GEN_RAW']).write_text(text) if fmt: text = rustfmt(text) if schema.agpl: text = strip_enterprise(text).rstrip('\n') + '\n' return text # --------------------------------------------------------------------------- # enums.rs / enums_impl.rs # --------------------------------------------------------------------------- def generated_enums(s): for name in s.d['enums']: if name in SC.EXTERNAL_ENUMS: continue yield name # Variant enums schema.json lost to a name clash (see VARIANT_ENUM_NAMES). yield from SC.VARIANT_ENUM_NAMES.values() def enum_variants(s, name): """[(ident, schema name, id, enterprise)] in schema order; id = list index.""" for sch, ename in SC.VARIANT_ENUM_NAMES.items(): if ename == name: ent = s.agpl and s.ent_schema(sch) return [(variant_ident(v['name']), v['name'], i, ent) for i, v in enumerate(s.d['schemas'][sch]['variants'])] return [(variant_ident(v['name']), v['name'], i, s.ent_variant(name, v['name'])) for i, v in enumerate(s.d['enums'][name])] def enum_is_ent(s, name): for sch, ename in SC.VARIANT_ENUM_NAMES.items(): if ename == name: return s.agpl and s.ent_schema(sch) return s.agpl and s.ent_enum(name) def gen_enums(s): o = Out(s) first = True for name in generated_enums(s): vs = enum_variants(s, name) item = ['#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]', '#[repr(u16)]', f'pub enum {name} {{'] body = [] first_open = next((i for i, v in enumerate(vs) if not v[3]), 0) if s.agpl else 0 for i, (ident, _, vid, ent) in enumerate(vs): if s.agpl and ent: attr = ['#[cfg_attr(feature = "enterprise", default)]'] if i == 0 else [] body.append(('ent', attr + [f'{ident} = {vid},'])) else: if i == 0: attr = ['#[default]'] elif s.agpl and i == first_open: attr = ['#[cfg_attr(not(feature = "enterprise"), default)]'] else: attr = [] body.append(('open', attr + [f'{ident} = {vid},'])) if enum_is_ent(s, name): o.ent(item + flatten(body) + ['}', '']) continue if not first: pass o(*item) emit_body(o, body) o('}', '') first = False for name, ty, values in SC.EXPRESSION_STATICS: o(f'pub static {name}: &[{ty}] = &[') for v in values: o(f' {ty}::{v},') o('];', '') return finish(s, o.text().rstrip('\n') + '\n') def flatten(body): out = [] for _, lines in body: out.extend(lines) return out def emit_body(o, body): for kind, lines in body: if kind == 'ent': o.ent(lines) else: o(*lines) def gen_enums_impl(s): o = Out(s) o('use crate::schema::prelude::*;', '') for name in generated_enums(s): emit_enum_impl(o, s, name, enum_variants(s, name), enum_is_ent(s, name), name in SC.ENUMS_WITH_ALL) return finish(s, o.text().rstrip('\n') + '\n') def emit_enum_impl(o, s, name, vs, whole_ent=False, with_all=False): """EnumImpl + serde impls. vs = [(ident, json name, id, enterprise)].""" has_ent = s.agpl and any(v[3] for v in vs) start = len(o.lines) o(f'impl EnumImpl for {name} {{') def parse_fn(variants, cfg): # rustfmt may leave the macro body alone, so indent it as final. lines = ([f' {cfg}'] if cfg else []) + [ ' fn parse(value: &str) -> Option {', ' hashify::map! {', ' value.as_bytes(),', f' {name},'] for ident, sname, _, _ in variants: lines.append(f' b"{sname}" => {name}::{ident},') lines += [' }', ' .copied()', ' }'] return lines def arm(text, ent): if ent and not whole_ent: o.ent([text]) elif not ent or s.agpl or True: o(text) if has_ent and not whole_ent: o(*parse_fn([v for v in vs if not v[3]], CFG_NOT_ENT)) o.ent(parse_fn(vs, CFG_ENT)) else: o(*parse_fn(vs, None)) o('', 'fn as_str(&self) -> &\'static str {', ' match self {') for ident, sname, _, ent in vs: arm(f'{name}::{ident} => "{sname}",', ent) o(' }', '}', '') o('fn to_id(&self) -> u16 {', ' *self as u16', '}', '') o('fn from_id(id: u16) -> Option {', ' match id {') for ident, sname, vid, ent in vs: arm(f'{vid} => Some({name}::{ident}),', ent) o(' _ => None,', ' }', '}', '') o(f'const COUNT: usize = {max(v[2] for v in vs) + 1};', '}', '') if with_all: o(f'impl {name} {{', f' pub const ALL: &[{name}] = &[') for ident, sname, vid, ent in vs: arm(f'{name}::{ident},', ent) o(' ];', '}', '') o(f'impl serde::Serialize for {name} {{', ' fn serialize(&self, serializer: S) -> Result', ' where', ' S: serde::Serializer,', ' {', ' serializer.serialize_str(self.as_str())', ' }', '}', '', f'impl<\'de> serde::Deserialize<\'de> for {name} {{', ' fn deserialize(deserializer: D) -> Result', ' where', ' D: serde::Deserializer<\'de>,', ' {', ' let s = Cow::::deserialize(deserializer)?;', ' Self::parse(&s).ok_or_else(|| serde::de::Error::unknown_variant(&s, &[]))', ' }', '}', '') if whole_ent: block = o.lines[start:] del o.lines[start:] o.ent(block) # --------------------------------------------------------------------------- # properties.rs / properties_impl.rs # --------------------------------------------------------------------------- def object_variants(s): return [(strip_x(n), strip_x(n), i, s.agpl and s.ent_object(n)) for i, n in enumerate(s.objects)] def property_variants(s): vs = [(pascal(n), n, i, s.agpl and n in s.ent_props) for n, i in s.prop_ids.items()] return sorted(vs, key=lambda v: (v[1] != '@type', v[0])) def gen_properties(s): o = Out(s) o('use crate::schema::prelude::*;') o('#[derive(Debug, Clone, Serialize, PartialEq, Eq)]', '#[serde(untagged)]', 'pub enum ObjectInner {') for ident, _, _, ent in object_variants(s): line = f'{ident}(Box<{ident}>),' o.ent([line]) if ent else o(line) o('}', '') o('#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]', '#[repr(u16)]', 'pub enum ObjectType {') for ident, _, i, ent in object_variants(s): line = f'{ident} = {i},' o.ent([line]) if ent else o(line) o('}', '') o('#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]', '#[repr(u16)]', 'pub enum Property {') for ident, _, i, ent in property_variants(s): line = f'{ident} = {i},' o.ent([line]) if ent else o(line) o('}') return finish(s, o.text()) # --------------------------------------------------------------------------- # Rust types # --------------------------------------------------------------------------- STRING_FORMAT_TYPES = { 'ipAddress': 'IpAddr', 'ipNetwork': 'IpAddrOrMask', 'socketAddress': 'SocketAddr', } NUMBER_FORMAT_TYPES = { 'duration': 'Duration', 'unsignedInteger': 'u64', 'size': 'u64', 'integer': 'i64', 'float': 'Float', } def enum_type(name): return SC.EXTERNAL_ENUMS.get(name) or name def base_type(t): """Rust type of a schema type, ignoring nullability.""" k = t['type'] if k == 'string': return STRING_FORMAT_TYPES.get(t.get('format'), 'String') if k == 'number': return NUMBER_FORMAT_TYPES[t['format']] if k == 'boolean': return 'bool' if k == 'utcDateTime': return 'UTCDateTime' if k == 'enum': return enum_type(t['enumName']) if k == 'object': return strip_x(t['objectName']) if k == 'objectId': return 'Id' if k == 'blobId': return 'BlobId' if k == 'objectList': return f"List<{strip_x(t['objectName'])}>" if k == 'set': return f"Map<{base_type(t['class'])}>" if k == 'map': return f"VecMap<{base_type(t['keyClass'])}, {base_type(t['valueClass'])}>" raise ValueError(t) def rust_type(f): ty = SC.RUST_TYPE_OVERRIDES.get((f.schema, f.name)) or base_type(f.t) return f'Option<{ty}>' if f.nullable else ty def schema_kind(s, name): return s.d['schemas'][name]['type'] def variants(s, name): """[(ident, json name, payload schema or None, enterprise)]""" out = [] enum = None for v in s.d['schemas'][name]['variants']: out.append((variant_ident(v['name']), v['name'], v.get('schemaName'), s.agpl and v.get('schemaName') is not None and s.ent_schema(v['schemaName']))) return out def variant_enum(name): """Name of the enum listing a multi-variant schema's variants.""" return SC.VARIANT_ENUM_NAMES.get(name, strip_x(name) + 'Type') def edition_schemas(s): """x: schemas to generate, alphabetical, with their Enterprise flag.""" return [(n, s.agpl and s.ent_schema(n)) for n in s.schemas] # --------------------------------------------------------------------------- # structs.rs # --------------------------------------------------------------------------- def gen_structs(s): o = Out(s) o('use crate::schema::prelude::*;') for name, ent in edition_schemas(s): lines = ['#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]'] rn = strip_x(name) body = [] if schema_kind(s, name) == 'multiple': lines += ['#[serde(tag = "@type")]', f'pub enum {rn} {{'] for ident, jname, payload, vent in variants(s, name): line = f'{ident}({strip_x(payload)}),' if payload else f'{ident},' body.append(('ent' if vent else 'open', [line])) else: lines += ['#[serde(default)]', f'pub struct {rn} {{'] for f in s.fields[name]: if f.virtual: continue fl = [] if f.rust != f.name: fl.append(f'#[serde(rename = "{f.name}")]') fl.append(f'pub {f.rust}: {rust_type(f)},') body.append(('ent' if f.ent else 'open', fl)) if ent: o.ent(lines + flatten(body) + ['}', '']) else: o(*lines) emit_body(o, body) o('}', '') return finish(s, o.text().rstrip('\n') + '\n') # --------------------------------------------------------------------------- # Field helpers shared by structs_impl.rs and properties_impl.rs # --------------------------------------------------------------------------- def kind_of(t): k = t['type'] if k == 'string': return {'ipAddress': 'ip', 'ipNetwork': 'ipmask', 'socketAddress': 'socket'}.get( t.get('format'), 'string') if k == 'number': return {'duration': 'duration', 'unsignedInteger': 'u64', 'size': 'u64', 'integer': 'i64', 'float': 'float'}[t['format']] return {'boolean': 'bool', 'utcDateTime': 'datetime', 'enum': 'enum', 'object': 'object', 'objectId': 'id', 'blobId': 'blob', 'objectList': 'list', 'set': 'set', 'map': 'map'}[k] def fkind(f): ov = SC.RUST_TYPE_OVERRIDES.get((f.schema, f.name)) if ov == 'Id': return 'id' return kind_of(f.t) def is_expr(t): return t['type'] == 'object' and t.get('objectName') == 'x:Expression' def is_secret(f): return (f.t.get('format') in ('secret', 'secretText') or (f.schema, f.name) in SC.MASKED_FIELDS) def parse_duration(v): """Schema duration default -> milliseconds.""" if isinstance(v, (int, float)): return int(v) m = re.fullmatch(r'(\d+)\s*(ms|s|m|h|d|w)?', v.strip()) if not m: raise ValueError(v) n = int(m.group(1)) return n * {'ms': 1, None: 1, 's': 1000, 'm': 60000, 'h': 3600000, 'd': 86400000, 'w': 604800000}[m.group(2)] def rust_str(v): v = SC.STRING_LITERAL_RENAMES.get(v, v) return '"' + v.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n') + '"' def float_lit(v): r = repr(float(v)) return r def num_literal(t, v, f=None): k = kind_of(t) if k == 'duration': return f'Duration::from_millis({parse_duration(v)})' if k == 'float': return f'Float::new({float_lit(v)}f64)' if k == 'i64': return f'{int(v)}i64' if k == 'u64': if t.get('format') == 'size' and (f is None or (f.schema, f.name) not in SC.SIZE_DEFAULT_SUFFIX): return f'{int(v)}' return f'{int(v)}u64' raise ValueError(t) def schema_struct_fields(s, name): """Stored (non-virtual) fields of a single schema, in declaration order.""" return [f for f in s.fields[name] if not f.virtual] def default_expr(s, f, t=None, value=None): """Rust expression for a field's default (None -> Default::default()).""" t = t or f.t if value is None: if not f.has_default: return None value = f.default k = kind_of(t) nullable = t.get('nullable') inner = value_literal(s, t, value, f) if inner is None: return None return f'Some({inner})' if nullable else inner def value_literal(s, t, v, f=None): k = kind_of(t) if k == 'string': if v is None: return None return f'{rust_str(v)}.to_string()' if k in ('ip', 'ipmask', 'socket'): return f'{base_type(t)}::from_str({rust_str(v)}).unwrap()' if k in ('u64', 'i64', 'float', 'duration'): return num_literal(t, v, f) if k == 'bool': return 'true' if v else 'false' if k == 'enum': return f"{enum_type(t['enumName'])}::{variant_ident(v)}" if k == 'set': keys = set_default_order(f, v) if t['class']['type'] == 'enum' and t['class']['enumName'] in s.d['enums']: order = [e['name'] for e in s.d['enums'][t['class']['enumName']]] keys = sorted(keys, key=lambda x: order.index(x) if x in order else len(order)) items = [value_literal(s, t['class'], x) for x in keys] return f"Map::new(vec![{', '.join(items)}])" if k == 'object': return object_literal(s, t['objectName'], v) if k == 'list': # objectList defaults are JSON objects keyed by position ("0", "1", ...). vals = [v[k] for k in sorted(v, key=int)] if isinstance(v, dict) else v items = [object_literal(s, t['objectName'], x) for x in vals] return f"List::from_iter([{', '.join(items)}])" if k == 'map': items = [f"({value_literal(s, t['keyClass'], key)}, {value_literal(s, t['valueClass'], val)})" for key, val in v.items()] return f"VecMap::from_iter([{', '.join(items)}])" raise ValueError((t, v)) def set_default_order(f, v): keys = list(v) if isinstance(v, dict) else list(v) if f is not None: order = SC.SET_DEFAULT_ORDER.get((f.schema, f.name)) if order: return order return keys def object_literal(s, oname, v): name = strip_x(oname) if oname == 'x:Expression': return expression_literal(v) sch = s.d['schemas'][oname] if sch['type'] == 'multiple': vname = v['@type'] var = next(x for x in sch['variants'] if x['name'] == vname) ident = variant_ident(vname) if not var.get('schemaName'): return f'{name}::{ident}' rest = {k: x for k, x in v.items() if k != '@type'} if not rest: return f'{name}::{ident}(Default::default())' return f"{name}::{ident}({struct_literal(s, var['schemaName'], rest)})" return struct_literal(s, oname, v) def struct_literal(s, sname, v): fields = schema_struct_fields(s, sname) parts = [] for f in fields: if f.name in v: parts.append(f'{f.rust}: {default_expr(s, f, value=v[f.name])}') if len(parts) < len(fields): parts.append('..Default::default()') return f"{strip_x(sname)} {{ {', '.join(parts)} }}" def expression_literal(v): """Expression default: else_ first, then match_ when the key is present.""" if isinstance(v, str): v = {'default': v} parts = [] e = v.get('else', v.get('default')) if e is not None: parts.append(f'else_: {rust_str(e)}.to_string()') if 'match' in v: ms = ', '.join(f"ExpressionMatch {{ if_: {rust_str(m['if'])}.to_string(), " f"then: {rust_str(m['then'])}.to_string() }}" for m in v['match']) parts.append(f'match_: List::from_iter([{ms}])') else: parts.append('..Default::default()') return f"Expression {{ {', '.join(parts)} }}" # --------------------------------------------------------------------------- # Derived model shared by structs_impl.rs and properties_impl.rs # --------------------------------------------------------------------------- def type_children(t): """Schema names referenced by a field type (objects, lists, sets, maps).""" k = t['type'] if k in ('object', 'objectList'): return [t['objectName']] out = [] for c in ('class', 'keyClass', 'valueClass'): if c in t: out += type_children(t[c]) return out class Model: """Facts computed over the full schema (Enterprise included).""" def __init__(self, s): self.s = s d = s.d self.object_set = set(s.objects) self.perms = {v['name'] for v in d['enums']['Permission']} # Types reachable from stored objects get a Pickle impl. self.pickled = set() stack = [o for o in s.objects if o not in SC.NOT_STORED_OBJECTS] while stack: n = stack.pop() if n in self.pickled or n not in d['schemas']: continue self.pickled.add(n) stack.extend(self.children(n)) # Types with an index function (fixed point). self.indexable = set() changed = True while changed: changed = False for n in s.schemas: if n not in self.indexable and self._indexable(n): self.indexable.add(n) changed = True # Types with expression contexts (fixed point over multi variants). self.has_ctx = {n for n in s.schemas if schema_kind(s, n) == 'single' and any(self.ctx_of(f) for f in schema_struct_fields(s, n))} changed = True while changed: changed = False for n in s.schemas: if n in self.has_ctx or schema_kind(s, n) != 'multiple': continue if any(v[2] in self.has_ctx for v in variants(s, n) if v[2]): self.has_ctx.add(n) changed = True def children(self, n): sch = self.s.d['schemas'][n] if sch['type'] == 'multiple': return [v['schemaName'] for v in sch['variants'] if v.get('schemaName')] out = [] for f in schema_struct_fields(self.s, n): out += type_children(f.t) return out def _indexable(self, n): s = self.s # Only stored types are indexed (an object's own index fn always exists). if n not in self.pickled: return False if schema_kind(s, n) == 'multiple': if n in SC.TYPE_INDEXED_OBJECTS: return True return any(v[2] in self.indexable for v in variants(s, n) if v[2]) for f in schema_struct_fields(s, n): if index_ops(f) or fk_target(f) or self.nested_index(f): return True return False def nested_index(self, f): k = kind_of(f.t) if k in ('object', 'list'): return f.t['objectName'] in self.indexable if k == 'map': vc = f.t['valueClass'] return vc['type'] == 'object' and vc['objectName'] in self.indexable return False def ctx_of(self, f): if not is_expr(f.t): return None return SC.EXPRESSION_CONTEXTS.get((f.schema, f.name)) def object_flags(self, n): s = self.s obj = s.d['objects'][n] flags = [] singleton = obj['type'] == 'singleton' if singleton: flags.append('OBJ_SINGLETON') var_fields = self.variant_field_names(n) if var_fields and all('accountId' in fs for fs in var_fields): flags.append('OBJ_FILTER_ACCOUNT') if any('memberTenantId' in fs for fs in var_fields): flags.append('OBJ_FILTER_TENANT') if n in SC.SEQ_ID_OBJECTS: flags.append('OBJ_SEQ_ID') if n in SC.NOT_STORED_OBJECTS: flags.append('OBJ_NOT_STORED') prefix = obj['permissionPrefix'] ops = ['Get', 'Update'] if singleton else ['Get', 'Create', 'Update', 'Destroy', 'Query'] for op in ['Get', 'Create', 'Update', 'Destroy', 'Query']: if op in ops and prefix + op not in self.perms: flags.append('OBJ_NO_' + op.upper()) return ' | '.join(flags) or '0' def variant_field_names(self, n): s = self.s if schema_kind(s, n) == 'multiple': return [{f.name for f in s.fields.get(v[2], [])} if v[2] else set() for v in variants(s, n)] return [{f.name for f in s.fields.get(n, [])}] def index_ops(f): return SC.INDEX_OPS.get(f.schema, {}).get(f.name, []) def fk_target(f): """(ObjectType ident, view filter or None) for objectId fields.""" t = f.t while t['type'] == 'set': t = t['class'] if t['type'] != 'objectId': return None obj = strip_x(t['objectName']) if '/' in obj: base, view = obj.split('/', 1) return base, f'Some({base}Type::{variant_ident(view)}.to_id())' return obj, 'None' def is_set(f): return f.t['type'] == 'set' # --------------------------------------------------------------------------- # structs_impl.rs # --------------------------------------------------------------------------- def P(f): return f'Property::{f.pident}' def scalar_checks(f, t, prop): """Validation lines for one value (`value: &T`).""" k = kind_of(t) ov = SC.RUST_TYPE_OVERRIDES.get((f.schema, f.name)) if ov == 'Id': k = 'id' elif ov and ov.startswith('Map {n} {{', f' errors.push(ValidationError::max_length({prop}, {n}));', '}'] elif k == 'blob': lines += ['if value.is_empty() {', f' errors.push(ValidationError::required({prop}));', '}'] elif k == 'id' and ov != 'Id': lines += ['if !value.is_valid() {', f' errors.push(ValidationError::required({prop}));', '}'] elif k in ('datetime', 'ip', 'ipmask', 'socket', 'id'): lines += ['if !value.is_valid() {', f' errors.push(ValidationError::invalid({prop}, value));', '}'] elif k in ('u64', 'i64', 'float', 'duration'): if k == 'duration' and ('min' in t or 'max' in t): lines += ['if !value.is_valid() {', f' errors.push(ValidationError::invalid({prop}, value));', '}'] bounds = [('min', '<', 'min_value'), ('max', '>', 'max_value')] if (f.schema, f.name) in SC.VALIDATE_MAX_FIRST: bounds.reverse() for key, op, fn in bounds: if key in t: lit, push = bound_literals(t, t[key]) lines += [f'if *value {op} {lit} {{', f' errors.push(ValidationError::{fn}({prop}, {push}));', '}'] elif k == 'object': if (f.schema, f.name) in SC.VALIDATE_IF_NOT_EMPTY: lines += ['if !value.match_.is_empty() || !value.else_.is_empty() {', ' value.validate(errors);', '}'] else: lines += ['value.validate(errors);'] return lines def bound_literals(t, v): k = kind_of(t) if k == 'duration': ms = parse_duration(v) return f'Duration::from_millis({ms})', f'{ms}' if k == 'float': return f'Float::new({float_lit(v)})', f'{int(v)}' if float(v).is_integer() else f'{v}' if k == 'i64': return f'({int(v)})', f'{int(v)}' return f'{int(v)}', f'{int(v)}' def validate_lines(f): t = f.t k = kind_of(t) prop = P(f) if k == 'set': inner = scalar_checks(f, t['class'], prop) lines = (['for value in value.iter() {'] + indent(inner) + ['}']) if inner else [] if 'minItems' in t: n = t['minItems'] lines += [f'if value.len() < {n} {{', f' errors.push(ValidationError::min_items({prop}, {n}));', '}'] elif k in ('list', 'map'): inner_t = ({'type': 'object', 'objectName': t['objectName']} if k == 'list' else t['valueClass']) inner = scalar_checks(f, inner_t, prop) lines = (['for value in value.values() {'] + indent(inner) + ['}']) if inner else [] if 'minItems' in t: n = t['minItems'] lines += [f'if value.len() < {n} {{', f' errors.push(ValidationError::min_items({prop}, {n}));', '}'] else: lines = scalar_checks(f, t, prop) if not lines: return [] if f.nullable: return [f'if let Some(value) = &self.{f.rust} {{'] + indent(lines) + ['}'] return [f'let value = &self.{f.rust};'] + lines def indent(lines, n=1): return [(' ' * n + l) if l else l for l in lines] def index_lines(m, f): out = [] fk = fk_target(f) if fk: obj, view = fk if is_set(f): out += [f'for id in self.{f.rust}.iter() {{', f' i.foreign_key(ObjectType::{obj}, Some(*id), {view});', '}'] elif f.nullable: out += [f'i.foreign_key(ObjectType::{obj}, self.{f.rust}, {view});'] else: out += [f'i.foreign_key(ObjectType::{obj}, self.{f.rust}.into(), {view});'] for op in index_ops(f): kind = op[0] if kind == 'composite': _, cprop, how, other = op arg = (f'IndexValue::U32(self.{other}.document_id())' if how == 'docid' else f'&self.{other}') out += [f'i.unique_global_composite(Property::{cprop}, &self.{f.rust}, {arg});'] continue call = f'i.{kind}(Property::{op[1]}, ' if is_set(f): out += [f'for value in self.{f.rust}.iter() {{', f' {call}value);', '}'] elif f.nullable: out += [f'if let Some(value) = &self.{f.rust} {{', f' {call}value);', '}'] else: out += [f'{call}&self.{f.rust});'] if m.nested_index(f): k = kind_of(f.t) if k == 'object': if f.nullable: out += [f'if let Some(value) = &self.{f.rust} {{', ' value.index(i);', '}'] else: out += [f'self.{f.rust}.index(i);'] else: out += [f'for item in self.{f.rust}.values() {{', ' item.index(i);', '}'] return out def emit_fields(o, s, fields, fn): """Emit fn(f) lines for each field, Enterprise fields wrapped.""" any_open = False for f in fields: lines = fn(f) if not lines: continue if f.ent: o.ent(lines) else: any_open = True o(*lines) return any_open def has_lines(fields, fn, ent=None): return any(fn(f) for f in fields if ent is None or f.ent == ent) def emit_validate_fn(o, s, fields): if not any(validate_lines(f) for f in fields): o('fn validate(&self, _: &mut Vec) -> bool {', ' true', '}') return o('fn validate(&self, errors: &mut Vec) -> bool {', ' let neb = errors.len();') emit_fields(o, s, fields, validate_lines) o(' errors.len() == neb', '}') def emit_index_fn(o, m, s, fields, pub=False): fn = lambda f: index_lines(m, f) open_ = has_lines(fields, fn, False) ent = has_lines(fields, fn, True) if not open_ and not ent: o("fn index<'x>(&'x self, _: &mut IndexBuilder<'x>) {}") return if not open_ and s.agpl: o('#[cfg_attr(not(feature = "enterprise"), allow(unused_variables))]') o("fn index<'x>(&'x self, i: &mut IndexBuilder<'x>) {") emit_fields(o, s, fields, fn) o('}') def emit_trailing_ent(o, s, name): if name in SC.TRAILING_ENTERPRISE_ITEM and s.agpl: o('') o.ent([]) def emit_single(o, m, s, name): rn = strip_x(name) fields = schema_struct_fields(s, name) allf = s.fields[name] is_obj = name in m.object_set # 1. ObjectImpl / inherent validate (+ index) if is_obj: o(f'impl ObjectImpl for {rn} {{', f' const FLAGS: u64 = {m.object_flags(name)};', ' const VERSION: u8 = 0;', f' const OBJECT: ObjectType = ObjectType::{rn};', '') emit_validate_fn(o, s, fields) o('') emit_index_fn(o, m, s, fields) else: o(f'impl {rn} {{') emit_validate_fn(o, s, fields) if name in m.indexable: o('') emit_index_fn(o, m, s, fields) emit_trailing_ent(o, s, name) o('}', '') # 2. Expression contexts ctxf = [f for f in fields if m.ctx_of(f)] if ctxf: o(f'impl {rn} {{') for f in ctxf: vars_, consts = m.ctx_of(f) d = default_expr(s, f) lines = [f'pub fn ctx_{f.rust}(&self) -> ExpressionContext<\'_> {{', ' ExpressionContext {', f' expr: &self.{f.rust},', f' default: {"Some(" + d + ")" if d else "None"},', f' property: {P(f)},', f' allowed_variables: {vars_ or "&[]"},', f' allowed_constants: {consts or "&[]"},', ' }', '}', ''] o.ent(lines) if f.ent else o(*lines) calls = [] o('pub fn expression_ctxs(&self) -> Vec> {') if any(f.ent for f in ctxf) and s.agpl: # vec![] with Enterprise entries (formatted vertically). o(' vec![') for f in ctxf: line = f' self.ctx_{f.rust}(),' o.ent([line]) if f.ent else o(line) o(' ]') else: o(' vec![' + ', '.join(f'self.ctx_{f.rust}()' for f in ctxf) + ']') o('}', '}', '') # 3. Pickle if name in m.pickled: emit_pickle_single(o, m, s, name, fields, is_obj) # 4. Default o(f'impl Default for {rn} {{', ' fn default() -> Self {', ' Self {') for f in fields: d = (SC.DEFAULT_OVERRIDES.get((f.schema, f.name), {}).get('default') or default_expr(s, f) or ('false' if fkind(f) == 'bool' and not f.nullable else 'Default::default()')) # Indented as final: rustfmt leaves a struct literal alone when one # of its lines (a long string default) cannot be made to fit. line = f' {f.rust}: {d},' o.ent([line]) if f.ent else o(line) o(' }', ' }', '}', '') # 5. IntoValue o(f'impl IntoValue for {rn} {{', " fn into_value(self) -> JmapValue<'static> {", ' self.into_value_with(ValueOptions::default())', ' }', '', " fn into_value_with(self, opts: ValueOptions) -> JmapValue<'static> {", f' let mut map = jmap_tools::Map::with_capacity({len(fields) + 2});') for f in fields: val = f'self.{f.rust}.into_value_with(opts)' if is_secret(f): val = (f'if opts.include_secrets {{ {val} }} else ' '{ JmapValue::Str(MASKED_PASSWORD.into()) }') line = f'map.insert_unchecked({P(f)}, {val});' if is_secret(f) and f.nullable: line = f'if self.{f.rust}.is_some() {{ {line} }}' o.ent([line]) if f.ent else o(line) o(' JmapValue::Object(map)', ' }', '}', '') # 6. Patch o(f'impl RegistryJsonPropertyPatch for {rn} {{', " fn patch_property<'x>(&mut self, mut pointer: JsonPointerPatch<'_>, value: JmapValue<'x>) -> PatchResult<'x> {", ' match pointer.next_property() {') for f in allf: line = patch_arm(f) o.ent([line]) if f.ent else o(line) o(' Some(Property::Type) => Ok(MaybeUnpatched::Unpatched { property: Property::Type, value }),', ' _ => Err(PatchError::new(pointer, "Invalid property")),', ' }', ' }', '}', '') def string_validators(f): v = SC.STRING_VALIDATORS.get((f.schema, f.name)) if v is not None: return v t = f.t while t['type'] == 'set': t = t['class'] if t['type'] == 'string' and t.get('format') == 'emailAddress' and f.update != 'serverSet': return ['Email'] return [] def patch_arm(f): arm = f'Some({P(f)})' if f.update == 'serverSet': return f'{arm} => pointer.assert_server_set(),' if f.virtual: return f'Some(property @ {P(f)}) => Ok(MaybeUnpatched::Unpatched {{ property, value }}),' ptr = 'pointer' if f.update == 'immutable': ptr += '.assert_read_only()?' if f.name == 'accountId': ptr += '.assert_can_set_account()?' if f.name == 'memberTenantId': # inbuxa: MT, only a caller allowed to move objects between tenants ptr += '.assert_can_set_tenant()?' vs = string_validators(f) if vs: ptr += '.with_validators(&[' + ', '.join(f'StringValidator::{v}' for v in vs) + '])' return f'{arm} => self.{f.rust}.patch({ptr}, value),' def emit_pickle_single(o, m, s, name, fields, is_obj): rn = strip_x(name) flagged = [f for f in fields if f.nullable or kind_of(f.t) == 'bool'] flag_no = {f.name: i for i, f in enumerate(flagged)} o(f'impl Pickle for {rn} {{', ' fn pickle(&self, out: &mut Vec) {') if flagged: if s.agpl and all(f.ent for f in flagged): o('#[cfg_attr(not(feature = "enterprise"), allow(unused_mut))]') o('let mut bitmap = 0u64;') for f in flagged: cond = f'self.{f.rust}' if not f.nullable else f'self.{f.rust}.is_some()' lines = [f'if {cond} {{', f' bitmap |= 1 << {flag_no[f.name]};', '}'] o.ent(lines) if f.ent else o(*lines) o(f'let block = crate::pickle::begin_block(out, {len(fields)}, {len(flagged)}, ' f'{"bitmap" if flagged else "0"});') for f in fields: if kind_of(f.t) == 'bool' and not f.nullable: continue if f.nullable: lines = [f'if let Some(value) = &self.{f.rust} {{', ' value.pickle(out);', '}'] else: lines = [f'self.{f.rust}.pickle(out);'] if f.ent: if s.agpl and not f.nullable: o.agpl_only([CFG_NOT_ENT, '{', ' let block = crate::pickle::begin_block(out, 0, 0, 0);', ' crate::pickle::end_block(out, block);', '}']) o.ent(lines) else: o(*lines) o('crate::pickle::end_block(out, block);', '}', '') o("fn unpickle(stream: &mut crate::pickle::PickledStream<'_>) -> Option {") if is_obj: o('if stream.version() > Self::VERSION {', ' return None;', '}') o('let block = stream.open_block()?;') for i, f in enumerate(fields): d = (SC.DEFAULT_OVERRIDES.get((f.schema, f.name), {}).get('unpickle') or default_expr(s, f) or 'Default::default()') ty = rust_type(f) if kind_of(f.t) == 'bool' and not f.nullable: if not f.has_default: d = 'false' lines = [f'let field{i}: {ty} = if block.has({i}) {{ block.flag({flag_no[f.name]}) }} else {{ {d} }};'] elif f.nullable: lines = [f'let field{i}: {ty} = if block.has({i}) {{', f' if block.flag({flag_no[f.name]}) {{ Some(Pickle::unpickle(stream)?) }} else {{ None }}', f'}} else {{ {d} }};'] else: lines = [f'let field{i}: {ty} = if block.has({i}) {{ Pickle::unpickle(stream)? }} else {{ {d} }};'] if f.ent: if s.agpl and not f.nullable and kind_of(f.t) != 'bool': o.agpl_only([CFG_NOT_ENT, f'if block.has({i}) {{', ' let block = stream.open_block()?;', ' stream.close_block(block);', '}']) o.ent(lines) else: o(*lines) o('stream.close_block(block);', 'Some(Self {') for i, f in enumerate(fields): line = f'{f.rust}: field{i},' o.ent([line]) if f.ent else o(line) o('})', '}', '}', '') def emit_multi(o, m, s, name): rn = strip_x(name) vs = variants(s, name) is_obj = name in m.object_set def arms(fn): for idx, (ident, jname, payload, vent) in enumerate(vs): lines = fn(idx, ident, jname, payload) if lines is None: continue o.ent(lines) if vent else o(*lines) # 1. ObjectImpl / validate if is_obj: o(f'impl ObjectImpl for {rn} {{', f' const FLAGS: u64 = {m.object_flags(name)};', ' const VERSION: u8 = 0;', f' const OBJECT: ObjectType = ObjectType::{rn};', '') else: o(f'impl {rn} {{') # a type whose variants carry no payload never reads these parameters # (full edition only: no open type is like that) unit_only = not any(p for _, _, p, _ in vs) errors_arg, opts_arg = ('_errors', '_opts') if unit_only else ('errors', 'opts') o(f'fn validate(&self, {errors_arg}: &mut Vec) -> bool {{', 'match self {') arms(lambda i, v, j, p: [f'{rn}::{v}(inner) => inner.validate(errors),'] if p else [f'{rn}::{v} => true,']) o('}', '}') if is_obj or name in m.indexable: o('') if name in m.indexable: typed = name in SC.TYPE_INDEXED_OBJECTS o("fn index<'x>(&'x self, i: &mut IndexBuilder<'x>) {", 'match self {') def ix(i, v, j, p): body = [f'i.typ({i});'] if typed else [] if p and p in m.indexable: body.append('object.index(i);') return [f'{rn}::{v}(object) => {{'] + body + ['}'] pat = f'{rn}::{v}(_)' if p else f'{rn}::{v}' return [f'{pat} => {{'] + body + ['}'] arms(ix) o('}', '}') else: o("fn index<'x>(&'x self, _: &mut IndexBuilder<'x>) {}") emit_trailing_ent(o, s, name) o('}', '') # 2. Default ident, jname, payload, vent = vs[0] o(f'impl Default for {rn} {{', ' fn default() -> Self {', f' {rn}::{ident}(Default::default())' if payload else f' {rn}::{ident}', ' }', '}', '') # 3. Pickle if name in m.pickled: o(f'impl Pickle for {rn} {{', ' fn pickle(&self, out: &mut Vec) {', 'match self {') arms(lambda i, v, j, p: [f'{rn}::{v}(inner) => {{', f'{i}u16.pickle(out);', 'inner.pickle(out);', '}'] if p else [f'{rn}::{v} => {{', f'{i}u16.pickle(out);', '}']) o('}', '}', '') o("fn unpickle(stream: &mut crate::pickle::PickledStream<'_>) -> Option {") if is_obj: o('if stream.version() > Self::VERSION {', ' return None;', '}') o('match u16::unpickle(stream)? {') arms(lambda i, v, j, p: [f'{i} => Pickle::unpickle(stream).map({rn}::{v}),'] if p else [f'{i} => Some({rn}::{v}),']) o('_ => None,', '}', '}', '}', '') # 4. IntoValue o(f'impl IntoValue for {rn} {{', " fn into_value(self) -> JmapValue<'static> {", ' self.into_value_with(ValueOptions::default())', ' }', '', f" fn into_value_with(self, {opts_arg}: ValueOptions) -> JmapValue<'static> {{", 'match self {') arms(lambda i, v, j, p: [f'{rn}::{v}(obj) => {{', 'let mut obj = obj.into_value_with(opts);', f'obj.as_object_mut().unwrap().insert_unchecked(Property::Type, JmapValue::Str("{j}".into()));', 'obj', '}'] if p else [f'{rn}::{v} => {{', 'let mut obj = jmap_tools::Map::new();', f'obj.insert_unchecked(Property::Type, JmapValue::Str("{j}".into()));', 'JmapValue::Object(obj)', '}']) o('}', '}', '}', '') # 5. Patch o(f'impl RegistryJsonPatch for {rn} {{', " fn patch<'x>(&mut self, pointer: JsonPointerPatch<'_>, value: JmapValue<'x>) -> PatchResult<'x> {", 'if !pointer.has_next() {', 'match object_type(&pointer, &value)? {') ve = variant_enum(name) arms(lambda i, v, j, p: [f'{ve}::{v} => *self = {rn}::{v}(Default::default()),'] if p else [f'{ve}::{v} => *self = {rn}::{v},']) o('}', '}', 'match self {') arms(lambda i, v, j, p: [f'{rn}::{v}(inner) => inner.patch(pointer, value),'] if p else [f'{rn}::{v} => pointer.assert_eof(),']) o('}', '}', '}', '') # 6. object_type (+ expression contexts) o(f'impl {rn} {{', f'pub fn object_type(&self) -> {ve} {{', 'match self {') arms(lambda i, v, j, p: [f'{rn}::{v}(_) => {ve}::{v},'] if p else [f'{rn}::{v} => {ve}::{v},']) o('}', '}') if name in m.has_ctx: o('', "pub fn expression_ctxs(&self) -> Vec> {", 'match self {') arms(lambda i, v, j, p: [f'{rn}::{v}(obj) => obj.expression_ctxs(),'] if p and p in m.has_ctx else None) if any(not (v[2] and v[2] in m.has_ctx) for v in vs): o('_ => vec![],') o('}', '}') o('}', '') def gen_structs_impl(s): m = Model(s) o = Out(s) o('use crate::schema::prelude::*;', '') for name, ent in edition_schemas(s): start = len(o.lines) if schema_kind(s, name) == 'multiple': emit_multi(o, m, s, name) else: emit_single(o, m, s, name) if ent: block = o.lines[start:] del o.lines[start:] o.ent(block) return finish(s, o.text().rstrip('\n') + '\n') # --------------------------------------------------------------------------- # properties_impl.rs # --------------------------------------------------------------------------- INDEX_VALUE_TYPES = {'string': 'Keyword', 'id': 'Id', 'ip': 'IpMask', 'ipmask': 'IpMask', 'enum': 'Enum', 'u64': 'Number', 'i64': 'Number', 'float': 'Number', 'duration': 'Number', 'bool': 'Boolean', 'datetime': 'Number'} def index_value_type(f): t = f.t while t['type'] == 'set': t = t['class'] k = kind_of(t) if SC.RUST_TYPE_OVERRIDES.get((f.schema, f.name)) == 'Id': k = 'id' return INDEX_VALUE_TYPES[k] def index_schemas(m, name, ent=False, seen=None): """[(Property ident, IndexSchemaType, IndexSchemaValueType, enterprise)]""" s = m.s seen = set() if seen is None else seen if name in seen: return [] seen = seen | {name} out = [] if schema_kind(s, name) == 'multiple': if name in SC.TYPE_INDEXED_OBJECTS: out.append(('Type', 'Search', 'Enum', ent)) for v in variants(s, name): if v[2] and v[2] in m.indexable: out += index_schemas(m, v[2], ent or v[3], seen) return out for f in schema_struct_fields(s, name): fe = ent or f.ent for op in index_ops(f): if op[0] == 'text': out.append((op[1], 'Search', 'Text', fe)) elif op[0] in ('search', 'unique'): vt = index_value_type(f) # Enum-valued field indexes are not queryable (only @type is). if vt != 'Enum': out.append((op[1], op[0].capitalize(), vt, fe)) if m.nested_index(f): k = kind_of(f.t) child = f.t['objectName'] if k in ('object', 'list') else f.t['valueClass']['objectName'] out += index_schemas(m, child, fe, seen) return out def emit_arms(o, s, rows): """rows: [(lines, enterprise)]""" for lines, ent in rows: if isinstance(lines, str): lines = [lines] o.ent(lines) if ent else o(*lines) def emit_id_accessors(o, s, objs, json_name, rust_name): """`{rust_name}()` and `set_{rust_name}()` on ObjectInner, for every object (or variant) that has the Id field `json_name`.""" acc_rows, set_rows = [], [] for n, r, e in objs: if schema_kind(s, n) == 'multiple': vs = [v for v in variants(s, n) if v[2] and any(f.name == json_name for f in schema_struct_fields(s, v[2]))] if not vs: continue get = [([f'ObjectInner::{r}(inner) => match inner.as_ref() {{'], False)] st = [([f'ObjectInner::{r}(inner) => match inner.as_mut() {{'], False)] for ident, _, payload, vent in vs: af = next(f for f in schema_struct_fields(s, payload) if f.name == json_name) get.append(([f'{r}::{ident}(obj) => ' + (f'obj.{rust_name},' if af.nullable else f'Some(obj.{rust_name}),')], vent)) st.append(([f'{r}::{ident}(obj) => obj.{rust_name} = ' + ('Some(id),' if af.nullable else 'id,')], vent)) every = len(vs) == len(variants(s, n)) get.append(((['_ => None,'] if not every else []) + ['},'], False)) st.append(((['_ => {}'] if not every else []) + ['},'], False)) if e: acc_rows.append(([l for ls, _ in get for l in ls], True)) set_rows.append(([l for ls, _ in st for l in ls], True)) else: acc_rows += get set_rows += st else: af = next((f for f in schema_struct_fields(s, n) if f.name == json_name), None) if af is None: continue acc_rows.append((f'ObjectInner::{r}(obj) => ' + (f'obj.{rust_name},' if af.nullable else f'Some(obj.{rust_name}),'), e or af.ent)) set_rows.append((f'ObjectInner::{r}(obj) => obj.{rust_name} = ' + ('Some(id),' if af.nullable else 'id,'), e or af.ent)) o(f'pub fn {rust_name}(&self) -> Option {{', 'match self {') emit_arms(o, s, acc_rows) o('_ => None,', '}', '}', '') o(f'pub fn set_{rust_name}(&mut self, id: Id) {{', 'match self {') emit_arms(o, s, set_rows) o('_ => {}', '}', '}', '') def gen_properties_impl(s): m = Model(s) o = Out(s) o('use crate::schema::prelude::*;', '') emit_enum_impl(o, s, 'ObjectType', object_variants(s)) emit_enum_impl(o, s, 'Property', property_variants(s)) objs = [(n, strip_x(n), s.agpl and s.ent_object(n)) for n in s.objects] json_of = {pascal(n): n for n in s.prop_ids} json_of['Type'] = '@type' def perm(n, op): prefix = s.d['objects'][n]['permissionPrefix'] name = prefix + op return f'Permission::{variant_ident(name)}' if name in m.perms else None # impl ObjectType o('impl ObjectType {', 'pub fn flags(&self) -> u64 {', 'match self {') emit_arms(o, s, [(f'ObjectType::{r} => {r}::FLAGS,', e) for n, r, e in objs]) o('}', '}', '') o('pub fn indexes(&self) -> Vec {', 'match self {') for n, r, e in objs: # Objects that are not stored have nothing to query by. rows = [] if n in SC.NOT_STORED_OBJECTS else index_schemas(m, n) uniq, seen = [], {} for row in rows: key = row[:3] if key in seen: if not row[3] and uniq[seen[key]][3]: uniq[seen[key]] = row continue seen[key] = len(uniq) uniq.append(row) if not uniq: continue uniq.sort(key=lambda x: (x[0] != 'Text', json_of.get(x[0], x[0]))) lines = [f'ObjectType::{r} => vec!['] body = [(f'IndexSchema::new(Property::{p}, IndexSchemaType::{st}, IndexSchemaValueType::{vt}),', ent) for p, st, vt, ent in uniq] if e: o.ent(lines + [b for b, _ in body] + ['],']) else: o(*lines) emit_arms(o, s, body) o('],') o('_ => vec![],', '}', '}', '') for fn, op, fallback, wildcard in (('get_permission', 'Get', None, False), ('query_permission', 'Query', None, True), ('create_permission', 'Create', 'Update', False), ('update_permission', 'Update', None, False), ('destroy_permission', 'Destroy', 'Update', False)): o(f'pub fn {fn}(&self) -> Permission {{', 'match self {') rows = [] for n, r, e in objs: # Singletons are created and destroyed by updating them. singleton = s.d['objects'][n]['type'] == 'singleton' p = perm(n, op) or (fallback and singleton and perm(n, fallback)) if p: rows.append((f'ObjectType::{r} => {p},', e)) elif not wildcard: rows.append((f'ObjectType::{r} => unreachable!(),', e)) emit_arms(o, s, rows) if wildcard: o('_ => unreachable!(),') o('}', '}', '') o('}', '') # impl ObjectInner o('impl ObjectInner {') # accountId everywhere; memberTenantId (inbuxa: MT) in the full edition only, # the agpl edition has no such fields. for json_name, rust_name in (('accountId', 'account_id'), ('memberTenantId', 'member_tenant_id')): if json_name == 'memberTenantId' and s.agpl: continue emit_id_accessors(o, s, objs, json_name, rust_name) o('pub fn to_pickled_vec(&self) -> Vec {', 'match &self {') emit_arms(o, s, [(f'ObjectInner::{r}(obj) => obj.to_pickled_vec(),' if n not in SC.NOT_STORED_OBJECTS else f'ObjectInner::{r}(_) => unimplemented!("{r} is not stored"),', e) for n, r, e in objs]) o('}', '}', '') o("pub fn unpickle(object: ObjectType, stream: &mut crate::pickle::PickledStream<'_>) -> Option {", 'match object {') emit_arms(o, s, [(f'ObjectType::{r} => Pickle::unpickle(stream).map(|obj| ObjectInner::{r}(Box::new(obj))),' if n not in SC.NOT_STORED_OBJECTS else f'ObjectType::{r} => unimplemented!("{r} is not stored"),', e) for n, r, e in objs]) o('}', '}', '') o("pub fn deserialize<'de, D>(object: ObjectType, deserializer: D) -> Result", 'where', "D: serde::Deserializer<'de>,", '{', 'match object {') emit_arms(o, s, [(f'ObjectType::{r} => {r}::deserialize(deserializer).map(|obj| ObjectInner::{r}(Box::new(obj))),', e) for n, r, e in objs]) o('}', '}', '') o("pub fn expression_ctxs(&self) -> Option>> {", 'match &self {') emit_arms(o, s, [(f'ObjectInner::{r}(obj) => Some(obj.expression_ctxs()),', e) for n, r, e in objs if n in m.has_ctx]) o('_ => None,', '}', '}', '}', '') # impl Object o('impl Object {', 'pub fn flags(&self) -> u64 {', 'match &self.inner {') emit_arms(o, s, [(f'ObjectInner::{r}(_) => {r}::FLAGS,', e) for n, r, e in objs]) o('}', '}', '', 'pub fn object_type(&self) -> ObjectType {', 'match &self.inner {') emit_arms(o, s, [(f'ObjectInner::{r}(_) => ObjectType::{r},', e) for n, r, e in objs]) o('}', '}', '', "pub fn object_variant(&self) -> Option<&'static str> {", 'match &self.inner {') emit_arms(o, s, [(f'ObjectInner::{r}(obj) => Some(obj.object_type().as_str()),', e) for n, r, e in objs if schema_kind(s, n) == 'multiple']) o('_ => None,', '}', '}', '') o('pub fn validate(&self, errors: &mut Vec) -> bool {', 'match &self.inner {') emit_arms(o, s, [(f'ObjectInner::{r}(obj) => obj.validate(errors),', e) for n, r, e in objs]) o('}', '}', '') o("pub fn index<'x>(&'x self, i: &mut IndexBuilder<'x>) {", 'match &self.inner {') emit_arms(o, s, [(f'ObjectInner::{r}(obj) => obj.index(i),', e) for n, r, e in objs]) o('}', '}', '') o("pub fn patch<'x>(&mut self, pointer: JsonPointerPatch<'_>, value: JmapValue<'x>) -> PatchResult<'x> {", 'match &mut self.inner {') emit_arms(o, s, [(f'ObjectInner::{r}(obj) => obj.patch(pointer, value),', e) for n, r, e in objs]) o('}', '}', '}', '') o('impl IntoValue for Object {', "fn into_value(self) -> JmapValue<'static> {", 'self.into_value_with(ValueOptions::default())', '}', '', "fn into_value_with(self, opts: ValueOptions) -> JmapValue<'static> {", 'match self.inner {') emit_arms(o, s, [(f'ObjectInner::{r}(obj) => (*obj).into_value_with(opts),', e) for n, r, e in objs]) o('}', '}', '}', '') o('impl From for ObjectInner {', 'fn from(obj: ObjectType) -> Self {', 'match obj {') emit_arms(o, s, [(f'ObjectType::{r} => ObjectInner::{r}(Box::default()),', e) for n, r, e in objs]) o('}', '}', '}', '') for n, r, e in objs: lines = [f'impl From<{r}> for ObjectInner {{', f'fn from(value: {r}) -> Self {{', f'ObjectInner::{r}(Box::new(value))', '}', '}', '', f'impl From for {r} {{', 'fn from(obj: Object) -> Self {', 'match obj.inner {', f'ObjectInner::{r}(obj) => *obj,', '_ => unreachable!(),', '}', '}', '}', ''] o.ent(lines) if e else o(*lines) return finish(s, o.text().rstrip('\n') + '\n') # --------------------------------------------------------------------------- # Driver # --------------------------------------------------------------------------- OUTPUTS = { 'crates/registry/src/schema/enums.rs': 'gen_enums', 'crates/registry/src/schema/enums_impl.rs': 'gen_enums_impl', 'crates/registry/src/schema/properties.rs': 'gen_properties', 'crates/registry/src/schema/properties_impl.rs': 'gen_properties_impl', 'crates/registry/src/schema/structs.rs': 'gen_structs', 'crates/registry/src/schema/structs_impl.rs': 'gen_structs_impl', } # crates/http/src/form/mod.rs is not generated: it is the hand-written # contact-form HTTP handler (no "auto-generated" line, nothing from the schema). def main(): ap = argparse.ArgumentParser(description=__doc__.strip().split('\n')[0]) ap.add_argument('--edition', choices=('agpl', 'full'), required=True) ap.add_argument('--schema', default='resources/schema/schema.json.gz', help='upstream schema (gzip JSON)') ap.add_argument('--oss-schema', default=None, help='upstream open-source schema (gzip JSON); defaults to ' 'schema_oss.json.gz next to --schema; required for agpl') ap.add_argument('--out', required=True, help='output root directory') ap.add_argument('--only', action='append', default=[], help='generate only outputs whose path contains this text') args = ap.parse_args() full = load_gz_json(args.schema) oss = None if args.edition == 'agpl': oss_path = args.oss_schema or str(Path(args.schema).with_name('schema_oss.json.gz')) oss = load_gz_json(oss_path) s = Schema(full, oss, args.edition) s.prepare() for rel, fn in OUTPUTS.items(): if args.only and not any(x in rel for x in args.only): continue text = globals()[fn](s) path = Path(args.out) / rel path.parent.mkdir(parents=True, exist_ok=True) path.write_text(text) print(f'{rel}: {text.count(chr(10))} lines') if __name__ == '__main__': main()