# Path System This document describes how paths are parsed, resolved, and managed in Implexus. ## Path Class The `Path` class represents a path to an entity in the database hierarchy. ```vala namespace Implexus.Core { public class Path : Object, Invercargill.Element, Invercargill.Hashable, Invercargill.Equatable { private Invercargill.DataStructures.Vector _segments; // Constructors public Path(string path_string) { _segments = new Invercargill.DataStructures.Vector(); parse(path_string); } public Path.root() { _segments = new Invercargill.DataStructures.Vector(); } public Path.from_segments(Invercargill.ReadOnlyCollection segments) { _segments = new Invercargill.DataStructures.Vector(); foreach (var seg in segments) { _segments.add(seg); } } public Path.child(Path parent, string name) { _segments = new Invercargill.DataStructures.Vector(); foreach (var seg in parent._segments) { _segments.add(seg); } _segments.add(name); } // Properties public bool is_root { get { return _segments.count == 0; } } public string name { owned get { if (is_root) return ""; return _segments.last(); } } public Path parent { owned get { if (is_root) return this; var parent_segments = _segments.take(_segments.count - 1); return new Path.from_segments(parent_segments); } } public int depth { get { return _segments.count; } } public Invercargill.ReadOnlyCollection segments { owned get { return _segments.as_read_only(); } } // Path Operations public Path child(string name) { return new Path.child(this, validate_name(name)); } public Path sibling(string name) { if (is_root) { throw new EngineError.INVALID_PATH("Root has no siblings"); } return parent.child(name); } public Path ancestor(int levels) { if (levels < 0 || levels > depth) { throw new EngineError.INVALID_PATH("Invalid ancestor level: %d", levels); } var ancestor_segments = _segments.take(depth - levels); return new Path.from_segments(ancestor_segments); } public bool is_ancestor_of(Path other) { if (depth >= other.depth) return false; for (int i = 0; i < depth; i++) { if (_segments.get(i) != other._segments.get(i)) return false; } return true; } public bool is_descendant_of(Path other) { return other.is_ancestor_of(this); } public Path relative_to(Path ancestor) { if (!ancestor.is_ancestor_of(this)) { throw new EngineError.INVALID_PATH("%s is not an ancestor of %s", ancestor.to_string(), this.to_string()); } var relative_segments = _segments.skip(ancestor.depth); return new Path.from_segments(relative_segments); } public Path resolve(Path relative_path) { var result_segments = new Invercargill.DataStructures.Vector(); foreach (var seg in _segments) { result_segments.add(seg); } foreach (var seg in relative_path._segments) { if (seg == "..") { if (result_segments.count > 0) { result_segments.remove_at(result_segments.count - 1); } } else if (seg != ".") { result_segments.add(seg); } } return new Path.from_segments(result_segments); } // String Conversion public string to_string() { if (is_root) return "/"; var builder = new StringBuilder(); foreach (var seg in _segments) { builder.append("/"); builder.append(escape_segment(seg)); } return builder.str; } public string to_key() { // Compact representation for storage keys if (is_root) return "\0"; return string.joinv("\0", _segments.to_array()); } public static Path from_key(string key) { if (key == "\0") return new Path.root(); var segments = key.split("\0"); return new Path.from_segments(new Invercargill.DataStructures.Vector.from_array(segments)); } // Parsing private void parse(string path_string) { if (path_string == null || path_string == "") { return; // Root path } var normalized = path_string; if (normalized.has_prefix("/")) { normalized = normalized.substring(1); } if (normalized.has_suffix("/")) { normalized = normalized.substring(0, normalized.length - 1); } if (normalized == "") { return; // Root path } var parts = normalized.split("/"); foreach (var part in parts) { if (part == "") continue; _segments.add(unescape_segment(part)); } } // Validation private string validate_name(string name) { if (name == null || name == "") { throw new EngineError.INVALID_PATH("Entity name cannot be empty"); } if (name.contains("/")) { throw new EngineError.INVALID_PATH("Entity name cannot contain /: %s", name); } if (name == "." || name == "..") { throw new EngineError.INVALID_PATH("Entity name cannot be . or .."); } return name; } // Escaping for special characters in segment names private string escape_segment(string segment) { return segment.replace("~", "~7e") .replace("/", "~2f") .replace("\\", "~5c") .replace("\0", "~00"); } private string unescape_segment(string segment) { return segment.replace("~00", "\0") .replace("~5c", "\\") .replace("~2f", "/") .replace("~7e", "~"); } // Hashable public uint hash() { uint h = 0; foreach (var seg in _segments) { h ^= str_hash(seg); } return h; } // Equatable public bool equals(Path other) { if (depth != other.depth) return false; for (int i = 0; i < depth; i++) { if (_segments.get(i) != other._segments.get(i)) return false; } return true; } // Element interface public Type type() { return typeof(Path); } public string type_name() { return "Path"; } public bool is_null() { return false; } public bool is_type(Type t) { return t.is_a(typeof(Path)); } public bool assignable_to(Type t) { return t.is_a(typeof(Path)); } public bool assignable_to_type(Type t) { return is_type(t); } public T? as() { return this; } public T assert_as() { return (T) this; } public T? as_or_default(T default_value) { return this; } public bool try_get_as(out T result) { result = this; return true; } public T to_value() { return (T) this; } // Static factory methods public static Path parse(string path_string) { return new Path(path_string); } public static Path combine(Path base_path, string relative) { return base_path.resolve(new Path(relative)); } } } // namespace Implexus.Core ``` ## Path Resolution Flow ```mermaid sequenceDiagram participant App as Application participant Engine as Engine participant Storage as Storage participant Cache as Entity Cache App->>Engine: get_entity with /users/john/profile Engine->>Engine: Create Path object Engine->>Storage: has_entity with path Storage-->>Engine: true Engine->>Cache: check cache for path Cache-->>Engine: not found Engine->>Storage: load_entity with path Storage-->>Engine: binary data Engine->>Engine: deserialize entity Engine->>Cache: cache entity Engine-->>App: Entity object ``` ## Path Examples | Path String | Segments | is_root | depth | |-------------|----------|---------|-------| | `/` | `[]` | true | 0 | | `/users` | `["users"]` | false | 1 | | `/users/john` | `["users", "john"]` | false | 2 | | `/users/john/profile` | `["users", "john", "profile"]` | false | 3 | ## Path Operations Examples ```vala // Creation var root = new Path.root(); var users = new Path("/users"); var john = users.child("john"); // Navigation assert(john.parent.equals(users)); assert(john.name == "john"); assert(john.depth == 2); // Relationships assert(users.is_ancestor_of(john)); assert(john.is_descendant_of(users)); assert(!john.is_ancestor_of(users)); // Relative paths var profile = john.child("profile"); var relative = profile.relative_to(users); assert(relative.to_string() == "john/profile"); // Resolution var resolved = users.resolve(new Path("john/profile")); assert(resolved.equals(profile)); // With .. and . var complex = john.resolve(new Path("../jane")); assert(complex.to_string() == "/users/jane"); ``` ## Path Validation Rules ### Valid Names - Non-empty strings - Cannot contain `/` - Cannot be `.` or `..` - Any other characters allowed (including unicode) ### Invalid Names - Empty string `""` - Contains slash `"a/b"` - Current directory `"."` - Parent directory `".."` ### Escaping Special characters in names are escaped using tilde encoding: | Character | Escaped | |-----------|---------| | `~` | `~7e` | | `/` | `~2f` | | `\` | `~5c` | | `\0` | `~00` | This allows names like `"a/b"` (escaped as `"a~2fb"`) to be stored safely. ## Path-Based Entity Resolution The engine resolves paths to entities through the storage layer: ```vala public Entity? resolve_path(Path path) { // 1. Check cache if (_cache.has(path)) { return _cache.get(path); } // 2. Load from storage if (!_storage.has_entity(path)) { return null; } // 3. Deserialize var data = _storage.load_entity(path); var entity = _deserializer.deserialize(data, this, path); // 4. Cache and return _cache.set(path, entity); return entity; } ``` ## Child Name Tracking Storage tracks child names for each path to enable efficient enumeration: ``` Key: children:/users Value: ["john", "jane", "admin"] Key: children:/users/john Value: ["profile", "settings"] ``` This allows `get_child_names()` to return quickly without scanning all entities.