Skip to main content

xidl_parser/rest_hir/model/
mod.rs

1use crate::error::{ParseError, ParserResult};
2use crate::hir;
3use serde::{Deserialize, Serialize};
4
5#[cfg(test)]
6mod tests;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9pub enum HttpMethod {
10    Get,
11    Post,
12    Put,
13    Patch,
14    Delete,
15    Head,
16    Options,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
20pub enum HttpParamKind {
21    Path,
22    Query,
23    Header,
24    Cookie,
25    Body,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
29pub enum HttpOperationSource {
30    Method,
31    AttributeGet,
32    AttributeSet,
33    AttributeWatch,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
37pub struct HttpRoute {
38    pub path: String,
39    pub path_params: Vec<String>,
40    pub query_params: Vec<String>,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct HttpParam {
45    pub name: String,
46    pub wire_name: String,
47    pub ty: hir::TypeSpec,
48    pub kind: HttpParamKind,
49    pub optional: bool,
50    pub flatten: bool,
51}
52
53#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
54pub struct HttpDocumentServer {
55    pub base_url: String,
56    pub description: Option<String>,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize, Default)]
60pub struct HttpDocumentMetadata {
61    pub package: Option<String>,
62    pub version: Option<String>,
63    pub servers: Vec<HttpDocumentServer>,
64    /// Document-level `exception` declarations projected for HTTP use.
65    #[serde(default)]
66    pub exceptions: Vec<HttpException>,
67    /// Document-level `@http` unions: content-negotiated representations.
68    #[serde(default)]
69    pub http_unions: Vec<HttpUnion>,
70}
71
72/// An `exception` declaration with its HTTP error-channel semantics.
73///
74/// Members annotated `@header`/`@cookie` become the error response's
75/// headers/cookies; unannotated members form the JSON error body.
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct HttpException {
78    pub module_path: Vec<String>,
79    pub ident: String,
80    /// HTTP status code bound via `@http(<status>)` / `@http(status = <status>)`.
81    pub status: u16,
82    pub headers: Vec<HttpExceptionMember>,
83    pub cookies: Vec<HttpExceptionMember>,
84    pub body: Vec<HttpExceptionField>,
85}
86
87/// A `@header`/`@cookie` annotated member of an exception.
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct HttpExceptionMember {
90    pub field: String,
91    pub wire_name: String,
92    pub ty: hir::TypeSpec,
93    /// `sequence<...>` members repeat the header (append) instead of replacing it.
94    pub is_multi: bool,
95    /// `@optional` members may be absent: the field is `Option<_>` and the
96    /// header is only written when a value exists.
97    #[serde(default)]
98    pub optional: bool,
99}
100
101/// An unannotated member of an exception: part of the JSON error body.
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct HttpExceptionField {
104    pub field: String,
105    /// JSON property name after applying the existing naming annotations.
106    pub wire_name: String,
107    /// Whether the JSON property may be absent.
108    pub optional: bool,
109    pub ty: hir::TypeSpec,
110}
111
112/// A `raises(...)` entry on an operation, resolved to a declared exception.
113#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct HttpExceptionRef {
115    pub module_path: Vec<String>,
116    pub ident: String,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct HttpOperation {
121    pub meta: HttpOperationMeta,
122    pub signature: HttpOperationSignature,
123    pub http: HttpOperationHttpMapping,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct HttpOperationMeta {
128    pub name: String,
129    pub operation_id: String,
130    pub source: HttpOperationSource,
131    pub method: HttpMethod,
132    pub routes: Vec<HttpRoute>,
133    pub stream: super::semantics::HttpStreamConfig,
134    pub cors: Option<super::semantics::HttpCorsProfile>,
135    pub security: Option<super::semantics::HttpSecurityProfile>,
136    pub basic_auth_realm: Option<String>,
137    pub deprecated: Option<super::semantics::DeprecatedInfo>,
138    pub upgrade_protocol: Option<String>,
139    pub upgrade_mode: Option<super::semantics::UpgradeMode>,
140    pub websocket: Option<super::semantics::WebSocketConfig>,
141    /// Exceptions declared via `raises(...)` on this operation.
142    #[serde(default)]
143    pub raises: Vec<HttpExceptionRef>,
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct HttpOperationSignature {
148    pub params: Vec<HttpSignatureParam>,
149    pub return_type: Option<hir::TypeSpec>,
150}
151
152#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct HttpSignatureParam {
154    pub name: String,
155    pub ty: hir::TypeSpec,
156    pub direction: HttpSignatureParamDirection,
157    pub is_optional: bool,
158    pub is_flatten: bool,
159    pub annotations: Vec<HttpSignatureParamAnnotation>,
160}
161
162#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
163pub enum HttpSignatureParamDirection {
164    In,
165    Out,
166    InOut,
167}
168
169#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
170pub enum HttpSignatureParamAnnotation {
171    Optional,
172    Flatten,
173    Path { name: String },
174    Query { name: String },
175    Header { name: String },
176    Cookie { name: String },
177    Body,
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct HttpOperationHttpMapping {
182    pub request: HttpRequestMapping,
183    pub response: HttpResponseMapping,
184}
185
186#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct HttpRequestMapping {
188    pub path: Vec<HttpInputBinding>,
189    pub query: Vec<HttpInputBinding>,
190    pub header: Vec<HttpInputBinding>,
191    pub cookie: Vec<HttpInputBinding>,
192    pub body: HttpRequestBodyMapping,
193}
194
195#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct HttpInputBinding {
197    pub source_param: String,
198    pub wire_name: String,
199    pub ty: hir::TypeSpec,
200    pub optional: bool,
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize)]
204pub struct HttpRequestBodyMapping {
205    pub content_type: Option<String>,
206    pub content_type_explicit: bool,
207    pub codec: Option<HttpBodyCodec>,
208    pub shape: HttpRequestBodyShape,
209}
210
211#[derive(Debug, Clone, Serialize, Deserialize)]
212pub enum HttpRequestBodyShape {
213    Empty,
214    SingleValue {
215        source_param: String,
216        flatten: bool,
217        ty: hir::TypeSpec,
218    },
219    Object {
220        fields: Vec<HttpRequestBodyField>,
221    },
222    Stream {
223        source_param: String,
224        item_ty: hir::TypeSpec,
225        codec: HttpStreamPayloadCodec,
226    },
227}
228
229#[derive(Debug, Clone, Serialize, Deserialize)]
230pub struct HttpRequestBodyField {
231    pub source_param: String,
232    pub field_name: String,
233    pub ty: hir::TypeSpec,
234    pub optional: bool,
235    pub flatten: bool,
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize)]
239pub struct HttpResponseMapping {
240    pub header: Vec<HttpOutputBinding>,
241    pub cookie: Vec<HttpOutputBinding>,
242    pub body: HttpResponseBodyMapping,
243    pub status: String,
244    /// Non-empty when the return type is an `@http` union: the success
245    /// response is content-negotiated across these representations.
246    #[serde(default)]
247    pub representations: Vec<HttpRepresentation>,
248}
249
250/// One content-negotiated representation of an `@http` union response.
251#[derive(Debug, Clone, Serialize, Deserialize)]
252pub struct HttpRepresentation {
253    /// Shared protocol value; both the case name and media type derive from it.
254    pub content_type: xidl_http::ContentType,
255    /// Case payload type.
256    pub ty: hir::TypeSpec,
257    /// `sequence<octet>` cases respond with raw bytes.
258    pub is_byte: bool,
259}
260
261/// An `@http` union declaration: HTTP representation switching (#296).
262#[derive(Debug, Clone, Serialize, Deserialize)]
263pub struct HttpUnion {
264    pub module_path: Vec<String>,
265    pub ident: String,
266    pub cases: Vec<HttpRepresentation>,
267}
268
269#[derive(Debug, Clone, Serialize, Deserialize)]
270pub struct HttpOutputBinding {
271    pub source: HttpOutputSource,
272    pub wire_name: String,
273    pub ty: hir::TypeSpec,
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize)]
277pub enum HttpOutputSource {
278    ReturnValue,
279    Param { name: String },
280}
281
282#[derive(Debug, Clone, Serialize, Deserialize)]
283pub struct HttpResponseBodyMapping {
284    pub content_type: Option<String>,
285    pub content_type_explicit: bool,
286    pub codec: Option<HttpBodyCodec>,
287    pub shape: HttpResponseBodyShape,
288}
289
290#[derive(Debug, Clone, Serialize, Deserialize)]
291pub enum HttpResponseBodyShape {
292    Empty,
293    ReturnOnly {
294        ty: hir::TypeSpec,
295    },
296    SingleValue {
297        source: HttpOutputSource,
298        ty: hir::TypeSpec,
299    },
300    Object {
301        fields: Vec<HttpResponseBodyField>,
302    },
303    Stream {
304        item_source: HttpOutputSource,
305        item_ty: hir::TypeSpec,
306        codec: HttpStreamPayloadCodec,
307    },
308}
309
310#[derive(Debug, Clone, Serialize, Deserialize)]
311pub struct HttpResponseBodyField {
312    pub source: HttpOutputSource,
313    pub field_name: String,
314    pub ty: hir::TypeSpec,
315}
316
317#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
318pub enum HttpBodyCodec {
319    Json,
320    Text,
321    FormUrlEncoded,
322    Msgpack,
323}
324
325#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
326pub enum HttpStreamPayloadCodec {
327    Ndjson,
328    Sse,
329    Bytes,
330}
331
332#[derive(Debug, Clone, Serialize, Deserialize)]
333pub struct HttpInterface {
334    pub name: String,
335    pub module_path: Vec<String>,
336    pub operations: Vec<HttpOperation>,
337}
338
339#[derive(Debug, Clone, Serialize, Deserialize)]
340pub struct RestHirDocument {
341    pub spec: hir::Specification,
342    pub document: HttpDocumentMetadata,
343    pub interfaces: Vec<HttpInterface>,
344}
345
346impl RestHirDocument {
347    pub fn from_props(props: &hir::ParserProperties) -> ParserResult<Self> {
348        let value = props
349            .get("rest_hir")
350            .cloned()
351            .ok_or_else(|| ParseError::Message("missing rest_hir properties".to_string()))?;
352        serde_json::from_value(value).map_err(|err| ParseError::Message(err.to_string()))
353    }
354
355    pub fn find_interface(&self, module_path: &[String], name: &str) -> Option<&HttpInterface> {
356        self.interfaces
357            .iter()
358            .find(|interface| interface.name == name && interface.module_path == module_path)
359    }
360}