Skip to main content

xidl_parser/rest_hir/model/
mod.rs

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