Auto-indexing service and GraphQL API for AT Protocol Records quickslice.slices.network/
atproto gleam graphql

refactor: move graphql_ws into graphql/ws module

Move the GraphQL-WS protocol implementation into the graphql/ folder
where it belongs with other GraphQL concerns.

Changes:
- Move graphql_ws.gleam to graphql/ws.gleam
- Move graphql_ws_test.gleam to graphql/ws_test.gleam
- Update handler imports from graphql_ws to graphql/ws

+398 -74
+337
dev-docs/plans/2025-12-10-graphql-ws-refactor.md
··· 1 + # GraphQL WebSocket Module Refactor Implementation Plan 2 + 3 + > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. 4 + 5 + **Goal:** Move `graphql_ws.gleam` into the `graphql/` folder and replace manual JSON string concatenation with `gleam_json`. 6 + 7 + **Architecture:** The protocol module (`graphql_ws.gleam`) belongs with GraphQL concerns, not as a top-level module. The handler stays in `handlers/` following existing conventions. JSON formatting uses `gleam_json` with `json.preprocessed()` for already-serialized payloads. 8 + 9 + **Tech Stack:** Gleam, gleam_json 10 + 11 + --- 12 + 13 + ### Task 1: Create graphql/ws.gleam with gleam_json formatting 14 + 15 + **Files:** 16 + - Create: `server/src/graphql/ws.gleam` 17 + - Reference: `server/src/graphql_ws.gleam` (copy and modify) 18 + 19 + **Step 1: Create the new module** 20 + 21 + Create `server/src/graphql/ws.gleam` with the refactored code: 22 + 23 + ```gleam 24 + /// GraphQL-WS Protocol Implementation 25 + /// 26 + /// Implements the graphql-ws WebSocket subprotocol for GraphQL subscriptions 27 + /// Spec: https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md 28 + import gleam/dict.{type Dict} 29 + import gleam/dynamic/decode 30 + import gleam/json 31 + import gleam/option.{type Option, None, Some} 32 + import gleam/result 33 + 34 + /// Client-to-server message types 35 + /// Server-to-client message types 36 + pub type Message { 37 + // Client messages 38 + ConnectionInit(payload: Dict(String, String)) 39 + Subscribe(id: String, query: String, variables: Option(String)) 40 + Complete(id: String) 41 + Ping 42 + Pong 43 + 44 + // Server messages 45 + ConnectionAck 46 + Next(id: String, data: String) 47 + ErrorMessage(id: String, message: String) 48 + } 49 + 50 + /// Parse a JSON string into a GraphQL-WS message 51 + pub fn parse_message(json_str: String) -> Result(Message, String) { 52 + // First parse to extract the type field 53 + let type_decoder = { 54 + use message_type <- decode.field("type", decode.string) 55 + decode.success(message_type) 56 + } 57 + 58 + use message_type <- result.try( 59 + json.parse(json_str, type_decoder) 60 + |> result.map_error(fn(_) { "Missing or invalid 'type' field" }), 61 + ) 62 + 63 + case message_type { 64 + "connection_init" -> { 65 + // Try to extract payload, but it's optional 66 + let payload = 67 + extract_string_payload(json_str) |> option.unwrap(dict.new()) 68 + Ok(ConnectionInit(payload)) 69 + } 70 + 71 + "subscribe" -> { 72 + let subscribe_decoder = { 73 + use id <- decode.field("id", decode.string) 74 + use query <- decode.subfield(["payload", "query"], decode.string) 75 + decode.success(#(id, query)) 76 + } 77 + 78 + use #(id, query) <- result.try( 79 + json.parse(json_str, subscribe_decoder) 80 + |> result.map_error(fn(_) { 81 + "Subscribe message missing required fields" 82 + }), 83 + ) 84 + 85 + // Variables are optional - try to extract them 86 + let vars = extract_variables_from_json(json_str) 87 + 88 + Ok(Subscribe(id, query, vars)) 89 + } 90 + 91 + "complete" -> { 92 + let complete_decoder = { 93 + use id <- decode.field("id", decode.string) 94 + decode.success(id) 95 + } 96 + 97 + use id <- result.try( 98 + json.parse(json_str, complete_decoder) 99 + |> result.map_error(fn(_) { "Complete message missing 'id' field" }), 100 + ) 101 + 102 + Ok(Complete(id)) 103 + } 104 + 105 + "ping" -> Ok(Ping) 106 + 107 + "pong" -> Ok(Pong) 108 + 109 + _ -> { 110 + let err_msg = "Unknown message type: " <> message_type 111 + Error(err_msg) 112 + } 113 + } 114 + } 115 + 116 + /// Format a GraphQL-WS message as JSON string 117 + pub fn format_message(message: Message) -> String { 118 + case message { 119 + ConnectionAck -> 120 + json.object([#("type", json.string("connection_ack"))]) 121 + |> json.to_string 122 + 123 + Next(id, data) -> 124 + json.object([ 125 + #("id", json.string(id)), 126 + #("type", json.string("next")), 127 + #("payload", json.preprocessed(data)), 128 + ]) 129 + |> json.to_string 130 + 131 + ErrorMessage(id, msg) -> 132 + json.object([ 133 + #("id", json.string(id)), 134 + #("type", json.string("error")), 135 + #("payload", json.preprocessed_array([ 136 + json.object([#("message", json.string(msg))]), 137 + ])), 138 + ]) 139 + |> json.to_string 140 + 141 + Complete(id) -> 142 + json.object([ 143 + #("id", json.string(id)), 144 + #("type", json.string("complete")), 145 + ]) 146 + |> json.to_string 147 + 148 + Pong -> 149 + json.object([#("type", json.string("pong"))]) 150 + |> json.to_string 151 + 152 + Ping -> 153 + json.object([#("type", json.string("ping"))]) 154 + |> json.to_string 155 + 156 + ConnectionInit(_) -> 157 + json.object([#("type", json.string("connection_init"))]) 158 + |> json.to_string 159 + 160 + Subscribe(id, _, _) -> 161 + json.object([ 162 + #("id", json.string(id)), 163 + #("type", json.string("subscribe")), 164 + ]) 165 + |> json.to_string 166 + } 167 + } 168 + 169 + // Helper to extract payload field as dict of strings 170 + fn extract_string_payload(json_str: String) -> Option(Dict(String, String)) { 171 + let decoder = { 172 + use payload <- decode.field( 173 + "payload", 174 + decode.dict(decode.string, decode.string), 175 + ) 176 + decode.success(payload) 177 + } 178 + 179 + json.parse(json_str, decoder) 180 + |> result.map(Some) 181 + |> result.unwrap(None) 182 + } 183 + 184 + // Helper to extract variables from subscribe message 185 + fn extract_variables_from_json(json_str: String) -> Option(String) { 186 + let vars_decoder = { 187 + use vars <- decode.subfield(["payload", "variables"], decode.string) 188 + decode.success(vars) 189 + } 190 + 191 + json.parse(json_str, vars_decoder) 192 + |> result.map(Some) 193 + |> result.unwrap(None) 194 + } 195 + ``` 196 + 197 + **Step 2: Build to verify no compile errors** 198 + 199 + Run: `cd /Users/chadmiller/code/quickslice/server && gleam build` 200 + Expected: Build succeeds (warnings about unused module are OK) 201 + 202 + **Step 3: Commit** 203 + 204 + ```bash 205 + git add server/src/graphql/ws.gleam 206 + git commit -m "feat: add graphql/ws module with gleam_json formatting" 207 + ``` 208 + 209 + --- 210 + 211 + ### Task 2: Move and update test file 212 + 213 + **Files:** 214 + - Create: `server/test/graphql/ws_test.gleam` 215 + - Reference: `server/test/graphql_ws_test.gleam` 216 + 217 + **Step 1: Read current test file** 218 + 219 + Read `server/test/graphql_ws_test.gleam` to understand existing tests. 220 + 221 + **Step 2: Create updated test file** 222 + 223 + Create `server/test/graphql/ws_test.gleam` with updated imports: 224 + - Change `import graphql_ws` to `import graphql/ws` 225 + - Change all `graphql_ws.` references to `ws.` 226 + 227 + **Step 3: Run tests to verify they pass** 228 + 229 + Run: `cd /Users/chadmiller/code/quickslice/server && gleam test` 230 + Expected: All ws_test tests pass 231 + 232 + **Step 4: Commit** 233 + 234 + ```bash 235 + git add server/test/graphql/ws_test.gleam 236 + git commit -m "test: move graphql_ws tests to graphql/ws_test" 237 + ``` 238 + 239 + --- 240 + 241 + ### Task 3: Update handler imports 242 + 243 + **Files:** 244 + - Modify: `server/src/handlers/graphql_ws.gleam` 245 + 246 + **Step 1: Update import statement** 247 + 248 + Change line 14: 249 + ```gleam 250 + // From: 251 + import graphql_ws 252 + 253 + // To: 254 + import graphql/ws 255 + ``` 256 + 257 + **Step 2: Update all references** 258 + 259 + Replace all occurrences of `graphql_ws.` with `ws.`: 260 + - `graphql_ws.format_message` → `ws.format_message` 261 + - `graphql_ws.parse_message` → `ws.parse_message` 262 + - `graphql_ws.ConnectionInit` → `ws.ConnectionInit` 263 + - `graphql_ws.Subscribe` → `ws.Subscribe` 264 + - `graphql_ws.Complete` → `ws.Complete` 265 + - `graphql_ws.Ping` → `ws.Ping` 266 + - `graphql_ws.Pong` → `ws.Pong` 267 + - `graphql_ws.ConnectionAck` → `ws.ConnectionAck` 268 + - `graphql_ws.Next` → `ws.Next` 269 + - `graphql_ws.ErrorMessage` → `ws.ErrorMessage` 270 + 271 + **Step 3: Build to verify** 272 + 273 + Run: `cd /Users/chadmiller/code/quickslice/server && gleam build` 274 + Expected: Build succeeds 275 + 276 + **Step 4: Run tests** 277 + 278 + Run: `cd /Users/chadmiller/code/quickslice/server && gleam test` 279 + Expected: All tests pass 280 + 281 + **Step 5: Commit** 282 + 283 + ```bash 284 + git add server/src/handlers/graphql_ws.gleam 285 + git commit -m "refactor: update handler to use graphql/ws module" 286 + ``` 287 + 288 + --- 289 + 290 + ### Task 4: Remove old files 291 + 292 + **Files:** 293 + - Delete: `server/src/graphql_ws.gleam` 294 + - Delete: `server/test/graphql_ws_test.gleam` 295 + 296 + **Step 1: Delete old source file** 297 + 298 + ```bash 299 + rm server/src/graphql_ws.gleam 300 + ``` 301 + 302 + **Step 2: Delete old test file** 303 + 304 + ```bash 305 + rm server/test/graphql_ws_test.gleam 306 + ``` 307 + 308 + **Step 3: Build and test to verify everything still works** 309 + 310 + Run: `cd /Users/chadmiller/code/quickslice/server && gleam build && gleam test` 311 + Expected: Build succeeds, all tests pass 312 + 313 + **Step 4: Commit** 314 + 315 + ```bash 316 + git add -A 317 + git commit -m "chore: remove old graphql_ws files" 318 + ``` 319 + 320 + --- 321 + 322 + ### Task 5: Final verification 323 + 324 + **Step 1: Run full test suite** 325 + 326 + Run: `cd /Users/chadmiller/code/quickslice/server && gleam test` 327 + Expected: All tests pass 328 + 329 + **Step 2: Verify no references to old module remain** 330 + 331 + Run: `grep -r "import graphql_ws" /Users/chadmiller/code/quickslice/server/src/` 332 + Expected: No output (no remaining imports) 333 + 334 + **Step 3: Verify new structure** 335 + 336 + Run: `ls -la /Users/chadmiller/code/quickslice/server/src/graphql/` 337 + Expected: Shows `ws.gleam` alongside `admin/`, `lexicon/`, `where_converter.gleam`
+18 -31
server/src/graphql_ws.gleam server/src/graphql/ws.gleam
··· 94 94 /// Format a GraphQL-WS message as JSON string 95 95 pub fn format_message(message: Message) -> String { 96 96 case message { 97 - ConnectionAck -> { 98 - "{\"type\":\"connection_ack\"}" 99 - } 97 + ConnectionAck -> "{\"type\":\"connection_ack\"}" 100 98 101 - Next(id, data) -> { 99 + Next(id, data) -> 102 100 // data is already a JSON string containing the GraphQL response 103 101 "{\"id\":\"" <> id <> "\",\"type\":\"next\",\"payload\":" <> data <> "}" 104 - } 105 102 106 103 ErrorMessage(id, msg) -> { 107 104 let escaped_msg = escape_json_string(msg) ··· 112 109 <> "\"}]}" 113 110 } 114 111 115 - Complete(id) -> { 116 - "{\"id\":\"" <> id <> "\",\"type\":\"complete\"}" 117 - } 112 + Complete(id) -> "{\"id\":\"" <> id <> "\",\"type\":\"complete\"}" 113 + 114 + Pong -> "{\"type\":\"pong\"}" 118 115 119 - Pong -> { 120 - "{\"type\":\"pong\"}" 121 - } 116 + Ping -> "{\"type\":\"ping\"}" 122 117 123 118 // These are client messages, shouldn't normally be formatted by server 124 - ConnectionInit(_) -> { 125 - "{\"type\":\"connection_init\"}" 126 - } 119 + ConnectionInit(_) -> "{\"type\":\"connection_init\"}" 127 120 128 - Subscribe(id, _, _) -> { 129 - "{\"id\":\"" <> id <> "\",\"type\":\"subscribe\"}" 130 - } 121 + Subscribe(id, _, _) -> "{\"id\":\"" <> id <> "\",\"type\":\"subscribe\"}" 122 + } 123 + } 131 124 132 - Ping -> { 133 - "{\"type\":\"ping\"}" 134 - } 135 - } 125 + // Helper to escape JSON strings 126 + fn escape_json_string(str: String) -> String { 127 + str 128 + |> string.replace("\\", "\\\\") 129 + |> string.replace("\"", "\\\"") 130 + |> string.replace("\n", "\\n") 131 + |> string.replace("\r", "\\r") 132 + |> string.replace("\t", "\\t") 136 133 } 137 134 138 135 // Helper to extract payload field as dict of strings ··· 161 158 |> result.map(Some) 162 159 |> result.unwrap(None) 163 160 } 164 - 165 - // Helper to escape JSON strings 166 - fn escape_json_string(str: String) -> String { 167 - str 168 - |> string.replace("\\", "\\\\") 169 - |> string.replace("\"", "\\\"") 170 - |> string.replace("\n", "\\n") 171 - |> string.replace("\r", "\\r") 172 - |> string.replace("\t", "\\t") 173 - }
+13 -13
server/src/handlers/graphql_ws.gleam
··· 11 11 import gleam/result 12 12 import gleam/string 13 13 import graphql/lexicon/schema as lexicon_schema 14 - import graphql_ws 14 + import graphql/ws 15 15 import lib/oauth/did_cache 16 16 import logging 17 17 import mist.{ ··· 282 282 } 283 283 mist.Custom(websocket_ffi.SubscriptionData(id, data)) -> { 284 284 // Handle subscription data from listener processes 285 - let next_msg = graphql_ws.format_message(graphql_ws.Next(id, data)) 285 + let next_msg = ws.format_message(ws.Next(id, data)) 286 286 let _ = mist.send_text_frame(conn, next_msg) 287 287 mist.continue(state) 288 288 } ··· 291 291 292 292 /// Handle text messages (GraphQL-WS protocol) 293 293 fn handle_text_message(state: State, conn: WebsocketConnection, text: String) { 294 - case graphql_ws.parse_message(text) { 295 - Ok(graphql_ws.ConnectionInit(_payload)) -> { 294 + case ws.parse_message(text) { 295 + Ok(ws.ConnectionInit(_payload)) -> { 296 296 // Send connection_ack 297 - let ack_msg = graphql_ws.format_message(graphql_ws.ConnectionAck) 297 + let ack_msg = ws.format_message(ws.ConnectionAck) 298 298 let _ = mist.send_text_frame(conn, ack_msg) 299 299 logging.log(logging.Info, "[websocket] Connection initialized") 300 300 mist.continue(state) 301 301 } 302 302 303 - Ok(graphql_ws.Subscribe(id, query, variables_opt)) -> { 303 + Ok(ws.Subscribe(id, query, variables_opt)) -> { 304 304 // Check per-connection subscription limit 305 305 let connection_count = dict.size(state.subscriptions) 306 306 case connection_count >= max_subscriptions_per_connection { ··· 311 311 <> string.inspect(connection_count), 312 312 ) 313 313 let error_msg = 314 - graphql_ws.format_message(graphql_ws.ErrorMessage( 314 + ws.format_message(ws.ErrorMessage( 315 315 id, 316 316 "Maximum subscriptions per connection exceeded (" 317 317 <> string.inspect(max_subscriptions_per_connection) ··· 331 331 <> string.inspect(global_count), 332 332 ) 333 333 let error_msg = 334 - graphql_ws.format_message(graphql_ws.ErrorMessage( 334 + ws.format_message(ws.ErrorMessage( 335 335 id, 336 336 "Global subscription limit exceeded", 337 337 )) ··· 347 347 "[websocket] Invalid subscription query: " <> err, 348 348 ) 349 349 let error_msg = 350 - graphql_ws.format_message(graphql_ws.ErrorMessage( 350 + ws.format_message(ws.ErrorMessage( 351 351 id, 352 352 "Invalid subscription query: " <> err, 353 353 )) ··· 405 405 } 406 406 } 407 407 408 - Ok(graphql_ws.Complete(id)) -> { 408 + Ok(ws.Complete(id)) -> { 409 409 // Client wants to stop subscription 410 410 case dict.get(state.subscriptions, id) { 411 411 Ok(info) -> { ··· 435 435 } 436 436 } 437 437 438 - Ok(graphql_ws.Ping) -> { 438 + Ok(ws.Ping) -> { 439 439 // Respond with pong 440 - let pong_msg = graphql_ws.format_message(graphql_ws.Pong) 440 + let pong_msg = ws.format_message(ws.Pong) 441 441 let _ = mist.send_text_frame(conn, pong_msg) 442 442 mist.continue(state) 443 443 } 444 444 445 - Ok(graphql_ws.Pong) -> { 445 + Ok(ws.Pong) -> { 446 446 // Client responded to our ping, just continue 447 447 mist.continue(state) 448 448 }
+30 -30
server/test/graphql_ws_test.gleam server/test/graphql/ws_test.gleam
··· 5 5 import gleam/option.{Some} 6 6 import gleeunit 7 7 import gleeunit/should 8 - import graphql_ws 8 + import graphql/ws 9 9 10 10 pub fn main() { 11 11 gleeunit.main() ··· 16 16 let json_str = 17 17 "{\"type\":\"connection_init\",\"payload\":{\"Authorization\":\"Bearer token123\"}}" 18 18 19 - case graphql_ws.parse_message(json_str) { 20 - Ok(graphql_ws.ConnectionInit(payload)) -> { 19 + case ws.parse_message(json_str) { 20 + Ok(ws.ConnectionInit(payload)) -> { 21 21 case dict.get(payload, "Authorization") { 22 22 Ok("Bearer token123") -> should.be_true(True) 23 23 _ -> should.fail() ··· 31 31 pub fn parse_connection_init_no_payload_test() { 32 32 let json_str = "{\"type\":\"connection_init\"}" 33 33 34 - case graphql_ws.parse_message(json_str) { 35 - Ok(graphql_ws.ConnectionInit(payload)) -> { 34 + case ws.parse_message(json_str) { 35 + Ok(ws.ConnectionInit(payload)) -> { 36 36 dict.size(payload) 37 37 |> should.equal(0) 38 38 } ··· 45 45 let json_str = 46 46 "{\"id\":\"1\",\"type\":\"subscribe\",\"payload\":{\"query\":\"subscription { postCreated { text } }\"}}" 47 47 48 - case graphql_ws.parse_message(json_str) { 49 - Ok(graphql_ws.Subscribe(id, query, _vars)) -> { 48 + case ws.parse_message(json_str) { 49 + Ok(ws.Subscribe(id, query, _vars)) -> { 50 50 id |> should.equal("1") 51 51 query |> should.equal("subscription { postCreated { text } }") 52 52 } ··· 59 59 let json_str = 60 60 "{\"id\":\"1\",\"type\":\"subscribe\",\"payload\":{\"query\":\"subscription { postCreated { text } }\",\"variables\":\"{}\"}}" 61 61 62 - case graphql_ws.parse_message(json_str) { 63 - Ok(graphql_ws.Subscribe(id, query, vars)) -> { 62 + case ws.parse_message(json_str) { 63 + Ok(ws.Subscribe(id, query, vars)) -> { 64 64 id |> should.equal("1") 65 65 query |> should.equal("subscription { postCreated { text } }") 66 66 vars |> should.equal(Some("{}")) ··· 73 73 pub fn parse_ping_message_test() { 74 74 let json_str = "{\"type\":\"ping\"}" 75 75 76 - case graphql_ws.parse_message(json_str) { 77 - Ok(graphql_ws.Ping) -> should.be_true(True) 76 + case ws.parse_message(json_str) { 77 + Ok(ws.Ping) -> should.be_true(True) 78 78 _ -> should.fail() 79 79 } 80 80 } ··· 83 83 pub fn parse_pong_message_test() { 84 84 let json_str = "{\"type\":\"pong\"}" 85 85 86 - case graphql_ws.parse_message(json_str) { 87 - Ok(graphql_ws.Pong) -> should.be_true(True) 86 + case ws.parse_message(json_str) { 87 + Ok(ws.Pong) -> should.be_true(True) 88 88 _ -> should.fail() 89 89 } 90 90 } ··· 93 93 pub fn parse_complete_message_test() { 94 94 let json_str = "{\"id\":\"1\",\"type\":\"complete\"}" 95 95 96 - case graphql_ws.parse_message(json_str) { 97 - Ok(graphql_ws.Complete(id)) -> { 96 + case ws.parse_message(json_str) { 97 + Ok(ws.Complete(id)) -> { 98 98 id |> should.equal("1") 99 99 } 100 100 _ -> should.fail() ··· 103 103 104 104 // Test: Format connection_ack message 105 105 pub fn format_connection_ack_test() { 106 - let message = graphql_ws.ConnectionAck 106 + let message = ws.ConnectionAck 107 107 108 - let json_str = graphql_ws.format_message(message) 108 + let json_str = ws.format_message(message) 109 109 110 110 // Should produce valid JSON with type "connection_ack" 111 111 json_str |> should.equal("{\"type\":\"connection_ack\"}") ··· 115 115 pub fn format_next_message_test() { 116 116 let data_json = "{\"data\":{\"postCreated\":{\"text\":\"Hello\"}}}" 117 117 118 - let message = graphql_ws.Next("1", data_json) 118 + let message = ws.Next("1", data_json) 119 119 120 - let json_str = graphql_ws.format_message(message) 120 + let json_str = ws.format_message(message) 121 121 122 122 // Should contain id, type "next", and payload 123 123 json_str ··· 128 128 129 129 // Test: Format error message 130 130 pub fn format_error_message_test() { 131 - let message = graphql_ws.ErrorMessage("1", "Syntax error") 131 + let message = ws.ErrorMessage("1", "Syntax error") 132 132 133 - let json_str = graphql_ws.format_message(message) 133 + let json_str = ws.format_message(message) 134 134 135 135 // Should contain id, type "error", and payload with message 136 136 json_str ··· 141 141 142 142 // Test: Format error message with quotes 143 143 pub fn format_error_message_with_quotes_test() { 144 - let message = graphql_ws.ErrorMessage("1", "Field \"text\" not found") 144 + let message = ws.ErrorMessage("1", "Field \"text\" not found") 145 145 146 - let json_str = graphql_ws.format_message(message) 146 + let json_str = ws.format_message(message) 147 147 148 148 // Should escape quotes in error message 149 149 json_str ··· 154 154 155 155 // Test: Format complete message 156 156 pub fn format_complete_message_test() { 157 - let message = graphql_ws.Complete("1") 157 + let message = ws.Complete("1") 158 158 159 - let json_str = graphql_ws.format_message(message) 159 + let json_str = ws.format_message(message) 160 160 161 161 json_str |> should.equal("{\"id\":\"1\",\"type\":\"complete\"}") 162 162 } 163 163 164 164 // Test: Format pong message 165 165 pub fn format_pong_message_test() { 166 - let message = graphql_ws.Pong 166 + let message = ws.Pong 167 167 168 - let json_str = graphql_ws.format_message(message) 168 + let json_str = ws.format_message(message) 169 169 170 170 json_str |> should.equal("{\"type\":\"pong\"}") 171 171 } ··· 174 174 pub fn parse_invalid_json_test() { 175 175 let json_str = "not valid json" 176 176 177 - case graphql_ws.parse_message(json_str) { 177 + case ws.parse_message(json_str) { 178 178 Error(_) -> should.be_true(True) 179 179 Ok(_) -> should.fail() 180 180 } ··· 184 184 pub fn parse_unknown_type_test() { 185 185 let json_str = "{\"type\":\"unknown_message_type\"}" 186 186 187 - case graphql_ws.parse_message(json_str) { 187 + case ws.parse_message(json_str) { 188 188 Error(_) -> should.be_true(True) 189 189 Ok(_) -> should.fail() 190 190 } ··· 194 194 pub fn parse_subscribe_missing_id_test() { 195 195 let json_str = "{\"type\":\"subscribe\",\"payload\":{}}" 196 196 197 - case graphql_ws.parse_message(json_str) { 197 + case ws.parse_message(json_str) { 198 198 Error(_) -> should.be_true(True) 199 199 Ok(_) -> should.fail() 200 200 }