Skip to main content

odp_core/
models.rs

1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6pub const VERSION: &str = "1.0";
7pub type AdditionalMembers = BTreeMap<String, Value>;
8
9macro_rules! string_enum {
10    ($name:ident { $($variant:ident => $value:literal),+ $(,)? }) => {
11        #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
12        pub enum $name {
13            $(#[serde(rename = $value)] $variant),+
14        }
15    };
16}
17
18string_enum!(Representation { Terse => "terse", Full => "full" });
19string_enum!(PriceType {
20    Fixed => "fixed",
21    Free => "free",
22    Metered => "metered",
23    Quote => "quote",
24    Range => "range",
25    StartingAt => "starting_at",
26});
27#[derive(Clone, Debug, Eq, Hash, PartialEq)]
28pub enum ActionRelation {
29    Download,
30    Invoke,
31    Purchase,
32    Quote,
33    Reserve,
34    Other(String),
35}
36
37impl<'de> Deserialize<'de> for ActionRelation {
38    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
39    where
40        D: serde::Deserializer<'de>,
41    {
42        Ok(match String::deserialize(deserializer)?.as_str() {
43            "download" => Self::Download,
44            "invoke" => Self::Invoke,
45            "purchase" => Self::Purchase,
46            "quote" => Self::Quote,
47            "reserve" => Self::Reserve,
48            other => Self::Other(other.to_owned()),
49        })
50    }
51}
52
53impl Serialize for ActionRelation {
54    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
55    where
56        S: serde::Serializer,
57    {
58        match self {
59            Self::Download => serializer.serialize_str("download"),
60            Self::Invoke => serializer.serialize_str("invoke"),
61            Self::Purchase => serializer.serialize_str("purchase"),
62            Self::Quote => serializer.serialize_str("quote"),
63            Self::Reserve => serializer.serialize_str("reserve"),
64            Self::Other(value) => serializer.serialize_str(value),
65        }
66    }
67}
68string_enum!(ResourceType { Collection => "collection", Offering => "offering" });
69string_enum!(Operation {
70    GetCollection => "get-collection",
71    GetOffering => "get-offering",
72    ListCollectionOfferings => "list-collection-offerings",
73    ListCollections => "list-collections",
74    ListOfferings => "list-offerings",
75    SearchCollections => "search-collections",
76    SearchOfferings => "search-offerings",
77});
78string_enum!(AuthenticationRequirement {
79    NotRequired => "not-required",
80    Optional => "optional",
81    Required => "required",
82});
83string_enum!(Protocol { Aep => "aep", Mpp => "mpp", Tap => "tap", X402 => "x402" });
84string_enum!(PaymentOption {
85    Algorand => "algorand",
86    Aptos => "aptos",
87    Arbitrum => "arbitrum",
88    Avalanche => "avalanche",
89    Base => "base",
90    Card => "card",
91    Ethereum => "ethereum",
92    Hedera => "hedera",
93    Inflow => "inflow",
94    Lightning => "lightning",
95    Polygon => "polygon",
96    Solana => "solana",
97    Stellar => "stellar",
98    Stripe => "stripe",
99    Tempo => "tempo",
100    Ton => "ton",
101});
102string_enum!(FilterType {
103    Boolean => "boolean",
104    Date => "date",
105    DateTime => "date-time",
106    Decimal => "decimal",
107    Integer => "integer",
108    Number => "number",
109    String => "string",
110});
111string_enum!(FilterOperator {
112    Equal => "eq",
113    Exists => "exists",
114    GreaterThan => "gt",
115    GreaterThanOrEqual => "gte",
116    In => "in",
117    LessThan => "lt",
118    LessThanOrEqual => "lte",
119});
120string_enum!(SortDirection { Ascending => "ascending", Descending => "descending" });
121string_enum!(MissingPlacement { First => "first", Last => "last" });
122string_enum!(ServiceBrandingImageType {
123    Png => "image/png",
124    Svg => "image/svg+xml",
125    WebP => "image/webp",
126});
127string_enum!(ResourceImageType {
128    Avif => "image/avif",
129    Jpeg => "image/jpeg",
130    Png => "image/png",
131    Svg => "image/svg+xml",
132    WebP => "image/webp",
133});
134string_enum!(McpEndpointType { StreamableHttp => "streamable-http" });
135
136#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
137pub struct OperationDescriptor {
138    pub authentication: AuthenticationRequirement,
139    pub name: Operation,
140}
141
142#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
143pub struct ServiceProtocols {
144    #[serde(default, skip_serializing_if = "Vec::is_empty")]
145    pub enrollment: Vec<EnrollmentProtocol>,
146    #[serde(default, skip_serializing_if = "Vec::is_empty")]
147    pub payments: Vec<PaymentProtocol>,
148    #[serde(default, skip_serializing_if = "Vec::is_empty")]
149    pub trust: Vec<TrustProtocol>,
150}
151
152#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
153pub struct EnrollmentProtocol {
154    pub name: Protocol,
155}
156
157#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
158pub struct PaymentProtocol {
159    pub authentication: AuthenticationRequirement,
160    pub name: Protocol,
161    #[serde(default, skip_serializing_if = "Vec::is_empty")]
162    pub options: Vec<PaymentOption>,
163}
164
165#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
166pub struct TrustProtocol {
167    pub name: Protocol,
168}
169
170#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
171pub struct HttpConfiguration {
172    pub endpoint_base: String,
173    #[serde(skip_serializing_if = "Option::is_none")]
174    pub openapi: Option<ServiceOpenApi>,
175    #[serde(flatten)]
176    pub additional: AdditionalMembers,
177}
178
179#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
180pub struct CapabilityLink {
181    pub href: String,
182    #[serde(flatten)]
183    pub additional: AdditionalMembers,
184}
185
186#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
187pub struct FilterUnit {
188    pub code: String,
189    pub system: String,
190    #[serde(default, skip_serializing_if = "String::is_empty")]
191    pub title: String,
192    #[serde(flatten)]
193    pub additional: AdditionalMembers,
194}
195
196#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
197pub struct FilterDefinition {
198    pub description: String,
199    pub id: String,
200    pub operators: Vec<FilterOperator>,
201    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
202    pub refinable: bool,
203    pub title: String,
204    #[serde(rename = "type")]
205    pub filter_type: FilterType,
206    #[serde(skip_serializing_if = "Option::is_none")]
207    pub unit: Option<FilterUnit>,
208    #[serde(flatten)]
209    pub additional: AdditionalMembers,
210}
211
212#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
213pub struct SortKey {
214    pub direction: SortDirection,
215    pub filter_id: String,
216    pub missing: MissingPlacement,
217    #[serde(flatten)]
218    pub additional: AdditionalMembers,
219}
220
221#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
222pub struct SortDefinition {
223    pub description: String,
224    pub id: String,
225    pub keys: Vec<SortKey>,
226    pub title: String,
227    #[serde(flatten)]
228    pub additional: AdditionalMembers,
229}
230
231#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
232pub struct FilterCapabilitySource {
233    #[serde(default, skip_serializing_if = "Vec::is_empty")]
234    pub inline: Vec<FilterDefinition>,
235    #[serde(skip_serializing_if = "Option::is_none")]
236    pub linked: Option<CapabilityLink>,
237    #[serde(flatten)]
238    pub additional: AdditionalMembers,
239}
240
241#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
242pub struct SortCapabilitySource {
243    #[serde(default, skip_serializing_if = "Vec::is_empty")]
244    pub inline: Vec<SortDefinition>,
245    #[serde(skip_serializing_if = "Option::is_none")]
246    pub linked: Option<CapabilityLink>,
247    #[serde(flatten)]
248    pub additional: AdditionalMembers,
249}
250
251#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
252pub struct SearchCapabilities {
253    #[serde(skip_serializing_if = "Option::is_none")]
254    pub filters: Option<FilterCapabilitySource>,
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub sorts: Option<SortCapabilitySource>,
257    #[serde(flatten)]
258    pub additional: AdditionalMembers,
259}
260
261#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
262pub struct ServiceBrandingImage {
263    pub src: String,
264    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
265    pub media_type: Option<ServiceBrandingImageType>,
266}
267
268#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
269pub struct ServiceBranding {
270    pub icon: ServiceBrandingImage,
271    pub logo: ServiceBrandingImage,
272}
273
274#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
275pub struct ServiceOpenApi {
276    pub url: String,
277}
278
279#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
280pub struct McpEndpoint {
281    #[serde(default, skip_serializing_if = "String::is_empty")]
282    pub description: String,
283    #[serde(default, skip_serializing_if = "String::is_empty")]
284    pub name: String,
285    #[serde(rename = "type")]
286    pub endpoint_type: McpEndpointType,
287    pub url: String,
288}
289
290#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
291pub struct ServiceDocument {
292    #[serde(skip_serializing_if = "Option::is_none")]
293    pub branding: Option<ServiceBranding>,
294    pub description: String,
295    #[serde(default, skip_serializing_if = "String::is_empty")]
296    pub documentation_url: String,
297    pub http: HttpConfiguration,
298    #[serde(default, skip_serializing_if = "Vec::is_empty")]
299    pub keywords: Vec<String>,
300    pub language: String,
301    pub localizations: Vec<String>,
302    #[serde(default, skip_serializing_if = "Vec::is_empty")]
303    pub mcp: Vec<McpEndpoint>,
304    pub name: String,
305    pub odp_version: String,
306    pub operations: Vec<OperationDescriptor>,
307    #[serde(default, skip_serializing_if = "Vec::is_empty")]
308    pub payment_origins: Vec<String>,
309    #[serde(skip_serializing_if = "Option::is_none")]
310    pub protocols: Option<ServiceProtocols>,
311    #[serde(skip_serializing_if = "Option::is_none")]
312    pub search_capabilities: Option<SearchCapabilities>,
313    #[serde(default, skip_serializing_if = "String::is_empty")]
314    pub status_url: String,
315    #[serde(default, skip_serializing_if = "String::is_empty")]
316    pub support_url: String,
317    #[serde(default, skip_serializing_if = "String::is_empty")]
318    pub website_url: String,
319    #[serde(flatten)]
320    pub additional: AdditionalMembers,
321}
322
323#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
324pub struct ResourceImage {
325    #[serde(default, skip_serializing_if = "String::is_empty")]
326    pub alt: String,
327    #[serde(default, skip_serializing_if = "is_zero")]
328    pub height: u32,
329    pub src: String,
330    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
331    pub media_type: Option<ResourceImageType>,
332    #[serde(default, skip_serializing_if = "is_zero")]
333    pub width: u32,
334}
335
336fn is_zero(value: &u32) -> bool {
337    *value == 0
338}
339
340#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
341pub struct Collection {
342    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
343    pub auth_expands: bool,
344    #[serde(default, skip_serializing_if = "String::is_empty")]
345    pub description: String,
346    #[serde(default, skip_serializing_if = "Vec::is_empty")]
347    pub detail_fields: Vec<String>,
348    pub id: String,
349    #[serde(default, skip_serializing_if = "Vec::is_empty")]
350    pub images: Vec<ResourceImage>,
351    #[serde(default, skip_serializing_if = "String::is_empty")]
352    pub language: String,
353    #[serde(default, skip_serializing_if = "Vec::is_empty")]
354    pub localizations: Vec<String>,
355    pub name: String,
356    #[serde(default, skip_serializing_if = "String::is_empty")]
357    pub odp_version: String,
358    #[serde(default, skip_serializing_if = "Vec::is_empty")]
359    pub parent_ids: Vec<String>,
360    #[serde(skip_serializing_if = "Option::is_none")]
361    pub search_capabilities: Option<SearchCapabilities>,
362    #[serde(default, skip_serializing_if = "String::is_empty")]
363    pub web_url: String,
364    #[serde(flatten)]
365    pub additional: AdditionalMembers,
366}
367
368#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
369pub struct SchemaReference {
370    pub url: String,
371}
372
373#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
374pub struct PricePreview {
375    #[serde(default, skip_serializing_if = "String::is_empty")]
376    pub amount: String,
377    #[serde(default, skip_serializing_if = "String::is_empty")]
378    pub currency: String,
379    #[serde(default, skip_serializing_if = "String::is_empty")]
380    pub maximum: String,
381    #[serde(default, skip_serializing_if = "String::is_empty")]
382    pub minimum: String,
383    #[serde(rename = "type")]
384    pub price_type: PriceType,
385    #[serde(default, skip_serializing_if = "String::is_empty")]
386    pub unit: String,
387    #[serde(flatten)]
388    pub additional: AdditionalMembers,
389}
390
391#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
392pub struct ActionRequest {
393    #[serde(default, skip_serializing_if = "String::is_empty")]
394    pub content_type: String,
395    #[serde(skip_serializing_if = "Option::is_none")]
396    pub schema: Option<SchemaReference>,
397}
398
399#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
400pub struct HttpActionTarget {
401    pub href: String,
402    pub method: String,
403    #[serde(skip_serializing_if = "Option::is_none")]
404    pub request: Option<ActionRequest>,
405    #[serde(default, skip_serializing_if = "Vec::is_empty")]
406    pub response_content_types: Vec<String>,
407}
408
409#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
410pub struct OpenApiActionTarget {
411    pub operation_id: String,
412    #[serde(default, skip_serializing_if = "String::is_empty")]
413    pub url: String,
414}
415
416#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
417pub struct Action {
418    pub authentication: AuthenticationRequirement,
419    #[serde(default, skip_serializing_if = "String::is_empty")]
420    pub description: String,
421    #[serde(skip_serializing_if = "Option::is_none")]
422    pub http: Option<HttpActionTarget>,
423    pub id: String,
424    #[serde(skip_serializing_if = "Option::is_none")]
425    pub openapi: Option<OpenApiActionTarget>,
426    pub rel: ActionRelation,
427}
428
429#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
430pub struct Offering {
431    #[serde(default, skip_serializing_if = "Vec::is_empty")]
432    pub actions: Vec<Action>,
433    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
434    pub auth_expands: bool,
435    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
436    pub attributes: BTreeMap<String, Value>,
437    #[serde(default, skip_serializing_if = "Vec::is_empty")]
438    pub collection_ids: Vec<String>,
439    #[serde(default, skip_serializing_if = "String::is_empty")]
440    pub description: String,
441    #[serde(default, skip_serializing_if = "Vec::is_empty")]
442    pub detail_fields: Vec<String>,
443    pub id: String,
444    #[serde(default, skip_serializing_if = "Vec::is_empty")]
445    pub images: Vec<ResourceImage>,
446    #[serde(default, skip_serializing_if = "String::is_empty")]
447    pub language: String,
448    #[serde(default, skip_serializing_if = "Vec::is_empty")]
449    pub localizations: Vec<String>,
450    pub name: String,
451    #[serde(default, skip_serializing_if = "String::is_empty")]
452    pub odp_version: String,
453    #[serde(skip_serializing_if = "Option::is_none")]
454    pub price: Option<PricePreview>,
455    #[serde(skip_serializing_if = "Option::is_none")]
456    pub schema: Option<SchemaReference>,
457    #[serde(default, skip_serializing_if = "String::is_empty")]
458    pub web_url: String,
459    #[serde(flatten)]
460    pub additional: AdditionalMembers,
461}
462
463#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
464pub struct InvalidParameter {
465    #[serde(rename = "in")]
466    pub location: String,
467    pub name: String,
468    pub reason: String,
469    #[serde(flatten)]
470    pub additional: AdditionalMembers,
471}
472
473#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
474pub struct ProblemDetails {
475    pub code: String,
476    #[serde(default, skip_serializing_if = "String::is_empty")]
477    pub detail: String,
478    #[serde(default, skip_serializing_if = "String::is_empty")]
479    pub instance: String,
480    #[serde(default, skip_serializing_if = "Vec::is_empty")]
481    pub invalid_params: Vec<InvalidParameter>,
482    pub status: u16,
483    pub title: String,
484    #[serde(rename = "type")]
485    pub problem_type: String,
486    #[serde(flatten)]
487    pub additional: AdditionalMembers,
488}
489
490#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
491pub struct ResourceIdentity {
492    pub id: String,
493    pub service: String,
494    #[serde(rename = "type")]
495    pub resource_type: ResourceType,
496}
497
498#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
499pub struct CollectionSearchRequest {
500    #[serde(default, skip_serializing_if = "is_zero_usize")]
501    pub limit: usize,
502    pub odp_version: String,
503    #[serde(default, skip_serializing_if = "Option::is_none")]
504    pub parent_id: Option<Option<String>>,
505    #[serde(default, skip_serializing_if = "String::is_empty")]
506    pub query: String,
507    #[serde(flatten)]
508    pub additional: AdditionalMembers,
509}
510
511#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
512pub struct OfferingSearchRequest {
513    #[serde(default, skip_serializing_if = "String::is_empty")]
514    pub collection_id: String,
515    #[serde(default, skip_serializing_if = "Vec::is_empty")]
516    pub filters: Vec<FilterExpression>,
517    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
518    pub include_descendants: bool,
519    #[serde(default, skip_serializing_if = "is_zero_usize")]
520    pub limit: usize,
521    pub odp_version: String,
522    #[serde(default, skip_serializing_if = "String::is_empty")]
523    pub query: String,
524    #[serde(default, skip_serializing_if = "Vec::is_empty")]
525    pub refinements: Vec<String>,
526    #[serde(default, skip_serializing_if = "String::is_empty")]
527    pub sort: String,
528    #[serde(flatten)]
529    pub additional: AdditionalMembers,
530}
531
532fn is_zero_usize(value: &usize) -> bool {
533    *value == 0
534}
535
536#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
537pub struct FilterExpression {
538    pub id: String,
539    pub operator: FilterOperator,
540    pub value: Value,
541    #[serde(flatten)]
542    pub additional: AdditionalMembers,
543}
544
545#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
546pub struct RefinementBucket {
547    pub count: u64,
548    #[serde(default, skip_serializing_if = "String::is_empty")]
549    pub count_relation: String,
550    pub value: Value,
551    #[serde(flatten)]
552    pub additional: AdditionalMembers,
553}
554
555#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
556pub struct RefinementGroup {
557    pub filter_id: String,
558    pub values: Vec<RefinementBucket>,
559    #[serde(flatten)]
560    pub additional: AdditionalMembers,
561}
562
563#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
564pub struct Page<T> {
565    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
566    pub auth_expands: bool,
567    pub items: Vec<T>,
568    #[serde(default, skip_serializing_if = "String::is_empty")]
569    pub next: String,
570    pub odp_version: String,
571    #[serde(flatten)]
572    pub additional: AdditionalMembers,
573}
574
575#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
576pub struct OfferingPage<T> {
577    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
578    pub auth_expands: bool,
579    pub items: Vec<T>,
580    #[serde(default, skip_serializing_if = "String::is_empty")]
581    pub next: String,
582    pub odp_version: String,
583    #[serde(default, skip_serializing_if = "Vec::is_empty")]
584    pub refinements: Vec<RefinementGroup>,
585    #[serde(flatten)]
586    pub additional: AdditionalMembers,
587}