Skip to content

Commit 3921688

Browse files
authored
Merge pull request #524 from AmbireTech/api-routes-documentation
Api routes documentation
2 parents d5cfc8c + ab12fa7 commit 3921688

File tree

14 files changed

+484
-76
lines changed

14 files changed

+484
-76
lines changed

primitives/Cargo.toml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ postgres = ["bytes", "tokio-postgres", "deadpool-postgres"]
2121
test-util = []
2222

2323
[[example]]
24-
name = "channel_list_query"
25-
required-features = ["test-util"]
24+
name = "analytics_query"
25+
26+
[[example]]
27+
name = "analytics_response"
2628

2729
[[example]]
2830
name = "campaign_list_query"
@@ -32,6 +34,17 @@ required-features = ["test-util"]
3234
name = "campaign_list_response"
3335
required-features = ["test-util"]
3436

37+
[[example]]
38+
name = "channel_list_query"
39+
required-features = ["test-util"]
40+
41+
[[example]]
42+
name = "create_campaign"
43+
required-features = ["test-util"]
44+
45+
[[example]]
46+
name = "modify_campaign"
47+
3548
[dependencies]
3649
# (De)Serialization
3750
serde = { version = "1.0", features = ["derive"] }
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
use primitives::{
2+
analytics::{
3+
query::{AllowedKey, Time},
4+
AnalyticsQuery, Metric, OperatingSystem, Timeframe,
5+
},
6+
sentry::{DateHour, EventType},
7+
Address, CampaignId, ChainId, IPFS,
8+
};
9+
use std::str::FromStr;
10+
11+
fn main() {
12+
// Empty query - default values only
13+
{
14+
let empty_query = "";
15+
let query: AnalyticsQuery = serde_qs::from_str(empty_query).unwrap();
16+
17+
assert_eq!(100, query.limit);
18+
assert_eq!(EventType::Impression, query.event_type);
19+
assert!(matches!(query.metric, Metric::Count));
20+
assert!(matches!(query.time.timeframe, Timeframe::Day));
21+
}
22+
// Query with different metric/chain/eventType
23+
{
24+
let query_str = "limit=200&eventType=CLICK&metric=paid&timeframe=month";
25+
let query: AnalyticsQuery = serde_qs::from_str(query_str).unwrap();
26+
27+
assert_eq!(200, query.limit);
28+
assert_eq!(EventType::Click, query.event_type);
29+
assert!(matches!(query.metric, Metric::Paid));
30+
assert!(matches!(query.time.timeframe, Timeframe::Month));
31+
}
32+
33+
// Query with allowed keys for guest - country, slotType
34+
{
35+
let query_str = "country=Bulgaria&adSlotType=legacy_300x100";
36+
let query: AnalyticsQuery = serde_qs::from_str(query_str).unwrap();
37+
38+
assert_eq!(Some("Bulgaria".to_string()), query.country);
39+
assert_eq!(Some("legacy_300x100".to_string()), query.ad_slot_type);
40+
}
41+
42+
// Query with all possible fields (publisher/advertiser/admin)
43+
{
44+
let query_str = "limit=200&eventType=CLICK&metric=paid&segmentBy=country\
45+
&timeframe=week&start=2022-08-04+09:00:00.000000000+UTC\
46+
&campaignId=0x936da01f9abd4d9d80c702af85c822a8\
47+
&adUnit=QmcUVX7fvoLMM93uN2bD3wGTH8MXSxeL8hojYfL2Lhp7mR\
48+
&adSlot=Qmasg8FrbuSQpjFu3kRnZF9beg8rEBFrqgi1uXDRwCbX5f\
49+
&adSlotType=legacy_300x100\
50+
&advertiser=0xDd589B43793934EF6Ad266067A0d1D4896b0dff0\
51+
&publisher=0xE882ebF439207a70dDcCb39E13CA8506c9F45fD9\
52+
&hostname=localhost&country=Bulgaria&osName=Windows\
53+
&chains[0]=1&chains[1]=1337";
54+
let query: AnalyticsQuery = serde_qs::from_str(query_str).unwrap();
55+
56+
assert_eq!(query.limit, 200);
57+
assert_eq!(query.event_type, EventType::Click);
58+
assert!(matches!(query.metric, Metric::Paid));
59+
assert_eq!(query.segment_by, Some(AllowedKey::Country));
60+
assert_eq!(
61+
query.time,
62+
Time {
63+
timeframe: Timeframe::Week,
64+
start: DateHour::from_ymdh(2022, 8, 4, 9),
65+
end: None,
66+
}
67+
);
68+
assert_eq!(
69+
query.campaign_id,
70+
Some(
71+
CampaignId::from_str("0x936da01f9abd4d9d80c702af85c822a8")
72+
.expect("should be valid")
73+
)
74+
);
75+
assert_eq!(
76+
query.ad_unit,
77+
Some(
78+
IPFS::from_str("QmcUVX7fvoLMM93uN2bD3wGTH8MXSxeL8hojYfL2Lhp7mR")
79+
.expect("should be valid")
80+
)
81+
);
82+
assert_eq!(
83+
query.ad_slot,
84+
Some(
85+
IPFS::from_str("Qmasg8FrbuSQpjFu3kRnZF9beg8rEBFrqgi1uXDRwCbX5f")
86+
.expect("should be valid")
87+
)
88+
);
89+
assert_eq!(query.ad_slot_type, Some("legacy_300x100".to_string()));
90+
assert_eq!(
91+
query.advertiser,
92+
Some(
93+
Address::from_str("0xDd589B43793934EF6Ad266067A0d1D4896b0dff0")
94+
.expect("should be valid")
95+
)
96+
);
97+
assert_eq!(
98+
query.publisher,
99+
Some(
100+
Address::from_str("0xE882ebF439207a70dDcCb39E13CA8506c9F45fD9")
101+
.expect("should be valid")
102+
)
103+
);
104+
assert_eq!(query.hostname, Some("localhost".to_string()));
105+
assert_eq!(query.country, Some("Bulgaria".to_string()));
106+
assert_eq!(
107+
query.os_name,
108+
Some(OperatingSystem::Whitelisted("Windows".to_string()))
109+
);
110+
assert_eq!(query.chains, vec!(ChainId::new(1), ChainId::new(1337)));
111+
}
112+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
use primitives::sentry::AnalyticsResponse;
2+
use serde_json::{from_value, json};
3+
4+
fn main() {
5+
let json = json!({
6+
"analytics": [{
7+
"time": 1659592800,
8+
"value": "3",
9+
"segment": null
10+
},
11+
{
12+
"time": 1659592800,
13+
"value": "10000000000",
14+
"segment": null
15+
},
16+
{
17+
"time": 1659592800,
18+
"value": "100000000",
19+
"segment": "country"
20+
}]
21+
});
22+
23+
assert!(from_value::<AnalyticsResponse>(json).is_ok());
24+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
use primitives::{sentry::campaign_create::CreateCampaign, test_util::DUMMY_CAMPAIGN, CampaignId};
2+
use serde_json::json;
3+
use std::str::FromStr;
4+
5+
fn main() {
6+
// CreateCampaign in an HTTP request.
7+
// A CampaignId will be randomly generated for the newly created Campaign.
8+
{
9+
let create_campaign = CreateCampaign::from_campaign_erased(DUMMY_CAMPAIGN.clone(), None);
10+
11+
let _create_campaign_str =
12+
serde_json::to_string(&create_campaign).expect("should serialize");
13+
14+
let create_campaign_json = json!({
15+
"channel":{
16+
"leader":"0x80690751969B234697e9059e04ed72195c3507fa",
17+
"follower":"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7",
18+
"guardian":"0xe061E1EB461EaBE512759aa18A201B20Fe90631D",
19+
"token":"0x2BCaf6968aEC8A3b5126FBfAb5Fd419da6E8AD8E",
20+
"nonce":"987654321"
21+
},
22+
"creator":"0xaCBaDA2d5830d1875ae3D2de207A1363B316Df2F",
23+
"budget":"100000000000",
24+
"validators":[
25+
{
26+
"id":"0x80690751969B234697e9059e04ed72195c3507fa",
27+
"fee":"3000000",
28+
"url":"http://localhost:8005"
29+
},
30+
{
31+
"id":"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7",
32+
"fee":"2000000",
33+
"url":"http://localhost:8006"
34+
}
35+
],
36+
"title":"Dummy Campaign",
37+
"pricingBounds":{
38+
"CLICK":{"min":"0","max":"0"},
39+
"IMPRESSION":{"min":"1","max":"10"}
40+
},
41+
"eventSubmission":{"allow":[]},
42+
"targetingRules":[],
43+
"created":1612162800000_u64,
44+
"activeTo":4073414400000_u64
45+
});
46+
47+
let create_campaign_json =
48+
serde_json::to_string(&create_campaign_json).expect("should serialize");
49+
50+
let deserialized: CreateCampaign =
51+
serde_json::from_str(&create_campaign_json).expect("should deserialize");
52+
53+
assert_eq!(create_campaign, deserialized);
54+
}
55+
56+
// CreateCampaign with a provided ID
57+
{
58+
let mut create_campaign =
59+
CreateCampaign::from_campaign_erased(DUMMY_CAMPAIGN.clone(), None);
60+
create_campaign.id = Some(
61+
CampaignId::from_str("0x936da01f9abd4d9d80c702af85c822a8").expect("Should be valid id"),
62+
);
63+
64+
let create_campaign_json = json!({
65+
"id":"0x936da01f9abd4d9d80c702af85c822a8",
66+
"channel":{
67+
"leader":"0x80690751969B234697e9059e04ed72195c3507fa",
68+
"follower":"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7",
69+
"guardian":"0xe061E1EB461EaBE512759aa18A201B20Fe90631D",
70+
"token":"0x2BCaf6968aEC8A3b5126FBfAb5Fd419da6E8AD8E",
71+
"nonce":"987654321"
72+
},
73+
"creator":"0xaCBaDA2d5830d1875ae3D2de207A1363B316Df2F",
74+
"budget":"100000000000",
75+
"validators":[
76+
{
77+
"id":"0x80690751969B234697e9059e04ed72195c3507fa",
78+
"fee":"3000000",
79+
"url":"http://localhost:8005"
80+
},
81+
{
82+
"id":"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7",
83+
"fee":"2000000",
84+
"url":"http://localhost:8006"
85+
}
86+
],
87+
"title":"Dummy Campaign",
88+
"pricingBounds":{"CLICK":{"min":"0","max":"0"},"IMPRESSION":{"min":"1","max":"10"}},
89+
"eventSubmission":{"allow":[]},
90+
"targetingRules":[],
91+
"created":1612162800000_u64,
92+
"activeTo":4073414400000_u64
93+
});
94+
95+
let create_campaign_json =
96+
serde_json::to_string(&create_campaign_json).expect("should serialize");
97+
let deserialized: CreateCampaign =
98+
serde_json::from_str(&create_campaign_json).expect("should deserialize");
99+
100+
assert_eq!(create_campaign, deserialized);
101+
}
102+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use primitives::{sentry::campaign_modify::ModifyCampaign, unified_num::FromWhole, UnifiedNum};
2+
use serde_json::json;
3+
4+
fn main() {
5+
{
6+
let modify_campaign = ModifyCampaign {
7+
ad_units: None,
8+
budget: Some(UnifiedNum::from_whole(100)),
9+
validators: None,
10+
title: None,
11+
pricing_bounds: None,
12+
event_submission: None,
13+
targeting_rules: None,
14+
};
15+
16+
let modify_campaign_json = json!({
17+
"ad_units": null,
18+
"budget": "10000000000",
19+
"validators": null,
20+
"title": null,
21+
"pricing_bounds": null,
22+
"event_submission": null,
23+
"targeting_rules": null,
24+
});
25+
26+
let modify_campaign_json =
27+
serde_json::to_string(&modify_campaign_json).expect("should serialize");
28+
let deserialized: ModifyCampaign =
29+
serde_json::from_str(&modify_campaign_json).expect("should deserialize");
30+
31+
assert_eq!(modify_campaign, deserialized);
32+
}
33+
}

primitives/src/analytics.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,12 @@ mod postgres {
9494

9595
pub mod query;
9696

97+
// Query used for filtering analytics
98+
//
99+
/// # Examples:
100+
/// ```
101+
#[doc = include_str!("../examples/analytics_query.rs")]
102+
/// ```
97103
#[derive(Debug, Serialize, Deserialize, Clone)]
98104
#[serde(rename_all = "camelCase")]
99105
pub struct AnalyticsQuery {

primitives/src/campaign_validator.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ impl Validator for Campaign {
7272
.find_chain_of(self.channel.token)
7373
.ok_or(Validation::UnlistedAsset)?;
7474

75-
// Check if the campaign budget is above the minimum deposit configured
75+
// Check if the campaign budget is above the minimum campaign budget configured
7676
if self
7777
.budget
7878
.to_precision(chain_context.token.precision.get())

primitives/src/sentry.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,19 @@ pub struct FetchedAnalytics {
319319
pub segment: Option<String>,
320320
}
321321

322+
/// Response returned when getting Analytics which returns the [`FetchedAnalytics`].
323+
///
324+
/// # Examples
325+
///
326+
/// ```
327+
#[doc = include_str!("../examples/analytics_response.rs")]
328+
/// ```
329+
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
330+
#[serde(rename_all = "camelCase")]
331+
pub struct AnalyticsResponse {
332+
pub analytics: Vec<FetchedAnalytics>,
333+
}
334+
322335
/// The value of the requested analytics [`crate::analytics::Metric`].
323336
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
324337
#[serde(untagged)]
@@ -639,6 +652,13 @@ pub struct ValidationErrorResponse {
639652
pub validation: Vec<String>,
640653
}
641654

655+
/// Request body for posting new [`Event`]s to a [`Campaign`](crate::Campaign).
656+
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
657+
#[serde(rename_all = "camelCase")]
658+
pub struct InsertEventsRequest {
659+
pub events: Vec<Event>,
660+
}
661+
642662
pub mod channel_list {
643663
use crate::{ChainId, Channel, ValidatorId};
644664
use serde::{Deserialize, Serialize};
@@ -802,6 +822,12 @@ pub mod campaign_create {
802822

803823
/// All fields are present except the `CampaignId` which is randomly created
804824
/// This struct defines the Body of the request (in JSON)
825+
///
826+
/// # Examples
827+
///
828+
/// ```
829+
#[doc = include_str!("../examples/create_campaign.rs")]
830+
/// ```
805831
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
806832
#[serde(rename_all = "camelCase")]
807833
pub struct CreateCampaign {
@@ -889,7 +915,12 @@ pub mod campaign_modify {
889915
AdUnit, Campaign, EventSubmission, UnifiedNum,
890916
};
891917

892-
// All editable fields stored in one place, used for checking when a budget is changed
918+
/// All editable fields stored in one place, used for checking when a budget is changed
919+
///
920+
/// # Examples:
921+
/// ```
922+
#[doc = include_str!("../examples/modify_campaign.rs")]
923+
/// ```
893924
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
894925
pub struct ModifyCampaign {
895926
pub budget: Option<UnifiedNum>,

0 commit comments

Comments
 (0)