I'm not saying Vala is the language of the future. All I'm saying is some great apps are being built in it.
ThiefMD1 recently added support for publishing to DEV.to. ThiefMD accomplishes this with Vala, and today I hope to show you how.
The DEV API is well documented. This post is more about Valavangelism (it's a great language). For API calls across the net, Vala has bindings for both cURL and libsoup2. libsoup integrates with Vala's Main Loop and has a nice API. libsoup + json-glib provides a fun and friendly way to interact with REST API's.
Prerequisites
I'm on Ubuntu 22.04. For dependencies I'll run:
sudo apt install build-essential valac libsoup2.4-dev libjson-glib-dev meson ninja-build
Abstracting the WebCall
For performing REST API calls, I created a class to wrap creating a Soup.Session and Soup.Message.
/**
* WebCall wraps a Soup.Session and handles managing Soup Messages
*/
private class WebCall {
private Soup.Session session;
private Soup.Message message;
private string url;
private string body;
private bool is_mime = false;
public string response_str;
public uint response_code;
/**
* Constructs a Soup.Session for the given endpoint
*
* @param endpoint The URL to connect to
* @param api The path the API rests at
*/
public class WebCall (string endpoint, string api) {
url = endpoint + api;
session = new Soup.Session ();
body = "";
}
/**
* Sets the data body for this request
*
* @param data The data to populate the body with
*/
public void set_body (string data) {
body = data;
}
/**
* Constructs a GET Soup.Message for this session
*/
public void set_get () {
message = new Soup.Message ("GET", url);
}
/**
* Constructs a DELETE Soup.Message for this session
*/
public void set_delete () {
message = new Soup.Message ("DELETE", url);
}
/**
* Constructs a POST Soup.Message for this session
*/
public void set_post () {
message = new Soup.Message ("POST", url);
}
/**
* Adds an HTTP header to the current Soup.Message
*
* @param key The HTTP Header Key to add
* @param value The value for the HTTP Header
*/
public void add_header (string key, string value) {
message.request_headers.append (key, value);
}
/**
* Performs the Session call for the current Message.
* Returns true if the call receives a success code.
* Returns false if the call fails or receives empty data.
*/
public bool perform_call () {
MainLoop loop = new MainLoop ();
bool success = false;
debug ("Calling %s", url);
// The DEV API requires a User Agent
add_header ("User-Agent", "Vala-to-Dev/0.1");
if (body != "") {
message.set_request ("application/json", Soup.MemoryUse.COPY, body.data);
}
session.queue_message (message, (sess, mess) => {
response_str = (string) mess.response_body.flatten ().data;
response_code = mess.status_code;
// Validate we have a string response and success HTTP code
if (response_str != null && response_str != "" &&
response_code >= 200 && response_code <= 250)
{
success = true;
}
loop.quit ();
});
loop.run ();
return success;
}
}
new WebCall
will construct our Soup.Session. The set_post
, set_get
and etc construct the proper Soup.Message type. perform_call
executes our REST API calls. MainLoop loop = new MainLoop ();
with session.queue_message
allows us to let the GUI to continue to run while web requests happen in the background.
With this class, we can easily talk to the DEV API. JSON-GLib will help us speak the DEV API language.
Speaking JSON from Vala
For a easy example, we're going to be checking The authenticated user. The sample output looks like:
{
"type_of": "user",
"id": 1234,
"username": "bob",
"name": "bob",
"summary": "Hello, world",
"twitter_username": "bob",
"github_username": "bob",
"website_url": null,
"location": "New York",
"joined_at": "Jan 1, 2017",
"profile_image": "https://res.cloudinary.com/...jpeg"
}
Using JSON-GLib, our class representing the object looks like this.
public class UserResponse : GLib.Object, Json.Serializable {
public string type_of { get; set; }
public int id { get; set; }
public string username { get; set; }
public string name { get; set; }
public string summary { get; set; }
public string twitter_username { get; set; }
public string github_username { get; set; }
public string website_url { get; set; }
public string location { get; set; }
public string joined_at { get; set; }
public string profile_image { get; set; }
}
Using Json.Serializable
makes it easy for us to go from a Vala Object to a JSON representation.
Making the API Call
Once we have our DEV API Key, we're ready to start sending messages. Our validation of the API Key can look something like:
/**
* Retrieves the username corresponding to the API-Key.
* Returns true if the API Key worked, false otherwise.
*
* @param username Receives the alias associated with the API-Key
* @param api_key The API KEY to use for authentication
*/
public bool get_authenticated_user (
out string username,
string api_key)
{
// Create a WebCall to handle this session
WebCall authentication = new WebCall ("https://dev.to", "/api/users/me");
// The Authentication API expects a GET request
authentication.set_get ();
// Add our API Key
authentication.add_header ("api-key", api_key);
// Perform our call
bool res = authentication.perform_call ();
if (!res) {
return false;
}
// Parse the reponse
Json.Parser parser = new Json.Parser ();
parser.load_from_data (authentication.response_str);
Json.Node data = parser.get_root ();
UserResponse response = Json.gobject_deserialize (
typeof (UserResponse),
data)
as UserResponse;
username = response.username;
return true;
}
Boom! Now we can validate our API key.
We can do something similar for createArticle, and we did! The code is at github.com/ThiefMD/forem-vala.
Thanks
Dear reader, I think your great. With Vala, I think you'll build great things! So go out and build, and don't hesitate to ask for help.
ThiefMD uses this library to allow exporting documents to DEV. With Spell Checking, Grammar Checking, and Write-Good ThiefMD will have you missing your teacher's red pen in no time. I mean, writing and publishing great DEV articles in no time.
Top comments (2)
Great article! There is one question I have though: How is the API key stored?
Thanks! For persisting the API Key, we use libsecret to store it encrypted in the OS Keyring.
The API Key is kept as a string in memory 😅. Vala doesn't appear to have securestring, and the DEV API doesn't do single session tokens after auth.