Navigating Koyeb's API with Rust
- 3 minutes read - 583 wordsI wrote about Navigating Koyeb’s Golang SDK. That client is generated using the OpenAPI Generator project using Koyeb’s Swagger (now OpenAPI) REST API spec.
This post shows how to generate a Rust SDK using the Generator and provides a very basic example of using the SDK.
The Generator will create a Rust library project:
VERS="v7.2.0"
PACKAGE_NAME="koyeb-api-client-rs"
PACKAGE_VERS="1.0.0"
podman run \
--interactive --tty --rm \
--volume=${PWD}:/local \
docker.io/openapitools/openapi-generator-cli:${VERS} \
generate \
-g=rust \
-i=https://developer.koyeb.com/public.swagger.json \
-o=/local/${PACKAGE_NAME} \
--additional-properties=\
packageName=${PACKAGE_NAME},\
packageVersion=${PACKAGE_VERS}
This will create the project in ${PWD}/${PACKAGE_NAME}
including the documentation at:
${PWD}/${PACKAGE_NAME}/docs/doc/koyeb_api_client_rs/index.html
You can create a Rust binary project that uses this SDK. You’ll need to add references to the generated SDK; to reqwest
; and to tokio
:
cargo new --bin "${PACKAGE_NAME}-test"
cd "${PACKAGE_NAME}-test"
cargo add --path=${PWD}/${PACKAGE_NAME}
cargo add reqwest
cargo add --features="full" tokio
You’ll need a Koyeb API access token to authenticate your code to Koyeb’s API. Visit your Organization Settings and select API. Provide a Name perhaps koyeb-api-client-rs
and click “Create API Access Token”. Ensure you record the value and set the environment variable:
KOYEB_API_ACCESS_TOKEN="{value}"
NOTE Replace
{value}
with your access token
The main.rs
need several use
statements:
use std::env;
use koyeb_api_client_rs::apis::{
apps_api::{
list_apps,
ListAppsError::{
DefaultResponse, Status400, Status401, Status403, Status404, Status500, UnknownValue,
},
},
configuration::{ApiKey, Configuration},
deployments_api::list_deployments,
services_api::list_services,
Error,
};
We’ll grab the environment variable:
const API_ACCESS_TOKEN: &str = "KOYEB_API_ACCESS_TOKEN";
let api_access_token = env::var(API_ACCESS_TOKEN)
.map_err(|_|format!(
"Environment variable '{}' not set",
API_ACCESS_TOKEN,
))?;
And, in a different approach to the Go SDK, we’ll include the access token in the Configuration
struct:
let configuration = Configuration{
api_key: Some(ApiKey {
prefix: Some("Bearer".to_owned()),
key: api_access_token,
}),
..Default::default()
};
println!("{:?}", configuration);
This isn’t good practice but we’ll need some additional constants to pass as parameters to Koyebs list methods:
const LIMIT: Option<&str> = None;
const OFFSET: Option<&str> = None;
const NAME: Option<&str> = None;
Koyeb’s resource hierarchy is Organization -> App -> Service -> Deployment
Because we created an API access token in a specific Organization, we need not worry about the top level (Organization) resource in order to enumerate Apps.
NOTE It’s possible to enumerate Services and Deployments too by providing
None
as a value for the name.
match list_apps(&configuration, LIMIT, OFFSET, NAME).await {
Ok(list_apps_reply: ListAppsReply)=> {
if let Some(apps: Vec<AppListItem>) = list_apps_reply.apps {
for app: AppListItem in apps {
println!("{:?}", app);
let app_id: Option<&str> = app.id.as_deref();
let types: Option<Vec<String>> = Some(vec![
"WEB".to_owned(),
]);;
match list_services(
&configuration,
app_id,
LIMIT,
OFFSET,
NAME,
types,
).await { ... }
Err(e: Error<ListAppsError>) => {
...
}
}
There’s a detailed structure of error-handling types. Here’s a fully-expanded example for ListAppsError
:
Err(e) => {
println!("Error: {:?}", e);
match e {
Error::ResponseError(response_content) => {
println!("Error: {:?}|{:?}",
response_content.status,
response_content.status.canonical_reason(),
);
match response_content.entity {
Some(list_apps_error) => match list_apps_error {
Status400(error_with_fields) => {
println!(
"Error with Fields: {:?}",
error_with_fields,
);
},
Status401(error) |
Status403(error) |
Status404(error) |
Status500(error) => {
println!("Error: {:?}", error);
},
DefaultResponse(s)=>{
println!(
"Error: {:?}|{:?} [{:?}]",
s.code,s.message,
s.details,
);
},
UnknownValue(v)=>{
println!("Unknown Value: {:?}",v);
},
},
None => {},
}
}
_ => {
println!("Error: {:?}", e);
}
}
}
Overall, the OpenAPI Generator for Rust does the job of generating a working SDK for the API. As with many machine-generated tools, Google API Client Libraries are a familiar example to me, the benefit is that, once you know how to navigate the API in one language (e.g. Go), you can readily navigate it in another (e.g. Rust). The downside is that the generated code can be less idiomatic. I’m a Rust noob, so I’m unable to talk much to this latter point.