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