Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Page
Library
Module
Module type
Parameter
Class
Class type
Source
validate.ml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172type validated = { endpoint : Endpoint.t; path_values : (string * string) list; query_values : (string * string) list; body : Yojson.Safe.t option; } type validated_response = { endpoint : Endpoint.t; status : int; body : Yojson.Safe.t option; } type expected_response_body = | No_body | Json_body : 'a Json_codec.t -> expected_response_body let retag location = function | Ok value -> Ok value | Error error -> Error { error with Error.location } let decode_scalar location codec value = codec.Codec.decode value |> retag location let required_query_missing name = Error.make ~location:(Error.Query_param name) "missing required query parameter" let path_param_missing name = Error.make ~location:(Error.Path_param name) "missing path parameter" let first_value name values = List.assoc_opt name values let decode_optional_json body codec = match body with | None -> Ok None | Some json -> ( match codec.Json_codec.decode json with | Ok value -> Ok (Some value) | Error error -> Error error) let validate_param path_values query_values = function | Endpoint.Path_param (name, codec) -> ( match first_value name path_values with | None -> Some (path_param_missing name) | Some value -> ( match decode_scalar (Error.Path_param name) codec value with | Ok _ -> None | Error error -> Some error)) | Endpoint.Query_param (name, required, codec) -> ( match first_value name query_values with | None when required -> Some (required_query_missing name) | None -> None | Some value -> ( match decode_scalar (Error.Query_param name) codec value with | Ok _ -> None | Error error -> Some error)) let validate_request_body endpoint_body request_body = match (endpoint_body, request_body) with | None, _ -> None | Some _, None -> Some (Error.make ~location:Error.Body "missing request body") | Some (Endpoint.Body codec), Some json -> ( match codec.Json_codec.decode json with | Ok _ -> None | Error error -> Some error) let request endpoint request = if endpoint.Endpoint.method_ <> request.Request.method_ then Error [ Error.make ~location:Error.Method ~expected:(Endpoint.method_to_string endpoint.method_) ~got:(Endpoint.method_to_string request.method_) "HTTP method does not match"; ] else match Path_template.match_path endpoint.path request.path with | Error error -> Error [ error ] | Ok path_values -> let param_errors = endpoint.params |> List.filter_map (validate_param path_values request.query) in let body_errors = match validate_request_body endpoint.body request.body with | None -> [] | Some error -> [ error ] in let errors = param_errors @ body_errors in if errors = [] then Ok { endpoint; path_values; query_values = request.query; body = request.body; } else Error errors let path validated name codec = match first_value name validated.path_values with | None -> Error (path_param_missing name) | Some value -> decode_scalar (Error.Path_param name) codec value let query validated name codec = match first_value name validated.query_values with | None -> Ok None | Some value -> ( match decode_scalar (Error.Query_param name) codec value with | Ok value -> Ok (Some value) | Error error -> Error error) let body (validated : validated) codec = decode_optional_json validated.body codec let status_to_string status = string_of_int status let endpoint_label endpoint = Endpoint.method_to_string endpoint.Endpoint.method_ ^ " " ^ Path_template.raw endpoint.path let expected_response_statuses endpoint = List.map (fun (Endpoint.Response (status, _)) -> status_to_string status) endpoint.Endpoint.responses let expected_response_for_status endpoint status = List.find_map (fun (Endpoint.Response (declared_status, body)) -> if declared_status = status then Some (match body with None -> No_body | Some codec -> Json_body codec) else None) endpoint.Endpoint.responses let unexpected_response_status endpoint response = let expected = match expected_response_statuses endpoint with | [] -> None | statuses -> Some (String.concat ", " statuses) in Error.make ?expected ~got:(status_to_string (Response.status response)) ~location:Error.Status ("unexpected response status for " ^ endpoint_label endpoint) let validate_response_body expected_body body = match (expected_body, body) with | No_body, None -> None | No_body, Some _ -> Some (Error.make ~location:Error.Body "unexpected response body") | Json_body _, None -> Some (Error.make ~location:Error.Body "missing response body") | Json_body codec, Some json -> ( match codec.Json_codec.decode json with | Ok _ -> None | Error error -> Some error) let response endpoint (response : Response.t) = let status = Response.status response in let body = Response.body response in match expected_response_for_status endpoint status with | None -> Error [ unexpected_response_status endpoint response ] | Some expected_body -> ( match validate_response_body expected_body body with | None -> Ok { endpoint; status; body } | Some error -> Error [ error ]) let response_body (validated : validated_response) codec = decode_optional_json validated.body codec