Using an API with OpenAPI Spec

An API with an OpenAPI 3.0 spec can be used to generate an AIMMS Library. This AIMMS Library can subsequently be used to ease interfacing the corresponding service significantly.

This article illustrates interfacing a service with an OpenAPI 3.0 specification. As you know the following architecture is provided:

../../_images/client-server-openapi-lib1.png

The translation of AIMMS data to the format accepted by the server (arrow 2), and translating the response provided by the server into AIMMS data (arrow 3) are taken care of by a generated AIMMS library.

The purpose of this article is to illustrate:

  1. initialization of an OpenAPI generated AIMMS library,

  2. make a request to such a library (arrow 1), and

  3. handle a response from such a library (arrow 4).

However, to make this concrete, an example is used, which is presented here. This example illustrates a IP Locator. Note that to work with this example you will need an API key, from abstractapi.

../../_images/tokyo.png

Preparation

The preparations needed come prepackaged in the LibraryInitialization routine of the OpenAPI generated library openapi-geoAbstract:

 1! Read mapping files for this library.
 2block
 3    DirectoryOfLibraryProject("openapi-geoAbstract", libFolder);
 4onerror err do
 5    libFolder := "../libs/openapi-geoAbstract/";
 6    errh::MarkAsHandled(err);
 7endblock;
 8dex::ReadMappings(libFolder, "Generated/openapi-geoAbstract", 0);
 9
10! Read server initialization data (e.g. service URL, API key, OAuth credentials)
11apiInitFile := "../api-init/openapi-geoAbstract.txt";
12if FileExists(apiInitFile) then
13    read from file apiInitFile;
14endif ;

Selected remarks about this code:

  • Lines 1-8: The mapping files are in the subfolder ./Mappings/Generated/openapi-geoAbstract of the library folder.

  • Lines 10-14: Read in geolocation abstractapi config information, such as server name and API Key.

Example contents for the openapi-geoAbstract.txt are as follows:

1    geoAbstract::api::APIServer := "https://ipgeolocation.abstractapi.com" ;
2    sp_apiKey := "11111111111111111111111" ;

There is one server for the service ipgeolocation.abstractapi, namely ipgeolocation.abstractapi.com; so the server is specified in the initialization file. Of course, you can choose to enter your API key directly in this file.

Calling the API

Using the openapi-geoAbstract library, making a request is just as follows:

1    ! Starting current call.
2    geoAbstract::api::NewCallInstance(ep_callInstance);
3
4    ! Install hook, which will copy the data or handle the error
5    geoAbstract::api::get_v1_::UserResponseHook :=
6            'pr_abstractapi_ResponseHook' ;
7
8    ! Start the request.
9    geoAbstract::api::get_v1_::apiCall(ep_callInstance,sp_apiKey,sp_myIPAddress);

Remarks:

  1. Line 2: Each request is an object. The value of this mechanism will be illustrated in another how-to.

  2. Line 6: The library needs to know which procedure should handle the response (arrow 4).

  3. Line 9: Actually starting the request, using the api key and IP.

  4. In the AIMMS project, this procedure is called pr_GeolocateMakeRequest.

Handling the Response

Using the openapi_geoAbstract library, handling the response is just as follows:

 1    if geoAbstract::api::CallStatusCode(ep_callInstance) then
 2            switch geoAbstract::api::CallStatusCode(ep_callInstance) do
 3
 4
 5                    '200':
 6                            ! Success, copy data retrieved to application core data structures.
 7                            block ! Copy to data structures of scalar widget.
 8                                    sp_city         := geoAbstract::_inline_response_200::city(        ep_callInstance);
 9                                    sp_country      := geoAbstract::_inline_response_200::country(     ep_callInstance);
10                                    sp_countryCode  := geoAbstract::_inline_response_200::country_code(ep_callInstance);
11                                    p_lat           := geoAbstract::_inline_response_200::latitude(    ep_callInstance);
12                                    p_lon           := geoAbstract::_inline_response_200::longitude(   ep_callInstance);
13                                    sp_state        := geoAbstract::_inline_response_200::region_(       ep_callInstance);
14                                    sp_timezone     := geoAbstract::_inline_response_200::timezone_::name_(   ep_callInstance);
15                                    sp_zip          := geoAbstract::_inline_response_200::postal_code(         ep_callInstance);
16                            endblock ;
17                            geoAbstract::_inline_response_200::EmptyInstance(ep_callInstance);
18                            block ! Use data in core data structures for presentation purposes.
19                                    p_shownLocationLatitude(  ep_def_location ) := p_lat ;
20                                    p_shownLocationLongitude( ep_def_location ) := p_lon ;
21                            endblock ;
22
23                    '400','401','402','403','404','405','406','407','408','409','410','411','412','413','414','415','416','417','421','422','423','424','425','426','427','428','429','431','451',
24                    '500','501','502','503','504','505','506','507','508','510','511':
25                            raise error formatString("geoAbstract::Geolocate(%s) failed (instance: \'%e\', status: %e, error: %e): %s",
26                                    sp_myIPAddress, ep_callInstance,
27                                    geoAbstract::api::CallStatusCode(ep_callInstance),
28                                    geoAbstract::api::CallErrorCode(ep_callInstance),
29                                    fnc_httpErrorCodeToString( geoAbstract::api::CallStatusCode(ep_callInstance) ) );
30
31                    default:
32                            raise error formatString("geoAbstract::Geolocate(%s) failed (instance: \'%e\', status: %e, error: %e): %s",
33                                    sp_myIPAddress, ep_callInstance,
34                                    geoAbstract::api::CallStatusCode(ep_callInstance),
35                                    geoAbstract::api::CallErrorCode(ep_callInstance),
36                                    "unknown reason" );
37
38            endswitch ;
39    else
40            dex::client::GetErrorMessage( geoAbstract::api::CallErrorCode(ep_callInstance), _sp_curlMessage);
41            raise error formatString("geoAbstract::Geolocate(%s) failed (instance: \'%e\', status: %e, error: %e): %s",
42                                    sp_myIPAddress, ep_callInstance,
43                                    geoAbstract::api::CallStatusCode(ep_callInstance),
44                                    geoAbstract::api::CallErrorCode(ep_callInstance),
45                                    "curl error: " + _sp_curlMessage );
46    endif ;

Remarks:

  1. Lines 7-16: This is where the application logic comes in again. Here we copy the data from the openapi-geoAbstract library into the data structures of the application.

  2. Line 17: After the data is retrieved as needed, the data can be removed from the OpenAPI library.

  3. Lines 16, 17: Use the data now in the core of the app.

  4. Lines 23-29, 32-35, and 40-45: try to be nice to the end-user by sharing information about a failure. By sharing both what the response tries to handle (context information), and the cause of failure provided by the service, you will increase the chance that the user is able to handle the failure self, or find the proper point of contact directly.

  5. Line 29: The service provided by geolacation.abstractapi does not provide a schema for error messages. Instead, its OpenAPI spec documents how to handle status codes in case of failure. This is why a separate function is built to translate documented status code to explanations.

  6. At geolacation.abstractapi example, this procedure is called pr_ResponseHook.

Further information:

Note

The article is an adaptation of an earlier version whereby the server ipTwist.com was used. Regrettably, this server no longer exists. ipTwist provided a neat minimal interface ideal for creating an introductory example on openapi. Luckily, geolocation.abstractapi does the same, and is almost plug compatible with ipTwist.