fixture_loader.mojo (5364B)
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(path: Path, key: String) raises -> Value: 96 var raw = path.read_text() 97 var data = raw.as_bytes() 98 var index = _skip_whitespace(raw, 0) 99 if index >= len(data) or data[index] != UInt8(ord("{")): 100 raise Error("fixture scenario must be a top-level JSON object") 101 102 index += 1 103 while index < len(data): 104 index = _skip_whitespace(raw, index) 105 if index >= len(data): 106 break 107 108 if data[index] == UInt8(ord("}")): 109 break 110 111 if data[index] != UInt8(ord('"')): 112 raise Error("fixture scenario object key must be a JSON string") 113 114 var key_json = _extract_json_value(raw, index) 115 var parsed_key = loads(key_json) 116 if not parsed_key.is_string(): 117 raise Error("fixture scenario object key did not parse as a string") 118 119 index += key_json.byte_length() 120 index = _skip_whitespace(raw, index) 121 if index >= len(data) or data[index] != UInt8(ord(":")): 122 raise Error( 123 "fixture scenario field '" + parsed_key.string_value() 124 + "' missing colon" 125 ) 126 127 var value_start = _skip_whitespace(raw, index + 1) 128 var value_json = _extract_json_value(raw, value_start) 129 if parsed_key.string_value() == key: 130 return loads(value_json) 131 132 index = value_start + value_json.byte_length() 133 index = _skip_whitespace(raw, index) 134 if index >= len(data): 135 break 136 if data[index] == UInt8(ord(",")): 137 index += 1 138 continue 139 if data[index] == UInt8(ord("}")): 140 break 141 raise Error( 142 "fixture scenario field '" + parsed_key.string_value() 143 + "' missing delimiter" 144 ) 145 146 raise Error("fixture scenario missing field '" + key + "'") 147 148 149 def load_fixture_manifest() raises -> Value: 150 return load_fixture_json_file(fixture_manifest_path()) 151 152 153 def load_fixture_scenario(relative_path: String) raises -> Value: 154 return load_fixture_json_file(fixture_root_path() / String(relative_path)) 155 156 157 def load_fixture_scenario_request(relative_path: String) raises -> Value: 158 return load_fixture_top_level_field_from_path( 159 fixture_root_path() / String(relative_path), "request" 160 ) 161 162 163 def load_fixture_scenario_expected(relative_path: String) raises -> Value: 164 return load_fixture_top_level_field_from_path( 165 fixture_root_path() / String(relative_path), "expected" 166 )