fixture_loader.mojo (5402B)
1 from std.pathlib import Path, _dir_of_current_file 2 3 from json import Value, loads 4 5 6 def fixture_root_path() raises -> Path: 7 return _dir_of_current_file() / "fixtures" / "v1" 8 9 10 def fixture_manifest_path() raises -> Path: 11 return fixture_root_path() / "manifest.json" 12 13 14 def load_fixture_json_file(path: Path) raises -> Value: 15 return loads(path.read_text()) 16 17 18 def _skip_whitespace(raw: String, start_index: Int) -> Int: 19 var data = raw.as_bytes() 20 var index = start_index 21 while index < len(data): 22 var byte = data[index] 23 if ( 24 byte == UInt8(ord(" ")) 25 or byte == UInt8(ord("\n")) 26 or byte == UInt8(ord("\t")) 27 or byte == UInt8(ord("\r")) 28 ): 29 index += 1 30 continue 31 break 32 return index 33 34 35 def _extract_json_value(raw: String, start_index: Int) raises -> String: 36 var data = raw.as_bytes() 37 if start_index >= len(data): 38 raise Error("fixture field value start out of bounds") 39 40 var first = data[start_index] 41 if first == UInt8(ord("{")) or first == UInt8(ord("[")): 42 var depth = 0 43 var in_string = False 44 var escaped = False 45 var index = start_index 46 while index < len(data): 47 var byte = data[index] 48 if escaped: 49 escaped = False 50 elif in_string: 51 if byte == UInt8(ord("\\")): 52 escaped = True 53 elif byte == UInt8(ord('"')): 54 in_string = False 55 else: 56 if byte == UInt8(ord('"')): 57 in_string = True 58 elif byte == UInt8(ord("{")) or byte == UInt8(ord("[")): 59 depth += 1 60 elif byte == UInt8(ord("}")) or byte == UInt8(ord("]")): 61 depth -= 1 62 if depth == 0: 63 return String(raw[byte = start_index : index + 1]) 64 index += 1 65 raise Error("unterminated fixture object or array field") 66 67 if first == UInt8(ord('"')): 68 var escaped = False 69 var index = start_index + 1 70 while index < len(data): 71 var byte = data[index] 72 if escaped: 73 escaped = False 74 elif byte == UInt8(ord("\\")): 75 escaped = True 76 elif byte == UInt8(ord('"')): 77 return String(raw[byte = start_index : index + 1]) 78 index += 1 79 raise Error("unterminated fixture string field") 80 81 var index = start_index 82 while index < len(data): 83 var byte = data[index] 84 if ( 85 byte == UInt8(ord(",")) 86 or byte == UInt8(ord("}")) 87 or byte == UInt8(ord("]")) 88 ): 89 return String(raw[byte=start_index:index]) 90 index += 1 91 92 return String(raw[byte=start_index:]) 93 94 95 def load_fixture_top_level_field_from_path( 96 path: Path, key: String 97 ) raises -> Value: 98 var raw = path.read_text() 99 var data = raw.as_bytes() 100 var index = _skip_whitespace(raw, 0) 101 if index >= len(data) or data[index] != UInt8(ord("{")): 102 raise Error("fixture scenario must be a top-level JSON object") 103 104 index += 1 105 while index < len(data): 106 index = _skip_whitespace(raw, index) 107 if index >= len(data): 108 break 109 110 if data[index] == UInt8(ord("}")): 111 break 112 113 if data[index] != UInt8(ord('"')): 114 raise Error("fixture scenario object key must be a JSON string") 115 116 var key_json = _extract_json_value(raw, index) 117 var parsed_key = loads(key_json) 118 if not parsed_key.is_string(): 119 raise Error("fixture scenario object key did not parse as a string") 120 121 index += key_json.byte_length() 122 index = _skip_whitespace(raw, index) 123 if index >= len(data) or data[index] != UInt8(ord(":")): 124 raise Error( 125 "fixture scenario field '" 126 + parsed_key.string_value() 127 + "' missing colon" 128 ) 129 130 var value_start = _skip_whitespace(raw, index + 1) 131 var value_json = _extract_json_value(raw, value_start) 132 if parsed_key.string_value() == key: 133 return loads(value_json) 134 135 index = value_start + value_json.byte_length() 136 index = _skip_whitespace(raw, index) 137 if index >= len(data): 138 break 139 if data[index] == UInt8(ord(",")): 140 index += 1 141 continue 142 if data[index] == UInt8(ord("}")): 143 break 144 raise Error( 145 "fixture scenario field '" 146 + parsed_key.string_value() 147 + "' missing delimiter" 148 ) 149 150 raise Error("fixture scenario missing field '" + key + "'") 151 152 153 def load_fixture_manifest() raises -> Value: 154 return load_fixture_json_file(fixture_manifest_path()) 155 156 157 def load_fixture_scenario(relative_path: String) raises -> Value: 158 return load_fixture_json_file(fixture_root_path() / String(relative_path)) 159 160 161 def load_fixture_scenario_request(relative_path: String) raises -> Value: 162 return load_fixture_top_level_field_from_path( 163 fixture_root_path() / String(relative_path), "request" 164 ) 165 166 167 def load_fixture_scenario_expected(relative_path: String) raises -> Value: 168 return load_fixture_top_level_field_from_path( 169 fixture_root_path() / String(relative_path), "expected" 170 )