1use std::error::Error;
2use std::fmt::{self, Display, Formatter};
3
4use http::header::{ACCEPT, CONTENT_ENCODING, CONTENT_TYPE};
5use http::{HeaderMap, Method, Request, Response, StatusCode};
6use wip_protocol::{
7 CallOperationRequest, CallOperationResponse, FetchInterfaceRequest, FetchInterfaceResponse,
8 INTERFACE_FORMAT_V1, InterfaceDescriptor, InterfaceTarget, ObserveRequest, ObserveResponse,
9 ProtocolError, ProtocolInteraction, Target,
10};
11
12use crate::response::{self, DecodedResponse};
13use crate::{
14 BodyKind, ClientResponseError, CodecError, Endpoint, InvalidResponseKind, JSON_CONTENT_TYPE,
15 Limits, Route, json,
16};
17
18fn request(
19 endpoint: &Endpoint,
20 route: Route,
21 body: Vec<u8>,
22) -> Result<Request<Vec<u8>>, http::Error> {
23 Request::builder()
24 .method(Method::POST)
25 .uri(endpoint.route(route).as_str())
26 .header(CONTENT_TYPE, JSON_CONTENT_TYPE)
27 .header(ACCEPT, "application/json")
28 .body(body)
29}
30
31fn response(status: StatusCode, body: Vec<u8>) -> Result<Response<Vec<u8>>, http::Error> {
32 Response::builder()
33 .status(status)
34 .header(CONTENT_TYPE, JSON_CONTENT_TYPE)
35 .body(body)
36}
37
38fn require_response_content_type<B>(response: &Response<B>) -> Result<(), ClientResponseError> {
39 if !has_supported_content_coding(response.headers()) {
40 return Err(response::unsupported_content_coding(response.status()));
41 }
42 let valid = response
43 .headers()
44 .get(CONTENT_TYPE)
45 .and_then(|value| value.to_str().ok())
46 .is_some_and(is_json_content_type);
47 if valid {
48 Ok(())
49 } else if response.status().is_success() {
50 Err(ClientResponseError::invalid(
51 InvalidResponseKind::InvalidSuccessBody,
52 "missing or non-UTF-8 application/json Content-Type",
53 ))
54 } else {
55 Err(response::transport_failure(response.status()))
56 }
57}
58
59fn is_utf8_charset(value: &str) -> bool {
60 let value = value.trim();
61 value.eq_ignore_ascii_case("utf-8")
62 || value
63 .strip_prefix('"')
64 .and_then(|value| value.strip_suffix('"'))
65 .is_some_and(|value| value.eq_ignore_ascii_case("utf-8"))
66}
67
68#[must_use]
74pub fn is_json_content_type(value: &str) -> bool {
75 let mut parts = value.split(';');
76 if !parts
77 .next()
78 .is_some_and(|media| media.trim().eq_ignore_ascii_case("application/json"))
79 {
80 return false;
81 }
82 let mut saw_charset = false;
83 for parameter in parts {
84 let Some((name, value)) = parameter.trim().split_once('=') else {
85 return false;
86 };
87 if !name.trim().eq_ignore_ascii_case("charset") || !is_utf8_charset(value) || saw_charset {
88 return false;
89 }
90 saw_charset = true;
91 }
92 true
93}
94
95fn accepts_json(headers: &HeaderMap) -> bool {
96 let mut present = false;
97 let mut best_match: Option<((u8, u8), f32)> = None;
98 for value in headers.get_all(ACCEPT) {
99 present = true;
100 let Ok(value) = value.to_str() else {
101 continue;
102 };
103 for range in value.split(',') {
104 let mut parts = range.split(';');
105 let media_range = parts.next().unwrap_or_default().trim();
106 let mut quality = 1.0_f32;
107 let mut saw_quality = false;
108 let mut saw_charset = false;
109 let mut media_parameter_count = 0;
110 let mut valid = true;
111 for parameter in parts {
112 let parameter = parameter.trim();
113 let (name, value) = parameter
114 .split_once('=')
115 .map_or((parameter, None), |(name, value)| {
116 (name.trim(), Some(value))
117 });
118 if name.is_empty() {
119 valid = false;
120 break;
121 }
122 if saw_quality {
123 continue;
126 }
127 if name.eq_ignore_ascii_case("q") {
128 let Some(value) = value else {
129 valid = false;
130 break;
131 };
132 saw_quality = true;
133 match value.trim().parse::<f32>() {
134 Ok(value) if (0.0..=1.0).contains(&value) => quality = value,
135 _ => {
136 valid = false;
137 break;
138 }
139 }
140 } else {
141 let Some(value) = value else {
142 valid = false;
143 break;
144 };
145 if !name.eq_ignore_ascii_case("charset")
146 || !is_utf8_charset(value)
147 || saw_charset
148 {
149 valid = false;
150 break;
151 }
152 saw_charset = true;
153 media_parameter_count += 1;
154 }
155 }
156 let media_specificity = if media_range.eq_ignore_ascii_case("application/json") {
157 Some(2)
158 } else if media_range.eq_ignore_ascii_case("application/*") {
159 Some(1)
160 } else if media_range == "*/*" {
161 Some(0)
162 } else {
163 None
164 };
165 if valid && let Some(media_specificity) = media_specificity {
166 let specificity = (media_specificity, media_parameter_count);
167 match best_match {
168 Some((best_specificity, _)) if best_specificity > specificity => {}
169 Some((best_specificity, best_quality)) if best_specificity == specificity => {
170 best_match = Some((specificity, best_quality.max(quality)));
171 }
172 _ => best_match = Some((specificity, quality)),
173 }
174 }
175 }
176 }
177 if present {
178 best_match.is_some_and(|(_, quality)| quality > 0.0)
179 } else {
180 true
181 }
182}
183
184#[derive(Debug)]
186pub enum RequestMetadataError {
187 NotFound,
189 MethodNotAllowed,
191 UnsupportedMediaType,
193 NotAcceptable,
195}
196
197impl RequestMetadataError {
198 #[must_use]
200 pub const fn status(&self) -> StatusCode {
201 match self {
202 Self::NotFound => StatusCode::NOT_FOUND,
203 Self::MethodNotAllowed => StatusCode::METHOD_NOT_ALLOWED,
204 Self::UnsupportedMediaType => StatusCode::UNSUPPORTED_MEDIA_TYPE,
205 Self::NotAcceptable => StatusCode::NOT_ACCEPTABLE,
206 }
207 }
208
209 #[must_use]
211 pub const fn allow(&self) -> Option<&'static str> {
212 match self {
213 Self::MethodNotAllowed => Some("POST"),
214 Self::NotFound | Self::UnsupportedMediaType | Self::NotAcceptable => None,
215 }
216 }
217}
218
219impl Display for RequestMetadataError {
220 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
221 match self {
222 Self::NotFound => formatter.write_str("request URI is not a standard WIP route"),
223 Self::MethodNotAllowed => formatter.write_str("standard WIP routes require POST"),
224 Self::UnsupportedMediaType => {
225 formatter.write_str("expected identity-coded UTF-8 application/json Content-Type")
226 }
227 Self::NotAcceptable => formatter.write_str("request does not accept application/json"),
228 }
229 }
230}
231
232impl Error for RequestMetadataError {}
233
234pub(crate) fn has_supported_content_coding(headers: &HeaderMap) -> bool {
235 headers.get_all(CONTENT_ENCODING).iter().all(|value| {
236 value.to_str().is_ok_and(|value| {
237 let mut codings = value.split(',').map(str::trim);
238 let first = codings.next();
239 first.is_some_and(|coding| coding.eq_ignore_ascii_case("identity"))
240 && codings.all(|coding| coding.eq_ignore_ascii_case("identity"))
241 })
242 })
243}
244
245pub fn validate_http_request<B>(
252 endpoint: &Endpoint,
253 route: Route,
254 value: &Request<B>,
255) -> Result<(), RequestMetadataError> {
256 let expected = endpoint.route(route);
257 if value.uri().path() != expected.path() || value.uri().query().is_some() {
258 return Err(RequestMetadataError::NotFound);
259 }
260 if value.method() != Method::POST {
261 return Err(RequestMetadataError::MethodNotAllowed);
262 }
263 let valid_content_type = value
264 .headers()
265 .get(CONTENT_TYPE)
266 .and_then(|value| value.to_str().ok())
267 .is_some_and(is_json_content_type);
268 if !valid_content_type || !has_supported_content_coding(value.headers()) {
269 return Err(RequestMetadataError::UnsupportedMediaType);
270 }
271 if !accepts_json(value.headers()) {
272 return Err(RequestMetadataError::NotAcceptable);
273 }
274 Ok(())
275}
276
277pub fn encode_observe_request(
279 endpoint: &Endpoint,
280 value: &ObserveRequest,
281 limits: Limits,
282) -> Result<Request<Vec<u8>>, EncodeHttpError> {
283 value.validate().map_err(CodecError::from)?;
284 let body = json::serialize(
285 &json::observe_request_to_json(value),
286 BodyKind::Request,
287 limits,
288 )?;
289 Ok(request(endpoint, Route::Observe, body)?)
290}
291
292pub fn decode_observe_request(body: &[u8], limits: Limits) -> Result<ObserveRequest, CodecError> {
294 let value = json::parse(body, BodyKind::Request, limits)?;
295 json::observe_request_from_json(&value)
296}
297
298pub fn encode_fetch_interface_request(
300 endpoint: &Endpoint,
301 value: &FetchInterfaceRequest,
302 limits: Limits,
303) -> Result<Request<Vec<u8>>, EncodeHttpError> {
304 let body = json::serialize(
305 &json::fetch_interface_request_to_json(value),
306 BodyKind::Request,
307 limits,
308 )?;
309 Ok(request(endpoint, Route::FetchInterface, body)?)
310}
311
312pub fn decode_fetch_interface_request(
314 body: &[u8],
315 limits: Limits,
316) -> Result<FetchInterfaceRequest, CodecError> {
317 let value = json::parse(body, BodyKind::Request, limits)?;
318 json::fetch_interface_request_from_json(&value)
319}
320
321#[derive(Debug, Clone, PartialEq, Eq)]
328pub struct CallOperationMetadata {
329 pub target: Target,
331 pub interface: InterfaceTarget,
333 pub operation: String,
335}
336
337impl CallOperationMetadata {
338 pub fn decode_request(
341 &self,
342 body: &[u8],
343 descriptor: &InterfaceDescriptor,
344 limits: Limits,
345 ) -> Result<CallOperationRequest, CodecError> {
346 let request = decode_call_operation_request(body, descriptor, limits)?;
347 if request.target != self.target
348 || request.interface != self.interface
349 || request.operation != self.operation
350 {
351 return Err(CodecError::InvalidField {
352 field: "request".into(),
353 reason: "call metadata changed after descriptor resolution".into(),
354 });
355 }
356 Ok(request)
357 }
358}
359
360pub fn decode_call_operation_metadata(
362 body: &[u8],
363 limits: Limits,
364) -> Result<CallOperationMetadata, CodecError> {
365 let value = json::parse(body, BodyKind::Request, limits)?;
366 let (target, interface, operation) = json::call_request_metadata_from_json(&value)?;
367 Ok(CallOperationMetadata {
368 target,
369 interface,
370 operation,
371 })
372}
373
374pub fn encode_call_operation_request(
376 endpoint: &Endpoint,
377 value: &CallOperationRequest,
378 descriptor: &InterfaceDescriptor,
379 limits: Limits,
380) -> Result<Request<Vec<u8>>, EncodeHttpError> {
381 let value = json::call_request_to_json(value, descriptor)?;
382 let body = json::serialize(&value, BodyKind::Request, limits)?;
383 Ok(request(endpoint, Route::CallOperation, body)?)
384}
385
386pub fn decode_call_operation_request(
388 body: &[u8],
389 descriptor: &InterfaceDescriptor,
390 limits: Limits,
391) -> Result<CallOperationRequest, CodecError> {
392 let value = json::parse(body, BodyKind::Request, limits)?;
393 json::call_request_from_json(&value, descriptor)
394}
395
396pub fn encode_observe_response(
398 request_value: &ObserveRequest,
399 value: &ObserveResponse,
400 limits: Limits,
401) -> Result<Response<Vec<u8>>, EncodeHttpError> {
402 request_value
403 .validate_response(value)
404 .map_err(CodecError::from)?;
405 let body = json::serialize(
406 &json::observe_response_to_json(value),
407 BodyKind::Response,
408 limits,
409 )?;
410 Ok(response(StatusCode::OK, body)?)
411}
412
413pub fn decode_observe_response<B: AsRef<[u8]>>(
415 request_value: &ObserveRequest,
416 value: &Response<B>,
417 limits: Limits,
418) -> Result<DecodedResponse<ObserveResponse>, ClientResponseError> {
419 require_response_content_type(value)?;
420 response::decode(
421 value.status(),
422 value.body().as_ref(),
423 ProtocolInteraction::Observe,
424 limits,
425 |json| {
426 let observation = json::observe_response_from_json(json)?;
427 request_value.validate_response(&observation)?;
428 Ok(observation)
429 },
430 )
431}
432
433pub fn encode_fetch_interface_response(
435 request_value: &FetchInterfaceRequest,
436 value: &FetchInterfaceResponse,
437 limits: Limits,
438) -> Result<Response<Vec<u8>>, EncodeHttpError> {
439 value
440 .validate_for(request_value)
441 .map_err(CodecError::from)?;
442 let body = json::serialize(
443 &json::fetch_interface_response_to_json(value),
444 BodyKind::Response,
445 limits,
446 )?;
447 Ok(response(StatusCode::OK, body)?)
448}
449
450pub fn decode_fetch_interface_response<B: AsRef<[u8]>>(
452 request_value: &FetchInterfaceRequest,
453 value: &Response<B>,
454 limits: Limits,
455) -> Result<DecodedResponse<FetchInterfaceResponse>, ClientResponseError> {
456 require_response_content_type(value)?;
457 let decoded = response::decode(
458 value.status(),
459 value.body().as_ref(),
460 ProtocolInteraction::FetchInterface,
461 limits,
462 |json| {
463 let interface = json::fetch_interface_response_from_json(json)?;
464 interface.validate_for(request_value)?;
465 Ok(interface)
466 },
467 )?;
468 if let DecodedResponse::Success(success) = &decoded
469 && success.descriptor.format != INTERFACE_FORMAT_V1
470 {
471 return Err(ClientResponseError::UnsupportedDescriptorFormat {
472 format: success.descriptor.format.clone(),
473 });
474 }
475 Ok(decoded)
476}
477
478pub fn encode_call_operation_response(
480 request_value: &CallOperationRequest,
481 descriptor: &InterfaceDescriptor,
482 value: &CallOperationResponse,
483 limits: Limits,
484) -> Result<Response<Vec<u8>>, EncodeHttpError> {
485 let json = json::call_response_to_json(value, descriptor, &request_value.operation)?;
486 let body = json::serialize(&json, BodyKind::Response, limits)?;
487 Ok(response(StatusCode::OK, body)?)
488}
489
490pub fn decode_call_operation_response<B: AsRef<[u8]>>(
492 request_value: &CallOperationRequest,
493 descriptor: &InterfaceDescriptor,
494 value: &Response<B>,
495 limits: Limits,
496) -> Result<DecodedResponse<CallOperationResponse>, ClientResponseError> {
497 require_response_content_type(value)?;
498 response::decode(
499 value.status(),
500 value.body().as_ref(),
501 ProtocolInteraction::CallOperation,
502 limits,
503 |json| json::call_response_from_json(json, descriptor, &request_value.operation),
504 )
505}
506
507fn validate_protocol_error(
508 interaction: ProtocolInteraction,
509 error: &ProtocolError,
510) -> Result<(), CodecError> {
511 if !error.code.is_allowed_for(interaction) {
512 return Err(CodecError::InvalidField {
513 field: "error.code".into(),
514 reason: format!(
515 "code `{}` is not allowed for {interaction:?}",
516 response::error_code_name(error.code)
517 ),
518 });
519 }
520 Ok(())
521}
522
523fn encode_protocol_error_with_status(
524 interaction: ProtocolInteraction,
525 error: &ProtocolError,
526 status: StatusCode,
527 limits: Limits,
528) -> Result<Response<Vec<u8>>, EncodeHttpError> {
529 validate_protocol_error(interaction, error)?;
530 let body = json::serialize(&response::error_to_json(error), BodyKind::Response, limits)?;
531 Ok(response(status, body)?)
532}
533
534pub fn encode_protocol_error_response(
542 interaction: ProtocolInteraction,
543 error: &ProtocolError,
544 limits: Limits,
545) -> Result<Response<Vec<u8>>, EncodeHttpError> {
546 encode_protocol_error_with_status(
547 interaction,
548 error,
549 response::status_for_error(error.code),
550 limits,
551 )
552}
553
554#[derive(Debug)]
556pub enum EncodeHttpError {
557 Codec(CodecError),
559 Http(http::Error),
561}
562
563impl std::fmt::Display for EncodeHttpError {
564 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
565 match self {
566 Self::Codec(error) => error.fmt(formatter),
567 Self::Http(error) => error.fmt(formatter),
568 }
569 }
570}
571
572impl std::error::Error for EncodeHttpError {
573 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
574 match self {
575 Self::Codec(error) => Some(error),
576 Self::Http(error) => Some(error),
577 }
578 }
579}
580
581impl From<CodecError> for EncodeHttpError {
582 fn from(value: CodecError) -> Self {
583 Self::Codec(value)
584 }
585}
586
587impl From<http::Error> for EncodeHttpError {
588 fn from(value: http::Error) -> Self {
589 Self::Http(value)
590 }
591}