service status on atproto

feat!: improve parsing push request and add ok, err for result

ptr.pet bf0bc92e f6eac273

verified
Changed files
+50 -24
proxy
+39 -17
proxy/src/index.ts
··· 9 9 } from "@atcute/identity-resolver"; 10 10 import { 11 11 parseCanonicalResourceUri, 12 - parseResourceUri, 13 12 type RecordKey, 14 13 } from "@atcute/lexicons/syntax"; 15 14 import { config } from "./config"; 16 15 import type {} from "@atcute/atproto"; 17 - import { safeParse } from "@atcute/lexicons"; 16 + import { is, safeParse } from "@atcute/lexicons"; 18 17 import { 19 18 SystemsGazeBarometerState, 20 19 SystemsGazeBarometerHost, 21 20 SystemsGazeBarometerService, 22 21 SystemsGazeBarometerCheck, 23 22 } from "barometer-lexicon"; 24 - import { expect, getRecord, putRecord } from "./utils"; 23 + import { err, expect, getRecord, ok, putRecord, type Result } from "./utils"; 25 24 26 25 interface Check { 27 26 record: SystemsGazeBarometerCheck.Main; ··· 75 74 hostname, 76 75 ); 77 76 } 77 + 78 + interface PushRequest { 79 + serviceName?: string; // service manager service name 80 + state: SystemsGazeBarometerState.Main; 81 + } 82 + 83 + const parsePushRequest = (json: unknown): Result<PushRequest, string> => { 84 + if (typeof json !== "object" || json === null) { 85 + return err("invalid request"); 86 + } 87 + if ("serviceName" in json && typeof json.serviceName !== "string") { 88 + return err("serviceName is not a string"); 89 + } 90 + if ("state" in json) { 91 + const parsed = safeParse(SystemsGazeBarometerState.mainSchema, json.state); 92 + if (!parsed.ok) { 93 + return err(`state is invalid: ${parsed.message}`); 94 + } 95 + } else { 96 + return err("state not found"); 97 + } 98 + return ok(json as PushRequest); 99 + }; 78 100 79 101 const badRequest = <Error extends { msg: string }>(error: Error) => { 80 102 return new Response(JSON.stringify(error), { status: 400 }); ··· 83 105 routes: { 84 106 "/push": { 85 107 POST: async (req) => { 86 - const maybeState = safeParse( 87 - SystemsGazeBarometerState.mainSchema, 88 - await req.json(), 89 - ); 90 - if (!maybeState.ok) { 108 + const maybeData = parsePushRequest(await req.json()); 109 + if (!maybeData.ok) { 91 110 return badRequest({ 92 - msg: `invalid state: ${maybeState.message}`, 93 - issues: maybeState.issues, 111 + msg: `invalid request: ${maybeData.error}`, 94 112 }); 95 113 } 96 - const state = maybeState.value; 114 + const data = maybeData.value; 97 115 98 116 const serviceAtUri = expect( 99 - parseCanonicalResourceUri(state.forService), 117 + parseCanonicalResourceUri(data.state.forService), 100 118 ); 101 119 let service = services.get(serviceAtUri.rkey); 102 120 if (!service) { ··· 105 123 serviceAtUri.rkey, 106 124 ); 107 125 if (!serviceRecord.ok) { 108 - return badRequest({ msg: "service was not found" }); 126 + return badRequest({ 127 + msg: `service was not found or is invalid: ${serviceRecord.error}`, 128 + }); 109 129 } 110 130 service = { 111 131 record: serviceRecord.value, ··· 114 134 services.set(serviceAtUri.rkey, service); 115 135 } 116 136 117 - if (state.generatedBy) { 137 + if (data.state.generatedBy) { 118 138 const checkAtUri = expect( 119 - parseCanonicalResourceUri(state.generatedBy), 139 + parseCanonicalResourceUri(data.state.generatedBy), 120 140 ); 121 141 let check = service.checks.get(checkAtUri.rkey); 122 142 if (!check) { ··· 125 145 checkAtUri.rkey, 126 146 ); 127 147 if (!checkRecord.ok) { 128 - return badRequest({ msg: "check record not found" }); 148 + return badRequest({ 149 + msg: `check record not found or is invalid: ${checkRecord.error}`, 150 + }); 129 151 } 130 152 check = { 131 153 record: checkRecord.value, ··· 134 156 } 135 157 } 136 158 137 - const result = await putRecord(state); 159 + const result = await putRecord(data.state); 138 160 return new Response( 139 161 JSON.stringify({ cid: result.cid, uri: result.uri }), 140 162 );
+11 -7
proxy/src/utils.ts
··· 1 1 import { safeParse, type InferOutput, type RecordKey } from "@atcute/lexicons"; 2 2 import { schemas as BarometerSchemas } from "barometer-lexicon"; 3 3 import { config } from "./config"; 4 - import { ok } from "@atcute/client"; 4 + import { ok as clientOk } from "@atcute/client"; 5 5 import { now as generateTid } from "@atcute/tid"; 6 6 import { atpClient } from "."; 7 7 ··· 14 14 ok: false; 15 15 error: E; 16 16 }; 17 + 18 + export const ok = <T, E>(value: T): Result<T, E> => { 19 + return { ok: true, value }; 20 + }; 21 + export const err = <T, E>(error: E): Result<T, E> => { 22 + return { ok: false, error }; 23 + }; 17 24 18 25 export const expect = <T, E>( 19 26 v: Result<T, E>, ··· 41 48 }, 42 49 }); 43 50 if (!maybeRecord.ok) { 44 - return { 45 - ok: false, 46 - error: maybeRecord.data.message ?? maybeRecord.data.error, 47 - }; 51 + return err(maybeRecord.data.message ?? maybeRecord.data.error); 48 52 } 49 53 const maybeTyped = safeParse( 50 54 BarometerSchemas[collection], 51 55 maybeRecord.data.value, 52 56 ); 53 57 if (!maybeTyped.ok) { 54 - return { ok: false, error: maybeTyped.message }; 58 + return err(maybeTyped.message); 55 59 } 56 60 return maybeTyped; 57 61 }; ··· 62 66 record: InferOutput<(typeof BarometerSchemas)[Collection]>, 63 67 rkey?: RecordKey, 64 68 ) => { 65 - return await ok( 69 + return await clientOk( 66 70 atpClient.post("com.atproto.repo.putRecord", { 67 71 input: { 68 72 collection: record["$type"],