#![allow(clippy::all, unused_imports, dead_code)]
use crate::{Actor, ActorBehavior, Message, Port};
use anyhow::{Error, Result};
use reflow_actor::{message::EncodableValue, ActorContext};
use reflow_actor_macro::actor;
use serde_json::{json, Value};
use std::collections::HashMap;
use std::time::Duration;
const BASE_URL: &str = "https://api.tomtom.com";
const ENV_KEY: &str = "TOMTOM_API_KEY";
fn apply_auth(
config: &reflow_actor::ActorConfig,
mut builder: reqwest::RequestBuilder,
) -> Result<reqwest::RequestBuilder> {
let credential = config
.get_config_or_env(ENV_KEY)
.ok_or_else(|| anyhow::anyhow!("Missing env var: {}", ENV_KEY))?;
builder = builder.query(&[("key", &credential)]);
Ok(builder)
}
#[actor(
TomtomSearchPlacesActor,
inports::<100>(query, key),
outports::<50>(response, error),
state(MemoryState)
)]
pub async fn tomtom_search_places(
context: ActorContext,
) -> Result<HashMap<String, Message>, Error> {
let inputs = context.get_payload();
let actor_config = context.get_config();
let mut endpoint = "/search/2/search/{query}.json".to_string();
if let Some(val) = inputs.get("query") {
endpoint = endpoint.replace("{{query}}", &super::message_to_str(val));
}
let url = format!("{}{}", BASE_URL.trim_end_matches('/'), endpoint);
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(30))
.build()?;
let mut builder = client.get(&url);
builder = builder.header("Content-Type", "application/json");
builder = apply_auth(actor_config, builder)?;
let mut query_pairs: Vec<(&str, String)> = Vec::new();
if let Some(val) = inputs.get("key") {
query_pairs.push(("key", super::message_to_str(val)));
}
if !query_pairs.is_empty() {
builder = builder.query(&query_pairs);
}
let mut output = HashMap::new();
match builder.send().await {
Ok(resp) => {
let status = resp.status().as_u16();
let headers: HashMap<String, String> = resp
.headers()
.iter()
.filter_map(|(k, v)| v.to_str().ok().map(|val| (k.to_string(), val.to_string())))
.collect();
let body_text = resp.text().await.unwrap_or_default();
let body_value: Value =
serde_json::from_str(&body_text).unwrap_or(Value::String(body_text));
output.insert(
"response".to_string(),
Message::object(EncodableValue::from(json!({
"status": status,
"headers": headers,
"body": body_value,
}))),
);
}
Err(e) => {
output.insert(
"error".to_string(),
Message::Error(format!("GET /search/2/search/{{query}}.json failed: {}", e).into()),
);
}
}
Ok(output)
}
#[actor(
TomtomReadRouteActor,
inports::<100>(locations, key),
outports::<50>(response, error),
state(MemoryState)
)]
pub async fn tomtom_read_route(context: ActorContext) -> Result<HashMap<String, Message>, Error> {
let inputs = context.get_payload();
let actor_config = context.get_config();
let mut endpoint = "/routing/1/calculateRoute/{locations}/json".to_string();
if let Some(val) = inputs.get("locations") {
endpoint = endpoint.replace("{{locations}}", &super::message_to_str(val));
}
let url = format!("{}{}", BASE_URL.trim_end_matches('/'), endpoint);
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(30))
.build()?;
let mut builder = client.get(&url);
builder = builder.header("Content-Type", "application/json");
builder = apply_auth(actor_config, builder)?;
let mut query_pairs: Vec<(&str, String)> = Vec::new();
if let Some(val) = inputs.get("key") {
query_pairs.push(("key", super::message_to_str(val)));
}
if !query_pairs.is_empty() {
builder = builder.query(&query_pairs);
}
let mut output = HashMap::new();
match builder.send().await {
Ok(resp) => {
let status = resp.status().as_u16();
let headers: HashMap<String, String> = resp
.headers()
.iter()
.filter_map(|(k, v)| v.to_str().ok().map(|val| (k.to_string(), val.to_string())))
.collect();
let body_text = resp.text().await.unwrap_or_default();
let body_value: Value =
serde_json::from_str(&body_text).unwrap_or(Value::String(body_text));
output.insert(
"response".to_string(),
Message::object(EncodableValue::from(json!({
"status": status,
"headers": headers,
"body": body_value,
}))),
);
}
Err(e) => {
output.insert(
"error".to_string(),
Message::Error(
format!(
"GET /routing/1/calculateRoute/{{locations}}/json failed: {}",
e
)
.into(),
),
);
}
}
Ok(output)
}
#[actor(
TomtomReadGeocodeActor,
inports::<100>(query, key, limit, language, countrySet, lat, lon, radius, topLeft, btmRight, view, storeResult),
outports::<50>(response, error),
state(MemoryState)
)]
pub async fn tomtom_read_geocode(context: ActorContext) -> Result<HashMap<String, Message>, Error> {
let inputs = context.get_payload();
let actor_config = context.get_config();
let mut endpoint = "/search/2/geocode/{query}.json".to_string();
if let Some(val) = inputs.get("query") {
endpoint = endpoint.replace("{{query}}", &super::message_to_str(val));
}
let url = format!("{}{}", BASE_URL.trim_end_matches('/'), endpoint);
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(30))
.build()?;
let mut builder = client.get(&url);
builder = builder.header("Content-Type", "application/json");
builder = apply_auth(actor_config, builder)?;
let mut query_pairs: Vec<(&str, String)> = Vec::new();
if let Some(val) = inputs.get("key") {
query_pairs.push(("key", super::message_to_str(val)));
}
if let Some(val) = inputs.get("limit") {
query_pairs.push(("limit", super::message_to_str(val)));
}
if let Some(val) = inputs.get("language") {
query_pairs.push(("language", super::message_to_str(val)));
}
if let Some(val) = inputs.get("countrySet") {
query_pairs.push(("countrySet", super::message_to_str(val)));
}
if let Some(val) = inputs.get("lat") {
query_pairs.push(("lat", super::message_to_str(val)));
}
if let Some(val) = inputs.get("lon") {
query_pairs.push(("lon", super::message_to_str(val)));
}
if let Some(val) = inputs.get("radius") {
query_pairs.push(("radius", super::message_to_str(val)));
}
if let Some(val) = inputs.get("topLeft") {
query_pairs.push(("topLeft", super::message_to_str(val)));
}
if let Some(val) = inputs.get("btmRight") {
query_pairs.push(("btmRight", super::message_to_str(val)));
}
if let Some(val) = inputs.get("view") {
query_pairs.push(("view", super::message_to_str(val)));
}
if let Some(val) = inputs.get("storeResult") {
query_pairs.push(("storeResult", super::message_to_str(val)));
}
if !query_pairs.is_empty() {
builder = builder.query(&query_pairs);
}
let mut output = HashMap::new();
match builder.send().await {
Ok(resp) => {
let status = resp.status().as_u16();
let headers: HashMap<String, String> = resp
.headers()
.iter()
.filter_map(|(k, v)| v.to_str().ok().map(|val| (k.to_string(), val.to_string())))
.collect();
let body_text = resp.text().await.unwrap_or_default();
let body_value: Value =
serde_json::from_str(&body_text).unwrap_or(Value::String(body_text));
output.insert(
"response".to_string(),
Message::object(EncodableValue::from(json!({
"status": status,
"headers": headers,
"body": body_value,
}))),
);
}
Err(e) => {
output.insert(
"error".to_string(),
Message::Error(
format!("GET /search/2/geocode/{{query}}.json failed: {}", e).into(),
),
);
}
}
Ok(output)
}
#[actor(
TomtomReadReverseGeocodeActor,
inports::<100>(position, key, language, returnSpeedLimit, heading, radius, number, returnRoadUse, callback, view, entityType),
outports::<50>(response, error),
state(MemoryState)
)]
pub async fn tomtom_read_reverse_geocode(
context: ActorContext,
) -> Result<HashMap<String, Message>, Error> {
let inputs = context.get_payload();
let actor_config = context.get_config();
let mut endpoint = "/search/2/reverseGeocode/{position}.json".to_string();
if let Some(val) = inputs.get("position") {
endpoint = endpoint.replace("{{position}}", &super::message_to_str(val));
}
let url = format!("{}{}", BASE_URL.trim_end_matches('/'), endpoint);
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(30))
.build()?;
let mut builder = client.get(&url);
builder = builder.header("Content-Type", "application/json");
builder = apply_auth(actor_config, builder)?;
let mut query_pairs: Vec<(&str, String)> = Vec::new();
if let Some(val) = inputs.get("key") {
query_pairs.push(("key", super::message_to_str(val)));
}
if let Some(val) = inputs.get("language") {
query_pairs.push(("language", super::message_to_str(val)));
}
if let Some(val) = inputs.get("returnSpeedLimit") {
query_pairs.push(("returnSpeedLimit", super::message_to_str(val)));
}
if let Some(val) = inputs.get("heading") {
query_pairs.push(("heading", super::message_to_str(val)));
}
if let Some(val) = inputs.get("radius") {
query_pairs.push(("radius", super::message_to_str(val)));
}
if let Some(val) = inputs.get("number") {
query_pairs.push(("number", super::message_to_str(val)));
}
if let Some(val) = inputs.get("returnRoadUse") {
query_pairs.push(("returnRoadUse", super::message_to_str(val)));
}
if let Some(val) = inputs.get("callback") {
query_pairs.push(("callback", super::message_to_str(val)));
}
if let Some(val) = inputs.get("view") {
query_pairs.push(("view", super::message_to_str(val)));
}
if let Some(val) = inputs.get("entityType") {
query_pairs.push(("entityType", super::message_to_str(val)));
}
if !query_pairs.is_empty() {
builder = builder.query(&query_pairs);
}
let mut output = HashMap::new();
match builder.send().await {
Ok(resp) => {
let status = resp.status().as_u16();
let headers: HashMap<String, String> = resp
.headers()
.iter()
.filter_map(|(k, v)| v.to_str().ok().map(|val| (k.to_string(), val.to_string())))
.collect();
let body_text = resp.text().await.unwrap_or_default();
let body_value: Value =
serde_json::from_str(&body_text).unwrap_or(Value::String(body_text));
output.insert(
"response".to_string(),
Message::object(EncodableValue::from(json!({
"status": status,
"headers": headers,
"body": body_value,
}))),
);
}
Err(e) => {
output.insert(
"error".to_string(),
Message::Error(
format!(
"GET /search/2/reverseGeocode/{{position}}.json failed: {}",
e
)
.into(),
),
);
}
}
Ok(output)
}
#[actor(
TomtomReadTrafficFlowActor,
inports::<100>(style, zoom, key, point, unit, thickness, openLr, onSide),
outports::<50>(response, error),
state(MemoryState)
)]
pub async fn tomtom_read_traffic_flow(
context: ActorContext,
) -> Result<HashMap<String, Message>, Error> {
let inputs = context.get_payload();
let actor_config = context.get_config();
let mut endpoint = "/traffic/services/5/flowSegmentData/{style}/{zoom}/json".to_string();
if let Some(val) = inputs.get("style") {
endpoint = endpoint.replace("{{style}}", &super::message_to_str(val));
}
if let Some(val) = inputs.get("zoom") {
endpoint = endpoint.replace("{{zoom}}", &super::message_to_str(val));
}
let url = format!("{}{}", BASE_URL.trim_end_matches('/'), endpoint);
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(30))
.build()?;
let mut builder = client.get(&url);
builder = builder.header("Content-Type", "application/json");
builder = apply_auth(actor_config, builder)?;
let mut query_pairs: Vec<(&str, String)> = Vec::new();
if let Some(val) = inputs.get("key") {
query_pairs.push(("key", super::message_to_str(val)));
}
if let Some(val) = inputs.get("point") {
query_pairs.push(("point", super::message_to_str(val)));
}
if let Some(val) = inputs.get("unit") {
query_pairs.push(("unit", super::message_to_str(val)));
}
if let Some(val) = inputs.get("thickness") {
query_pairs.push(("thickness", super::message_to_str(val)));
}
if let Some(val) = inputs.get("openLr") {
query_pairs.push(("openLr", super::message_to_str(val)));
}
if let Some(val) = inputs.get("onSide") {
query_pairs.push(("onSide", super::message_to_str(val)));
}
if !query_pairs.is_empty() {
builder = builder.query(&query_pairs);
}
let mut output = HashMap::new();
match builder.send().await {
Ok(resp) => {
let status = resp.status().as_u16();
let headers: HashMap<String, String> = resp
.headers()
.iter()
.filter_map(|(k, v)| v.to_str().ok().map(|val| (k.to_string(), val.to_string())))
.collect();
let body_text = resp.text().await.unwrap_or_default();
let body_value: Value =
serde_json::from_str(&body_text).unwrap_or(Value::String(body_text));
output.insert(
"response".to_string(),
Message::object(EncodableValue::from(json!({
"status": status,
"headers": headers,
"body": body_value,
}))),
);
}
Err(e) => {
output.insert("error".to_string(), Message::Error(format!("GET /traffic/services/5/flowSegmentData/{{style}}/{{zoom}}/json failed: {}", e).into()));
}
}
Ok(output)
}
#[actor(
TomtomReadTrafficIncidentsActor,
inports::<100>(key, bbox, fields, language, t, timeValidityFilter),
outports::<50>(response, error),
state(MemoryState)
)]
pub async fn tomtom_read_traffic_incidents(
context: ActorContext,
) -> Result<HashMap<String, Message>, Error> {
let inputs = context.get_payload();
let actor_config = context.get_config();
let endpoint = "/traffic/services/5/incidentDetails".to_string();
let url = format!("{}{}", BASE_URL.trim_end_matches('/'), endpoint);
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(30))
.build()?;
let mut builder = client.get(&url);
builder = builder.header("Content-Type", "application/json");
builder = apply_auth(actor_config, builder)?;
let mut query_pairs: Vec<(&str, String)> = Vec::new();
if let Some(val) = inputs.get("key") {
query_pairs.push(("key", super::message_to_str(val)));
}
if let Some(val) = inputs.get("bbox") {
query_pairs.push(("bbox", super::message_to_str(val)));
}
if let Some(val) = inputs.get("fields") {
query_pairs.push(("fields", super::message_to_str(val)));
}
if let Some(val) = inputs.get("language") {
query_pairs.push(("language", super::message_to_str(val)));
}
if let Some(val) = inputs.get("t") {
query_pairs.push(("t", super::message_to_str(val)));
}
if let Some(val) = inputs.get("timeValidityFilter") {
query_pairs.push(("timeValidityFilter", super::message_to_str(val)));
}
if !query_pairs.is_empty() {
builder = builder.query(&query_pairs);
}
let mut output = HashMap::new();
match builder.send().await {
Ok(resp) => {
let status = resp.status().as_u16();
let headers: HashMap<String, String> = resp
.headers()
.iter()
.filter_map(|(k, v)| v.to_str().ok().map(|val| (k.to_string(), val.to_string())))
.collect();
let body_text = resp.text().await.unwrap_or_default();
let body_value: Value =
serde_json::from_str(&body_text).unwrap_or(Value::String(body_text));
output.insert(
"response".to_string(),
Message::object(EncodableValue::from(json!({
"status": status,
"headers": headers,
"body": body_value,
}))),
);
}
Err(e) => {
output.insert(
"error".to_string(),
Message::Error(
format!("GET /traffic/services/5/incidentDetails failed: {}", e).into(),
),
);
}
}
Ok(output)
}
#[actor(
TomtomReadMapTileActor,
inports::<100>(layer, style, zoom, x, y, format, key, tileSize, view, language),
outports::<50>(response, error),
state(MemoryState)
)]
pub async fn tomtom_read_map_tile(
context: ActorContext,
) -> Result<HashMap<String, Message>, Error> {
let inputs = context.get_payload();
let actor_config = context.get_config();
let mut endpoint = "/map/1/tile/{layer}/{style}/{zoom}/{x}/{y}.{format}".to_string();
if let Some(val) = inputs.get("layer") {
endpoint = endpoint.replace("{{layer}}", &super::message_to_str(val));
}
if let Some(val) = inputs.get("style") {
endpoint = endpoint.replace("{{style}}", &super::message_to_str(val));
}
if let Some(val) = inputs.get("zoom") {
endpoint = endpoint.replace("{{zoom}}", &super::message_to_str(val));
}
if let Some(val) = inputs.get("x") {
endpoint = endpoint.replace("{{x}}", &super::message_to_str(val));
}
if let Some(val) = inputs.get("y") {
endpoint = endpoint.replace("{{y}}", &super::message_to_str(val));
}
if let Some(val) = inputs.get("format") {
endpoint = endpoint.replace("{{format}}", &super::message_to_str(val));
}
let url = format!("{}{}", BASE_URL.trim_end_matches('/'), endpoint);
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(30))
.build()?;
let mut builder = client.get(&url);
builder = builder.header("Content-Type", "application/json");
builder = apply_auth(actor_config, builder)?;
let mut query_pairs: Vec<(&str, String)> = Vec::new();
if let Some(val) = inputs.get("key") {
query_pairs.push(("key", super::message_to_str(val)));
}
if let Some(val) = inputs.get("tileSize") {
query_pairs.push(("tileSize", super::message_to_str(val)));
}
if let Some(val) = inputs.get("view") {
query_pairs.push(("view", super::message_to_str(val)));
}
if let Some(val) = inputs.get("language") {
query_pairs.push(("language", super::message_to_str(val)));
}
if !query_pairs.is_empty() {
builder = builder.query(&query_pairs);
}
let mut output = HashMap::new();
match builder.send().await {
Ok(resp) => {
let status = resp.status().as_u16();
let headers: HashMap<String, String> = resp
.headers()
.iter()
.filter_map(|(k, v)| v.to_str().ok().map(|val| (k.to_string(), val.to_string())))
.collect();
let body_text = resp.text().await.unwrap_or_default();
let body_value: Value =
serde_json::from_str(&body_text).unwrap_or(Value::String(body_text));
output.insert(
"response".to_string(),
Message::object(EncodableValue::from(json!({
"status": status,
"headers": headers,
"body": body_value,
}))),
);
}
Err(e) => {
output.insert("error".to_string(), Message::Error(format!("GET /map/1/tile/{{layer}}/{{style}}/{{zoom}}/{{x}}/{{y}}.{{format}} failed: {}", e).into()));
}
}
Ok(output)
}
#[actor(
TomtomSearchAddressesActor,
inports::<100>(key, countryCode, streetNumber, streetName, crossStreet, municipality, municipalitySubdivision, countrySubdivision, countrySecondarySubdivision, postalCode, limit, language),
outports::<50>(response, error),
state(MemoryState)
)]
pub async fn tomtom_search_addresses(
context: ActorContext,
) -> Result<HashMap<String, Message>, Error> {
let inputs = context.get_payload();
let actor_config = context.get_config();
let endpoint = "/search/2/structuredGeocode.json".to_string();
let url = format!("{}{}", BASE_URL.trim_end_matches('/'), endpoint);
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(30))
.build()?;
let mut builder = client.get(&url);
builder = builder.header("Content-Type", "application/json");
builder = apply_auth(actor_config, builder)?;
let mut query_pairs: Vec<(&str, String)> = Vec::new();
if let Some(val) = inputs.get("key") {
query_pairs.push(("key", super::message_to_str(val)));
}
if let Some(val) = inputs.get("countryCode") {
query_pairs.push(("countryCode", super::message_to_str(val)));
}
if let Some(val) = inputs.get("streetNumber") {
query_pairs.push(("streetNumber", super::message_to_str(val)));
}
if let Some(val) = inputs.get("streetName") {
query_pairs.push(("streetName", super::message_to_str(val)));
}
if let Some(val) = inputs.get("crossStreet") {
query_pairs.push(("crossStreet", super::message_to_str(val)));
}
if let Some(val) = inputs.get("municipality") {
query_pairs.push(("municipality", super::message_to_str(val)));
}
if let Some(val) = inputs.get("municipalitySubdivision") {
query_pairs.push(("municipalitySubdivision", super::message_to_str(val)));
}
if let Some(val) = inputs.get("countrySubdivision") {
query_pairs.push(("countrySubdivision", super::message_to_str(val)));
}
if let Some(val) = inputs.get("countrySecondarySubdivision") {
query_pairs.push(("countrySecondarySubdivision", super::message_to_str(val)));
}
if let Some(val) = inputs.get("postalCode") {
query_pairs.push(("postalCode", super::message_to_str(val)));
}
if let Some(val) = inputs.get("limit") {
query_pairs.push(("limit", super::message_to_str(val)));
}
if let Some(val) = inputs.get("language") {
query_pairs.push(("language", super::message_to_str(val)));
}
if !query_pairs.is_empty() {
builder = builder.query(&query_pairs);
}
let mut output = HashMap::new();
match builder.send().await {
Ok(resp) => {
let status = resp.status().as_u16();
let headers: HashMap<String, String> = resp
.headers()
.iter()
.filter_map(|(k, v)| v.to_str().ok().map(|val| (k.to_string(), val.to_string())))
.collect();
let body_text = resp.text().await.unwrap_or_default();
let body_value: Value =
serde_json::from_str(&body_text).unwrap_or(Value::String(body_text));
output.insert(
"response".to_string(),
Message::object(EncodableValue::from(json!({
"status": status,
"headers": headers,
"body": body_value,
}))),
);
}
Err(e) => {
output.insert(
"error".to_string(),
Message::Error(
format!("GET /search/2/structuredGeocode.json failed: {}", e).into(),
),
);
}
}
Ok(output)
}