📜 ⬆️ ⬇️

REST interface generator framework vibe.d

In this article, I will show you step by step how to use the rest interface generator built into the vibe.d framework. If you are interested in D and its features, then this article, I hope, will slightly clarify to beginners how to use D in practice and learn all of its power.

For this we need:
  1. Configure eclipse to work with vibe.d. for beginners
  2. Launch “Hello World” on vibe.d. for beginners
  3. Create a client for the OpenWeatherMap api.
  4. Create a backup server based on the OpenWeatherMap api

The documentation for vibe.d has an example of using a generator. However, this example, in my opinion, is too simple and does not reveal the mechanism of the generator.

Training


For the D language, there are many programming tools, including both individual full-fledged ides and plug-ins to existing ones. I chose the DDT plugin for eclipse for myself, because thanks to the cross-platform eclipse allows me not to be tied to one operating system.
Instruments
  • Download and install dmd .
  • Download and install dub . From now on, you can program in D. However, we need ide.
  • Download and install eclipse .
  • Install ddt.

At this point, the installation can be considered complete. Next, we will customize the project. By the way, the standard DDT settings are suitable for writing simple programs, like “hello world”, although for such purposes a regular console is perfect, not to mention the built-in support for D with the sublime text editor.

For something more complicated, dub is convenient to use as a collector. To do this, disable the standard ddt builders: Project-> Properties-> Builders, and add a dub there that is launched from the directory with your project and the build argument. I also advise you to check that in the menu Window-> Preferences-> DDT-> Compilers there is a dmd compiler. If not, indicate the path to it. Then ddt will have access to the standard phobos library.

')
Hello World on vibe.d
  • Initialize the project:
    dub init helloVibe 
  • Edit dub.json. Add there the following

     "libs-posix": ["dl"], "versions": ["VibeCustomMain"], "dependencies": { "vibe-d": "~master" } 


  • Then from the helloVibe directory
     dub run 
    Congratulations, your first program on D earned.
  • Create a project in eclipse. And as a library, add the source folder
     %APPDATA%/dub/packages/vibe-d-master/source 
    Now autocompletion is our assistant.
  • Run vibe.d. To do this, we need to start listening to the port and run a loop of events. This is done by functions
     listenHTTP(HTTPServerSettings,URLRouter) 
     runEventLoop() 
    respectively. You also need to create server settings and configure the router.
  • Create the settings as follows:

     auto settings = new HTTPServerSettings; settings.bindAddresses = ["127.0.0.1"]; settings.port = 80; 
  • Do not forget to connect the vibe:
     import vibe.d; 
  • The router will have one address available using the GET method.

     auto router = new URLRouter; router.get("/", &index); 


  • And the handler function:

     void index(HTTPServerRequest req, HTTPServerResponse res) { res.writeBody("Hello World!"); } 


    A handler can be either a function or a delegate, which is very convenient and allows you to use class methods as handlers. This feature is used in both the REST interface generator and the Web interface generator.
  • It remains to call all in the right order:

     import std.stdio; import vibe.d; void main() { writeln("Edit source/app.d to start your project."); void index(HTTPServerRequest req, HTTPServerResponse res) { res.writeBody("Hello World!"); } auto settings = new HTTPServerSettings; settings.bindAddresses = ["127.0.0.1"]; settings.port = 80; auto router = new URLRouter; router.get("/", &index); listenHTTP(settings, router); runEventLoop(); } 


  •  dub run 
    . Congratulations, your first server on vibe.d is ready.



Rest interface generator



Customer


Vibe.d is suitable for writing servers and clients.
To create a rest interface, you must declare the interface (which is logical). In this article we will consider the client for the OpenWeatherMap API . This API has only one weather method with the q parameter. So let's write

 interface OpenWeather { Weather getWeather(string q); } 


The response from the server will automatically be converted to a Weather structure. We will describe it according to the OpenWeatherMap API.
The structure into which the server response is converted
 struct Weather { @Label("City identification") long id; @Label("Data receiving time, unix time, GMT") ulong dt; @Label("City name") string name; WCoord coord; WSys sys; WMain main; WWind wind; WClouds clouds; //@optional() //WConditions[] weather; @optional() WRain rain; @optional() WSnow snow; } struct WCoord { @Label("City geo location, lat") double lat; @Label("City geo location, lon") double lon; } struct WSys { @Label("System parameter, do not use it") double message; @Label("Country (GB, JP etc.)") string country; @Label("Sunrise time, unix, UTC") ulong sunrise; @Label("Sunset time, unix, UTC") ulong sunset; } struct WMain { @Label("Temperature, Kelvin (subtract 273.15 to convert to Celsius)") double temp; @Label("Humidity, %") double humidity; @Label("Minimum temperature at the moment. This is deviation from current temp that is possible for large cities and megalopolises geographically expanded (use these parameter optionally)") double temp_min; @Label("Maximum temperature at the moment. This is deviation from current temp that is possible for large cities and megalopolises geographically expanded (use these parameter optionally)") double temp_max; @Label("Atmospheric pressure, hPa ") double pressure; @Label("Atmospheric pressure on the sea level, hPa") @optional() double sea_level; @Label("Atmospheric pressure on the ground level, hPa") @optional() double grnd_level; } struct WWind { @Label("Wind speed, mps") double speed; @Label("Wind direction, degrees (meteorological)") double deg; @Label("Wind gust, mps") @optional() double gust; } struct WClouds { @Label("Cloudiness, %") double all; } struct WConditions { @Label("Weather condition id") long id; @Label("Group of weather parameters (Rain, Snow, Extreme etc.)") Weather main; @Label("Weather condition within the group") string description; @Label("Weather icon id") string icon; } struct WRain { @Label("Precipitation volume for last 3 hours, mm") @optional() double _3h; } struct WSnow { @Label("Snow volume for last 3 hours, mm") @optional() double _3h; } 

A couple of words about the shoals of vibe.d.
First, deserialization of arrays from user types does not work well.
Secondly, due to the peculiarities of such a programming language, variables cannot begin with a digit (this is not only the case with d). I would write this joint to the OpenWeatherMap API account, not vibe.d, since, IMHO, this moment was poorly thought out by the API developers.

D allows us to centrally manage the response. The Label () attribute (here "doggy") contains a description of the variable, @optional () tells the generator that this field may not be contained in the server's response. Label () is user defined, it allows, for example, not to worry about the correspondence of the description and variable.
 Label 
is a structure:

 struct Label { string text; } 


All that remains is to create an interface client. This is done like this:

 auto client = new RestInterfaceClient!OpenWeather("http://api.openweathermap.org/data/2.5/"); 


Here through the "!" interface passed. This means that the RestInterfaceClient template class uses it in compiletime. In D, your program runs first in compiletime and then in runtime. In compiletime, the template is instantiated; in other words, a special class is generated, the object of which will be used during program execution.
If the method starts with get *, then the generator will register this handler using the GET method, similarly for post *, put *, delete *. You can not specify a method at all, then the generator itself will determine how to deal with it. For more information, see the documentation for vibe.d.
Rewrite the index function

 void index(HTTPServerRequest req, HTTPServerResponse res) { auto client = new RestInterfaceClient!OpenWeather("http://api.openweathermap.org/data/2.5/"); auto weather = client.getWeather("Moscow"); string result = "<html>"; foreach(each; respond(weather)) { auto writer = res.bodyWriter(); if (!each.label.empty) { result ~=format("<b>%s</b>:<br/>\"%s\" = %s<br/>", each.label, each.name, each.value); } else { result ~= format("<i>%s</i><br/>", each.name); } } result ~= "</html>"; res.writeBody(result); } 


Respond function

 import std.traits; import std.conv; struct A { string name; string value; string label; } string getLabel(alias ST, alias mem)() { foreach (attr; __traits(getAttributes, __traits(getMember,ST,mem))) { if (is(typeof(attr) == Label)) { return attr.text; } } return ""; } A[] respond(ST)(ST st) { A[] ret = new A[0]; foreach(mem; __traits(derivedMembers, ST)) { static if(is(typeof(__traits(getMember,ST,mem)) == struct)) { ret ~= A(mem,"",""); ret ~= respond!(typeof(__traits(getMember,ST,mem)))(__traits(getMember,st, mem)); } else { ret ~= A(mem, to!string(__traits(getMember,st, mem)), getLabel!(ST,mem) ); } } return ret; } 


The getLabel template restores the description to a variable (I wrote above about matching the variable and the description). The respond template collects variable names, their values ​​and descriptions into a convenient array of structures A.

After the project is assembled and launched, at 127.0.0.1 we will receive detailed information about the weather in Moscow.


Server

You can create a server by creating a class that releases the API, that is, the class OpenWeatherMapImpl.

 class OpenWeatherImpl:OpenWeather { Weather getWeather(string q) { auto client = new RestInterfaceClient!OpenWeather("http://api.openweathermap.org/data/2.5/"); return client.getWeather(q); } } 


This class duplicates the readings of the official OpenWeatherMap server. We register the interface
 router.registerRestInterface(new OpenWeatherImpl); 


Together
 import std.traits; import std.conv; struct Label { string text; } struct A { string name; string value; string label; } string getLabel(alias ST, alias mem)() { foreach (attr; __traits(getAttributes, __traits(getMember,ST,mem))) { if (is(typeof(attr) == Label)) { return attr.text; } } return ""; } A[] respond(ST)(ST st) { A[] ret = new A[0]; foreach(mem; __traits(derivedMembers, ST)) { static if(is(typeof(__traits(getMember,ST,mem)) == struct)) { ret ~= A(mem,"",""); ret ~= respond!(typeof(__traits(getMember,ST,mem)))(__traits(getMember,st, mem)); } else { ret ~= A(mem, to!string(__traits(getMember,st, mem)), getLabel!(ST,mem) ); } } return ret; } struct Weather { @Label("City identification") long id; @Label("Data receiving time, unix time, GMT") ulong dt; @Label("City name") string name; WCoord coord; WSys sys; WMain main; WWind wind; WClouds clouds; //@optional() //WConditions[] weather; @optional() WRain rain; @optional() WSnow snow; } struct WCoord { @Label("City geo location, lat") double lat; @Label("City geo location, lon") double lon; } struct WSys { @Label("System parameter, do not use it") double message; @Label("Country (GB, JP etc.)") string country; @Label("Sunrise time, unix, UTC") ulong sunrise; @Label("Sunset time, unix, UTC") ulong sunset; } struct WMain { @Label("Temperature, Kelvin (subtract 273.15 to convert to Celsius)") double temp; @Label("Humidity, %") double humidity; @Label("Minimum temperature at the moment. This is deviation from current temp that is possible for large cities and megalopolises geographically expanded (use these parameter optionally)") double temp_min; @Label("Maximum temperature at the moment. This is deviation from current temp that is possible for large cities and megalopolises geographically expanded (use these parameter optionally)") double temp_max; @Label("Atmospheric pressure, hPa ") double pressure; @Label("Atmospheric pressure on the sea level, hPa") @optional() double sea_level; @Label("Atmospheric pressure on the ground level, hPa") @optional() double grnd_level; } struct WWind { @Label("Wind speed, mps") double speed; @Label("Wind direction, degrees (meteorological)") double deg; @Label("Wind gust, mps") @optional() double gust; } struct WClouds { @Label("Cloudiness, %") double all; } struct WConditions { @Label("Weather condition id") long id; @Label("Group of weather parameters (Rain, Snow, Extreme etc.)") Weather main; @Label("Weather condition within the group") string description; @Label("Weather icon id") string icon; } struct WRain { @Label("Precipitation volume for last 3 hours, mm") @optional() double _3h; } struct WSnow { @Label("Snow volume for last 3 hours, mm") @optional() double _3h; } interface OpenWeather { Weather getWeather(string q); } class OpenWeatherImpl:OpenWeather { Weather getWeather(string q) { auto client = new RestInterfaceClient!OpenWeather("http://api.openweathermap.org/data/2.5/"); return client.getWeather(q); } } import std.stdio; import vibe.d; import std.string; void main() { writeln("Edit source/app.d to start your project."); void index(HTTPServerRequest req, HTTPServerResponse res) { auto client = new RestInterfaceClient!OpenWeather("http://api.openweathermap.org/data/2.5/"); auto weather = client.getWeather("Moscow"); string result = "<html>"; foreach(each; respond(weather)) { auto writer = res.bodyWriter(); if (!each.label.empty) { result ~=format("<b>%s</b>:<br/>\"%s\" = %s<br/>", each.label, each.name, each.value); } else { result ~= format("<i>%s</i><br/>", each.name); } } result ~= "</html>"; res.writeBody(result); } auto settings = new HTTPServerSettings; settings.bindAddresses = ["127.0.0.1"]; settings.port = 80; auto router = new URLRouter; router.get("/", &index); router.registerRestInterface(new OpenWeatherImpl); listenHTTP(settings, router); runEventLoop(); } 



At 127.0.0.1/weather?q= "Moscow" we will receive a json response to the weather in Moscow.

Examples from the article in a convenient format github.com/ntstv/viberest

Source: https://habr.com/ru/post/231581/


All Articles