Class: Async::Matrix::Api::PathTree

Inherits:
Object
  • Object
show all
Defined in:
lib/async/matrix/api/path_tree.rb

Overview

A trie of valid Matrix API paths, built from OpenAPI 3.1.0 YAML schemas.

Each leaf node stores the set of HTTP methods valid at that path. Template segments like roomId become wildcard nodes that match any value.

Example:

tree = PathTree.load
tree.match(["_matrix", "client", "v3", "rooms", "!abc:ex.com", "ban"], "POST")
# => { valid: true, operation_id: "ban", methods: ["post"] }

Defined Under Namespace

Classes: Node

Constant Summary collapse

SCHEMA_DIR =
Pathname.new(File.expand_path("../../../../data/matrix-spec/api/client-server", __dir__))

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializePathTree

Returns a new instance of PathTree.



38
39
40
# File 'lib/async/matrix/api/path_tree.rb', line 38

def initialize
  @root = Node.new
end

Instance Attribute Details

#rootObject (readonly)

Returns the value of attribute root.



36
37
38
# File 'lib/async/matrix/api/path_tree.rb', line 36

def root
  @root
end

Class Method Details

.load(schema_dir: SCHEMA_DIR) ⇒ Object

Load all OpenAPI schemas from data/ and build the tree.



43
44
45
46
47
48
49
# File 'lib/async/matrix/api/path_tree.rb', line 43

def self.load(schema_dir: SCHEMA_DIR)
  tree = new
  Pathname.glob(schema_dir / "*.yaml").each do |path|
    tree.load_schema(path)
  end
  tree
end

Instance Method Details

#insert(segments, method, operation_id = nil) ⇒ Object

Insert a path (as array of segments) with an HTTP method into the trie.



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/async/matrix/api/path_tree.rb', line 76

def insert(segments, method, operation_id = nil)
  node = @root
  segments.each do |segment|
    if segment.start_with?("{") && segment.end_with?("}")
      # Wildcard segment — matches any value
      node.wildcard ||= Node.new
      node = node.wildcard
    else
      node.children[segment] ||= Node.new
      node = node.children[segment]
    end
  end
  node.methods << method.downcase unless node.methods.include?(method.downcase)
  node.operation_ids[method.downcase] = operation_id if operation_id
end

#load_schema(path) ⇒ Object

Parse a single OpenAPI YAML file and insert its paths into the tree.



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/async/matrix/api/path_tree.rb', line 52

def load_schema(path)
  doc = YAML.safe_load(File.read(path), permitted_classes: [Symbol], aliases: true)
  return unless doc.is_a?(Hash)

  base_path = extract_base_path(doc)
  paths = doc["paths"]
  return unless paths.is_a?(Hash)

  paths.each do |path_template, methods_hash|
    next unless methods_hash.is_a?(Hash)

    # Build full path: basePath + path_template
    full_path = "#{base_path}#{path_template.strip}"
    segments = full_path.split("/").reject(&:empty?)

    methods_hash.each do |method, operation|
      next unless %w[get post put delete patch head].include?(method)
      operation_id = operation.is_a?(Hash) ? operation["operationId"] : nil
      insert(segments, method, operation_id)
    end
  end
end

#match(segments, method = nil) ⇒ Object

Match a concrete path (array of segments) against the trie. Returns a result hash.



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/async/matrix/api/path_tree.rb', line 94

def match(segments, method = nil)
  node = @root
  segments.each do |segment|
    if node.children.key?(segment)
      node = node.children[segment]
    elsif node.wildcard
      node = node.wildcard
    else
      return {valid: false, methods: [], operation_id: nil}
    end
  end

  if method
    method_down = method.downcase
    valid = node.methods.include?(method_down)
    {valid: valid, methods: node.methods, operation_id: node.operation_ids[method_down]}
  else
    {valid: node.methods.any?, methods: node.methods, operation_id: nil}
  end
end