Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | minor cleanups in prep for the "larger" JSON APIs. |
---|---|
Downloads: | Tarball | ZIP archive |
Timelines: | family | ancestors | descendants | both | json |
Files: | files | file ages | folders |
SHA1: |
87e20659c6e4f75fcc8f463d2b30ff6f |
User & Date: | stephan 2011-09-19 17:11:18.811 |
Context
2011-09-19
| ||
18:48 | Implemented /json/wiki/list (first draft, may change). Pulled in latest cson_sqlite3 additions to simplify the impl. ... (check-in: 5cc88946 user: stephan tags: json) | |
17:11 | minor cleanups in prep for the "larger" JSON APIs. ... (check-in: 87e20659 user: stephan tags: json) | |
2011-09-18
| ||
19:16 | terribly minor internal cleanups. ... (check-in: 507a4582 user: stephan tags: json) | |
Changes
Changes to src/json.c.
︙ | ︙ | |||
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | ** Code for the JSON API. ** ** For notes regarding the public JSON interface, please see: ** ** https://docs.google.com/document/d/1fXViveNhDbiXgCuE7QDXQOKeFzf2qNUkBEgiUvoqFN4/edit ** ** */ #include "config.h" #include "VERSION.h" #include "json.h" #include <assert.h> #include <time.h> #if INTERFACE #include "cson_amalgamation.h" #include "json_detail.h" /* workaround for apparent enum limitation in makeheaders */ #endif /* ** Holds keys used for various JSON API properties. */ static const struct FossilJsonKeys_{ char const * authToken; char const * commandPath; | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | ** Code for the JSON API. ** ** For notes regarding the public JSON interface, please see: ** ** https://docs.google.com/document/d/1fXViveNhDbiXgCuE7QDXQOKeFzf2qNUkBEgiUvoqFN4/edit ** ** ** Notes for hackers... ** ** Here's how command/page dispatching works: json_page_top() (in HTTP mode) or ** json_cmd_top() (in CLI mode) catch the "json" path/command. Those functions then ** dispatch to a JSON-mode-specific command/page handler with the type fossil_json_f(). ** See the API docs for that typedef (below) for the semantics of the callbacks. ** ** */ #include "config.h" #include "VERSION.h" #include "json.h" #include <assert.h> #include <time.h> #if INTERFACE #include "cson_amalgamation.h" #include "json_detail.h" /* workaround for apparent enum limitation in makeheaders */ #endif /* ** Signature for JSON page/command callbacks. By the time the callback ** is called, json_page_top() or json_cmd_top() will have set up the ** JSON-related environment. Implementations may generate a "result ** payload" of any JSON type by returning its value from this function ** (ownership is tranferred to the caller). On error they should set ** g.json.resultCode to one of the FossilJsonCodes values and return ** either their payload object or NULL. Note that NULL is a legal ** success value - it simply means the response will contain no ** payload. If g.json.resultCode is non-zero when this function ** returns then the top-level dispatcher will destroy any payload ** returned by this function and will output a JSON error response ** instead. ** ** All of the setup/response code is handled by the top dispatcher ** functions and the callbacks concern themselves only with generating ** the payload. ** ** It is imperitive that NO callback functions EVER output ANYTHING to ** stdout, as that will effectively corrupt any HTTP output. */ typedef cson_value * (*fossil_json_f)(); /* ** Holds keys used for various JSON API properties. */ static const struct FossilJsonKeys_{ char const * authToken; char const * commandPath; |
︙ | ︙ | |||
215 216 217 218 219 220 221 222 223 | cson_object_set( g.json.param.o, zKey, rc ); return rc; } } } return NULL; } /* | > > > > > > > > > > > > | | | | | | 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 | cson_object_set( g.json.param.o, zKey, rc ); return rc; } } } return NULL; } /* ** Returns the string form of a json_getenv() value, but ONLY ** If that value is-a String. Non-strings are not converted ** to strings for this purpose. Returned memory is owned by ** g.json or fossil.. */ static char const * json_getenv_cstr( char const * zKey ){ return cson_value_get_cstr( json_getenv(zKey) ); } /* ** Adds v to g.json.param.o using the given key. May cause any prior ** item with that key to be destroyed (depends on current reference ** count for that value). On success, transfers (or shares) ownership ** of v to (or with) g.json.param.o. On error ownership of v is not ** modified. */ int json_setenv( char const * zKey, cson_value * v ){ return cson_object_set( g.json.param.o, zKey, v ); } /* ** Guesses a RESPONSE Content-Type value based (primarily) on the |
︙ | ︙ | |||
320 321 322 323 324 325 326 | mode does not use any authentication, so we don't need to support it here. */ char const * zCookie = P(login_cookie_name()); if( zCookie && *zCookie ){ /* Transfer fossil's cookie to JSON for downstream convenience... */ cson_value * v = cson_value_new_string(zCookie, strlen(zCookie)); | | | > | 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 | mode does not use any authentication, so we don't need to support it here. */ char const * zCookie = P(login_cookie_name()); if( zCookie && *zCookie ){ /* Transfer fossil's cookie to JSON for downstream convenience... */ cson_value * v = cson_value_new_string(zCookie, strlen(zCookie)); if(0 == json_gc_add( FossilJsonKeys.authToken, v, 1 )){ g.json.authToken = v; } } } } return g.json.authToken; } /* |
︙ | ︙ | |||
464 465 466 467 468 469 470 | } do{/* set up JSON out formatting options. */ unsigned char indent = g.isCGI ? 0 : 1; cson_value const * indentV = json_getenv("indent"); if(indentV){ if(cson_value_is_string(indentV)){ | | | | < < | 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 | } do{/* set up JSON out formatting options. */ unsigned char indent = g.isCGI ? 0 : 1; cson_value const * indentV = json_getenv("indent"); if(indentV){ if(cson_value_is_string(indentV)){ int const n = atoi(cson_string_cstr(cson_value_get_string(indentV))); indent = (n>0) ? (unsigned char)n : 0; }else if(cson_value_is_number(indentV)){ cson_int_t const n = cson_value_get_integer(indentV); indent = (n>0) ? (unsigned char)n : 0; } } g.json.outOpt.indentation = indent; g.json.outOpt.addNewline = g.isCGI ? 0 : 1; }while(0); json_auth_token()/* will copy our auth token, if any, to fossil's core. */; |
︙ | ︙ | |||
551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 | /* Returns the C-string form of json_auth_token(), or NULL ** if json_auth_token() returns NULL. */ char const * json_auth_token_cstr(){ return cson_value_get_cstr( json_auth_token() ); } /* ** Holds name-to-function mappings for JSON page/command dispatching. ** */ typedef struct JsonPageDef{ /* ** The commmand/page's name (path, not including leading /json/). ** ** Reminder to self: we cannot use sub-paths with commands this way ** without additional string-splitting downstream. e.g. foo/bar. ** Alternately, we can create different JsonPageDef arrays for each ** subset. */ char const * name; /* ** Returns a payload object for the response. If it returns a ** non-NULL value, the caller owns it. To trigger an error this ** function should set g.json.resultCode to a value from the | > | > > | | 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 | /* Returns the C-string form of json_auth_token(), or NULL ** if json_auth_token() returns NULL. */ char const * json_auth_token_cstr(){ return cson_value_get_cstr( json_auth_token() ); } /* ** Holds name-to-function mappings for JSON page/command dispatching. ** */ typedef struct JsonPageDef{ /* ** The commmand/page's name (path, not including leading /json/). ** ** Reminder to self: we cannot use sub-paths with commands this way ** without additional string-splitting downstream. e.g. foo/bar. ** Alternately, we can create different JsonPageDef arrays for each ** subset. */ char const * name; /* ** Returns a payload object for the response. If it returns a ** non-NULL value, the caller owns it. To trigger an error this ** function should set g.json.resultCode to a value from the ** FossilJsonCodes enum. If it sets an error value and returns ** a payload, the payload will be destroyed (not sent with the ** response). */ fossil_json_f func; /* ** Which mode(s) of execution does func() support: ** ** <0 = CLI only, >0 = HTTP only, 0==both */ char runMode; } JsonPageDef; |
︙ | ︙ | |||
623 624 625 626 627 628 629 | case 3: modulo = 1000; break; default: break; } if( modulo ) code = code - (code % modulo); return code; } } | < < < < < < < | | > | > | < < < < | 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 | case 3: modulo = 1000; break; default: break; } if( modulo ) code = code - (code % modulo); return code; } } /* ** Creates a new Fossil/JSON response envelope skeleton. It is owned ** by the caller, who must eventually free it using cson_value_free(), ** or add it to a cson container to transfer ownership. Returns NULL ** on error. ** ** If payload is not NULL and resultCode is 0 then it is set as the ** "payload" property of the returned object. If resultCode is ** non-zero and payload is not NULL then this function calls ** cson_value_free(payload) and does not insert the payload into the ** response. In either case, onwership of payload is transfered to ** this function. ** ** pMsg is an optional message string property (resultText) of the ** response. If resultCode is non-0 and pMsg is NULL then ** json_err_str() is used to get the error string. The caller may ** provide his own or may use an empty string to suppress the ** resultText property. ** */ cson_value * json_create_response( int resultCode, cson_value * payload, char const * pMsg ){ cson_value * v = NULL; cson_value * tmp = NULL; cson_object * o = NULL; |
︙ | ︙ | |||
715 716 717 718 719 720 721 | if( pMsg && *pMsg ){ tmp = cson_value_new_string(pMsg,strlen(pMsg)); SET("resultText"); } tmp = json_getenv("requestId"); if( tmp ) cson_object_set( o, "requestId", tmp ); | | | | > > > > > < < < < < < < | > | | | > > | < < > > > > > > > > | 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 | if( pMsg && *pMsg ){ tmp = cson_value_new_string(pMsg,strlen(pMsg)); SET("resultText"); } tmp = json_getenv("requestId"); if( tmp ) cson_object_set( o, "requestId", tmp ); if(0){/* these are only intended for my own testing...*/ if(g.json.cmd.v){ tmp = g.json.cmd.v; SET("$commandPath"); } if(g.json.param.v){ tmp = g.json.param.v; SET("$params"); } if(0){/*Only for debuggering, add some info to the response.*/ tmp = cson_value_new_integer( g.json.cmd.offset ); cson_object_set( o, "cmd.offset", tmp ); cson_object_set( o, "isCGI", cson_value_new_bool( g.isCGI ) ); } } /* Only add the payload to SUCCESS responses. Else delete it. */ if( NULL != payload ){ if( resultCode ){ cson_value_free(payload); payload = NULL; }else{ tmp = payload; SET("payload"); } } #undef SET goto ok; cleanup: cson_value_free(v); v = NULL; ok: return v; } /* ** Outputs a JSON error response to either the cgi_xxx() family of ** buffers (in CGI/server mode) or stdout (in CLI mode). If rc is 0 ** then g.json.resultCode is used. If that is also 0 then the "Unknown ** Error" code is used. ** ** If g.isCGI then the generated JSON error response object replaces ** any currently buffered page output. Because the output goes via ** the cgi_xxx() family of functions, this function inherits any ** compression which fossil does for its output. ** ** If alsoOutput is true AND g.isCGI then cgi_reply() is called to ** flush the output (and headers). Generally only do this if you are ** about to call exit(). ** ** !g.isCGI then alsoOutput is ignored and all output is sent to ** stdout immediately. ** */ void json_err( int code, char const * msg, char alsoOutput ){ int rc = code ? code : (g.json.resultCode ? g.json.resultCode : FSL_JSON_E_UNKNOWN); cson_value * resp = NULL; rc = json_dumbdown_rc(rc); if( rc && !msg ){ msg = json_err_str(rc); } resp = json_create_response(rc, NULL, msg); if(!resp){ /* about the only error case here is out-of-memory. DO NOT call fossil_panic() here because that calls this function. */ fprintf(stderr, "%s: Fatal error: could not allocate " "response object.\n", fossil_nameofexe()); fossil_exit(1); } if( g.isCGI ){ Blob buf = empty_blob; cgi_reset_content(); cson_output_Blob( resp, &buf, &g.json.outOpt ); cgi_set_content(&buf); if( alsoOutput ){ cgi_reply(); |
︙ | ︙ | |||
819 820 821 822 823 824 825 | cson_object_set( jobj, "releaseVersionNumber", cson_value_new_integer(RELEASE_VERSION_NUMBER) ); cson_object_set( jobj, "resultCodeParanoiaLevel", cson_value_new_integer(g.json.errorDetailParanoia) ); return jval; } | < < < < < < < < < < | 863 864 865 866 867 868 869 870 871 872 873 874 875 876 | cson_object_set( jobj, "releaseVersionNumber", cson_value_new_integer(RELEASE_VERSION_NUMBER) ); cson_object_set( jobj, "resultCodeParanoiaLevel", cson_value_new_integer(g.json.errorDetailParanoia) ); return jval; } /* ** Implementation for /json/cap ** ** Returned object contains details about the "capabilities" of the ** current user (what he may/may not do). ** |
︙ | ︙ | |||
1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 | {"cap", json_page_cap, 0}, {"HAI",json_page_version,0}, {"login",json_page_login,1}, {"logout",json_page_logout,1}, {"stat",json_page_stat,0}, {"tag", json_page_nyi,0}, {"ticket", json_page_nyi,0}, {"user", json_page_nyi,0}, {"version",json_page_version,0}, {"wiki",json_page_wiki,0}, /* Last entry MUST have a NULL name. */ {NULL,NULL,0} }; /* ** WEBPAGE: json ** ** Pages under /json/... must be entered into JsonPageDefs. */ void json_page_top(void){ int rc = FSL_JSON_E_UNKNOWN_COMMAND; Blob buf = empty_blob; char const * cmd; cson_value * payload = NULL; cson_value * root = NULL; | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 | {"cap", json_page_cap, 0}, {"HAI",json_page_version,0}, {"login",json_page_login,1}, {"logout",json_page_logout,1}, {"stat",json_page_stat,0}, {"tag", json_page_nyi,0}, {"ticket", json_page_nyi,0}, {"timeline", json_page_nyi,0}, {"user", json_page_nyi,0}, {"version",json_page_version,0}, {"wiki",json_page_wiki,0}, /* Last entry MUST have a NULL name. */ {NULL,NULL,0} }; /* ** Mapping of /json/wiki/XXX commands/paths to callbacks. */ static const JsonPageDef JsonPageDefs_Wiki[] = { {"get", json_page_nyi, 0}, {"list", json_page_nyi, 0}, {"save", json_page_nyi, 1}, /* Last entry MUST have a NULL name. */ {NULL,NULL,0} }; /* ** Mapping of /json/ticket/XXX commands/paths to callbacks. */ static const JsonPageDef JsonPageDefs_Ticket[] = { {"get", json_page_nyi, 0}, {"list", json_page_nyi, 0}, {"save", json_page_nyi, 1}, {"create", json_page_nyi, 1}, /* Last entry MUST have a NULL name. */ {NULL,NULL,0} }; /* ** Mapping of /json/artifact/XXX commands/paths to callbacks. */ static const JsonPageDef JsonPageDefs_Artifact[] = { {"vinfo", json_page_nyi, 0}, {"finfo", json_page_nyi, 0}, /* Last entry MUST have a NULL name. */ {NULL,NULL,0} }; /* ** Mapping of /json/branch/XXX commands/paths to callbacks. */ static const JsonPageDef JsonPageDefs_Branch[] = { {"list", json_page_nyi, 0}, {"create", json_page_nyi, 1}, /* Last entry MUST have a NULL name. */ {NULL,NULL,0} }; /* ** Mapping of /json/tag/XXX commands/paths to callbacks. */ static const JsonPageDef JsonPageDefs_Tag[] = { {"list", json_page_nyi, 0}, {"create", json_page_nyi, 1}, /* Last entry MUST have a NULL name. */ {NULL,NULL,0} }; /* ** WEBPAGE: json ** ** Pages under /json/... must be entered into JsonPageDefs. ** This function dispatches them, and is the HTTP equivalent of ** json_cmd_top(). */ void json_page_top(void){ int rc = FSL_JSON_E_UNKNOWN_COMMAND; Blob buf = empty_blob; char const * cmd; cson_value * payload = NULL; cson_value * root = NULL; |
︙ | ︙ | |||
1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 | cson_output_Blob( root, &buf, NULL ); cson_value_free(root); cgi_set_content(&buf)/*takes ownership of the buf memory*/; } } /* ** COMMAND: json ** ** Usage: %fossil json SUBCOMMAND ** ** The commands include: ** ** cap | > > > | 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 | cson_output_Blob( root, &buf, NULL ); cson_value_free(root); cgi_set_content(&buf)/*takes ownership of the buf memory*/; } } /* ** This function dispatches json commands and is the CLI equivalent of ** json_page_top(). ** ** COMMAND: json ** ** Usage: %fossil json SUBCOMMAND ** ** The commands include: ** ** cap |
︙ | ︙ | |||
1272 1273 1274 1275 1276 1277 1278 | ** timeline ** wiki ** ... ** */ void json_cmd_top(void){ char const * cmd = NULL; | | | 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 | ** timeline ** wiki ** ... ** */ void json_cmd_top(void){ char const * cmd = NULL; int rc = FSL_JSON_E_UNKNOWN_COMMAND; cson_value * payload = NULL; JsonPageDef const * pageDef; memset( &g.perm, 0xff, sizeof(g.perm) ) /* In CLI mode fossil does not use permissions and they all default to false. We enable them here because (A) fossil doesn't use them in local mode but (B) having them set gives us one less |
︙ | ︙ |
Changes to src/main.c.
︙ | ︙ | |||
441 442 443 444 445 446 447 | int rc = 1; va_list ap; mainInFatalError = 1; va_start(ap, zFormat); z = vmprintf(zFormat, ap); va_end(ap); if( g.json.isJsonMode ){ | | | 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 | int rc = 1; va_list ap; mainInFatalError = 1; va_start(ap, zFormat); z = vmprintf(zFormat, ap); va_end(ap); if( g.json.isJsonMode ){ json_err( g.json.resultCode, z, 1 ); if( g.isCGI ){ rc = 0 /* avoid HTTP 500 */; } } else if( g.cgiOutput ){ g.cgiOutput = 0; cgi_printf("<p class=\"generalError\">%h</p>", z); |
︙ | ︙ |