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        #[serde(skip)]
217        wire_name: String,
218        flatten: bool,
219        ty: hir::TypeSpec,
220    },
221    Object {
222        fields: Vec<HttpRequestBodyField>,
223    },
224    Stream {
225        source_param: String,
226        #[serde(skip)]
227        wire_name: String,
228        item_ty: hir::TypeSpec,
229        codec: HttpStreamPayloadCodec,
230    },
231}
232
233#[derive(Debug, Clone, Serialize, Deserialize)]
234pub struct HttpRequestBodyField {
235    pub source_param: String,
236    pub field_name: String,
237    pub ty: hir::TypeSpec,
238    pub optional: bool,
239    pub flatten: bool,
240}
241
242#[derive(Debug, Clone, Serialize, Deserialize)]
243pub struct HttpResponseMapping {
244    pub header: Vec<HttpOutputBinding>,
245    pub cookie: Vec<HttpOutputBinding>,
246    pub body: HttpResponseBodyMapping,
247    pub status: String,
248    /// Non-empty when the return type is an `@http` union: the success
249    /// response is content-negotiated across these representations.
250    #[serde(default)]
251    pub representations: Vec<HttpRepresentation>,
252}
253
254/// One content-negotiated representation of an `@http` union response.
255#[derive(Debug, Clone, Serialize, Deserialize)]
256pub struct HttpRepresentation {
257    /// Shared protocol value; both the case name and media type derive from it.
258    pub content_type: xidl_http::ContentType,
259    /// Case payload type.
260    pub ty: hir::TypeSpec,
261    /// `sequence<octet>` cases respond with raw bytes.
262    pub is_byte: bool,
263}
264
265/// An `@http` union declaration: HTTP representation switching (#296).
266#[derive(Debug, Clone, Serialize, Deserialize)]
267pub struct HttpUnion {
268    pub module_path: Vec<String>,
269    pub ident: String,
270    pub cases: Vec<HttpRepresentation>,
271}
272
273#[derive(Debug, Clone, Serialize, Deserialize)]
274pub struct HttpOutputBinding {
275    pub source: HttpOutputSource,
276    pub wire_name: String,
277    pub ty: hir::TypeSpec,
278}
279
280#[derive(Debug, Clone, Serialize, Deserialize)]
281pub enum HttpOutputSource {
282    ReturnValue,
283    Param { name: String },
284}
285
286#[derive(Debug, Clone, Serialize, Deserialize)]
287pub struct HttpResponseBodyMapping {
288    pub content_type: Option<String>,
289    pub content_type_explicit: bool,
290    pub codec: Option<HttpBodyCodec>,
291    pub shape: HttpResponseBodyShape,
292}
293
294#[derive(Debug, Clone, Serialize, Deserialize)]
295pub enum HttpResponseBodyShape {
296    Empty,
297    ReturnOnly {
298        ty: hir::TypeSpec,
299    },
300    SingleValue {
301        source: HttpOutputSource,
302        ty: hir::TypeSpec,
303    },
304    Object {
305        fields: Vec<HttpResponseBodyField>,
306    },
307    Stream {
308        item_source: HttpOutputSource,
309        item_ty: hir::TypeSpec,
310        codec: HttpStreamPayloadCodec,
311    },
312}
313
314#[derive(Debug, Clone, Serialize, Deserialize)]
315pub struct HttpResponseBodyField {
316    pub source: HttpOutputSource,
317    pub field_name: String,
318    pub ty: hir::TypeSpec,
319}
320
321#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
322pub enum HttpBodyCodec {
323    Json,
324    Text,
325    FormUrlEncoded,
326    Msgpack,
327}
328
329#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
330pub enum HttpStreamPayloadCodec {
331    Ndjson,
332    Sse,
333    Bytes,
334}
335
336#[derive(Debug, Clone, Serialize, Deserialize)]
337pub struct HttpInterface {
338    pub name: String,
339    pub module_path: Vec<String>,
340    pub operations: Vec<HttpOperation>,
341}
342
343#[derive(Debug, Clone, Serialize, Deserialize)]
344pub struct RestHirDocument {
345    pub spec: hir::Specification,
346    pub document: HttpDocumentMetadata,
347    pub interfaces: Vec<HttpInterface>,
348}
349
350impl RestHirDocument {
351    pub fn from_props(props: &hir::ParserProperties) -> ParserResult<Self> {
352        let value = props
353            .get("rest_hir")
354            .cloned()
355            .ok_or_else(|| ParseError::Message("missing rest_hir properties".to_string()))?;
356        serde_json::from_value(value).map_err(|err| ParseError::Message(err.to_string()))
357    }
358
359    pub fn find_interface(&self, module_path: &[String], name: &str) -> Option<&HttpInterface> {
360        self.interfaces
361            .iter()
362            .find(|interface| interface.name == name && interface.module_path == module_path)
363    }
364}