Fix tutorial to use Github API v3
[jansson.git] / doc / tutorial.rst
1 .. _tutorial:
2
3 ********
4 Tutorial
5 ********
6
7 .. highlight:: c
8
9 In this tutorial, we create a program that fetches the latest commits
10 of a repository in GitHub_ over the web. `GitHub API`_ uses JSON, so
11 the result can be parsed using Jansson.
12
13 To stick to the the scope of this tutorial, we will only cover the the
14 parts of the program related to handling JSON data. For the best user
15 experience, the full source code is available:
16 :download:`github_commits.c`. To compile it (on Unix-like systems with
17 gcc), use the following command::
18
19     gcc -o github_commits github_commits.c -ljansson -lcurl
20
21 libcurl_ is used to communicate over the web, so it is required to
22 compile the program.
23
24 The command line syntax is::
25
26     github_commits USER REPOSITORY
27
28 ``USER`` is a GitHub user ID and ``REPOSITORY`` is the repository
29 name. Please note that the GitHub API is rate limited, so if you run
30 the program too many times within a short period of time, the sever
31 starts to respond with an error.
32
33 .. _GitHub: https://github.com/
34 .. _GitHub API: http://developer.github.com/
35 .. _libcurl: http://curl.haxx.se/
36
37
38 .. _tutorial-github-commits-api:
39
40 The GitHub Repo Commits API
41 ===========================
42
43 The `GitHub Repo Commits API`_ is used by sending HTTP requests to
44 URLs like ``https://api.github.com/repos/USER/REPOSITORY/commits``,
45 where ``USER`` and ``REPOSITORY`` are the GitHub user ID and the name
46 of the repository whose commits are to be listed, respectively.
47
48 GitHub responds with a JSON array of the following form:
49
50 .. code-block:: none
51
52     [
53         {
54             "sha": "<the commit ID>",
55             "commit": {
56                 "message": "<the commit message>",
57                 <more fields, not important to this tutorial...>
58             },
59             <more fields...>
60         },
61         {
62             "sha": "<the commit ID>",
63             "commit": {
64                 "message": "<the commit message>",
65                 <more fields...>
66             },
67             <more fields...>
68         },
69         <more commits...>
70     ]
71
72 In our program, the HTTP request is sent using the following
73 function::
74
75     static char *request(const char *url);
76
77 It takes the URL as a parameter, preforms a HTTP GET request, and
78 returns a newly allocated string that contains the response body. If
79 the request fails, an error message is printed to stderr and the
80 return value is *NULL*. For full details, refer to :download:`the code
81 <github_commits.c>`, as the actual implementation is not important
82 here.
83
84 .. _GitHub Repo Commits API: http://developer.github.com/v3/repos/commits/
85
86 .. _tutorial-the-program:
87
88 The Program
89 ===========
90
91 First the includes::
92
93     #include <string.h>
94     #include <jansson.h>
95
96 Like all the programs using Jansson, we need to include
97 :file:`jansson.h`.
98
99 The following definitions are used to build the GitHub API request
100 URL::
101
102    #define URL_FORMAT   "https://api.github.com/repos/%s/%s/commits"
103    #define URL_SIZE     256
104
105 The following function is used when formatting the result to find the
106 first newline in the commit message::
107
108     /* Return the offset of the first newline in text or the length of
109        text if there's no newline */
110     static int newline_offset(const char *text)
111     {
112         const char *newline = strchr(text, '\n');
113         if(!newline)
114             return strlen(text);
115         else
116             return (int)(newline - text);
117     }
118
119 The main function follows. In the beginning, we first declare a bunch
120 of variables and check the command line parameters::
121
122     int main(int argc, char *argv[])
123     {
124         size_t i;
125         char *text;
126         char url[URL_SIZE];
127
128         json_t *root;
129         json_error_t error;
130
131         if(argc != 3)
132         {
133             fprintf(stderr, "usage: %s USER REPOSITORY\n\n", argv[0]);
134             fprintf(stderr, "List commits at USER's REPOSITORY.\n\n");
135             return 2;
136         }
137
138 Then we build the request URL using the user and repository names
139 given as command line parameters::
140
141     snprintf(url, URL_SIZE, URL_FORMAT, argv[1], argv[2]);
142
143 This uses the ``URL_SIZE`` and ``URL_FORMAT`` constants defined above.
144 Now we're ready to actually request the JSON data over the web::
145
146     text = request(url);
147     if(!text)
148         return 1;
149
150 If an error occurs, our function ``request`` prints the error and
151 returns *NULL*, so it's enough to just return 1 from the main
152 function.
153
154 Next we'll call :func:`json_loads()` to decode the JSON text we got
155 as a response::
156
157     root = json_loads(text, 0, &error);
158     free(text);
159
160     if(!root)
161     {
162         fprintf(stderr, "error: on line %d: %s\n", error.line, error.text);
163         return 1;
164     }
165
166 We don't need the JSON text anymore, so we can free the ``text``
167 variable right after decoding it. If :func:`json_loads()` fails, it
168 returns *NULL* and sets error information to the :type:`json_error_t`
169 structure given as the second parameter. In this case, our program
170 prints the error information out and returns 1 from the main function.
171
172 Now we're ready to extract the data out of the decoded JSON response.
173 The structure of the response JSON was explained in section
174 :ref:`tutorial-github-commits-api`.
175
176 We check that the returned value really is an array::
177
178     if(!json_is_array(root))
179     {
180         fprintf(stderr, "error: root is not an array\n");
181         return 1;
182     }
183
184 Then we proceed to loop over all the commits in the array::
185
186     for(i = 0; i < json_array_size(root); i++)
187     {
188         json_t *data, *sha, *commit, *message;
189         const char *message_text;
190
191         data = json_array_get(root, i);
192         if(!json_is_object(data))
193         {
194             fprintf(stderr, "error: commit data %d is not an object\n", i + 1);
195             return 1;
196         }
197     ...
198
199 The function :func:`json_array_size()` returns the size of a JSON
200 array. First, we again declare some variables and then extract the
201 i'th element of the ``root`` array using :func:`json_array_get()`.
202 We also check that the resulting value is a JSON object.
203
204 Next we'll extract the commit ID (a hexadecimal SHA-1 sum),
205 intermediate commit info object, and the commit message from that
206 object. We also do proper type checks::
207
208         sha = json_object_get(commit, "sha");
209         if(!json_is_string(sha))
210         {
211             fprintf(stderr, "error: commit %d: sha is not a string\n", i + 1);
212             return 1;
213         }
214
215         commit = json_object_get(data, "commit");
216         if(!json_is_object(commit))
217         {
218             fprintf(stderr, "error: commit %d: commit is not an object\n", i + 1);
219             return 1;
220         }
221
222         message = json_object_get(commit, "message");
223         if(!json_is_string(message))
224         {
225             fprintf(stderr, "error: commit %d: message is not a string\n", i + 1);
226             return 1;
227         }
228     ...
229
230 And finally, we'll print the first 8 characters of the commit ID and
231 the first line of the commit message. A C-style string is extracted
232 from a JSON string using :func:`json_string_value()`::
233
234         message_text = json_string_value(message);
235         printf("%.8s %.*s\n",
236                json_string_value(id),
237                newline_offset(message_text),
238                message_text);
239     }
240
241 After sending the HTTP request, we decoded the JSON text using
242 :func:`json_loads()`, remember? It returns a *new reference* to the
243 JSON value it decodes. When we're finished with the value, we'll need
244 to decrease the reference count using :func:`json_decref()`. This way
245 Jansson can release the resources::
246
247     json_decref(root);
248     return 0;
249
250 For a detailed explanation of reference counting in Jansson, see
251 :ref:`apiref-reference-count` in :ref:`apiref`.
252
253 The program's ready, let's test it and view the latest commits in
254 Jansson's repository::
255
256     $ ./github_commits akheron jansson
257     1581f26a Merge branch '2.3'
258     aabfd493 load: Change buffer_pos to be a size_t
259     bd72efbd load: Avoid unexpected behaviour in macro expansion
260     e8fd3e30 Document and tweak json_load_callback()
261     873eddaf Merge pull request #60 from rogerz/contrib
262     bd2c0c73 Ignore the binary test_load_callback
263     17a51a4b Merge branch '2.3'
264     09c39adc Add json_load_callback to the list of exported symbols
265     cbb80baf Merge pull request #57 from rogerz/contrib
266     040bd7b0 Add json_load_callback()
267     2637faa4 Make test stripping locale independent
268     <...>
269
270
271 Conclusion
272 ==========
273
274 In this tutorial, we implemented a program that fetches the latest
275 commits of a GitHub repository using the GitHub Repo Commits API.
276 Jansson was used to decode the JSON response and to extract the commit
277 data.
278
279 This tutorial only covered a small part of Jansson. For example, we
280 did not create or manipulate JSON values at all. Proceed to
281 :ref:`apiref` to explore all features of Jansson.