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}