The root AST node representing a complete FDL file.
| 316 | |
| 317 | @dataclass |
| 318 | class Schema: |
| 319 | """The root AST node representing a complete FDL file.""" |
| 320 | |
| 321 | package: Optional[str] |
| 322 | package_alias: Optional[str] = None |
| 323 | imports: List[Import] = field(default_factory=list) |
| 324 | enums: List[Enum] = field(default_factory=list) |
| 325 | messages: List[Message] = field(default_factory=list) |
| 326 | unions: List[Union] = field(default_factory=list) |
| 327 | services: List[Service] = field(default_factory=list) |
| 328 | options: dict = field( |
| 329 | default_factory=dict |
| 330 | ) # File-level options (java_package, go_package, etc.) |
| 331 | source_file: Optional[str] = None |
| 332 | source_format: Optional[str] = None |
| 333 | resolved_import_files: List[str] = field(default_factory=list) |
| 334 | source_packages: Dict[str, Optional[str]] = field( |
| 335 | default_factory=dict |
| 336 | ) # Source file path -> the package name it declares |
| 337 | |
| 338 | def __repr__(self) -> str: |
| 339 | opts = f", options={len(self.options)}" if self.options else "" |
| 340 | alias = f", package_alias={self.package_alias}" if self.package_alias else "" |
| 341 | return ( |
| 342 | f"Schema(package={self.package}{alias}, imports={len(self.imports)}, " |
| 343 | f"enums={len(self.enums)}, messages={len(self.messages)}, unions={len(self.unions)}, " |
| 344 | f"services={len(self.services)}{opts})" |
| 345 | ) |
| 346 | |
| 347 | def get_option(self, name: str, default: Optional[str] = None) -> Optional[str]: |
| 348 | """Get a file-level option value.""" |
| 349 | return self.options.get(name, default) |
| 350 | |
| 351 | def get_type(self, name: str) -> Optional[TypingUnion[Message, Enum, "Union"]]: |
| 352 | """Look up a type by name, supporting qualified names like Parent.Child.""" |
| 353 | return self._get_type_by_path(self.resolve_type_name(name)) |
| 354 | |
| 355 | def resolve_type_name(self, name: str) -> str: |
| 356 | """Resolve package-qualified type names to the schema-local type path.""" |
| 357 | absolute = name.startswith(".") |
| 358 | cleaned = name.lstrip(".") |
| 359 | if absolute: |
| 360 | package_resolved = self._resolve_package_qualified_type(cleaned) |
| 361 | if package_resolved is not None: |
| 362 | return package_resolved |
| 363 | return name |
| 364 | if self._get_type_by_path(cleaned) is not None: |
| 365 | return cleaned |
| 366 | package_resolved = self._resolve_package_qualified_type(cleaned) |
| 367 | if package_resolved is not None: |
| 368 | return package_resolved |
| 369 | return cleaned |
| 370 | |
| 371 | def _resolve_package_qualified_type(self, name: str) -> Optional[str]: |
| 372 | if "." not in name: |
| 373 | return None |
| 374 | packages = { |
| 375 | type_def.source_package |
no test coverage detected