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}