Fossil SCM

minor cleanups in prep for the "larger" JSON APIs.

stephan 2011-09-19 17:11 UTC json
Commit 87e20659c6e4f75fcc8f463d2b30ff6fa80399fa
2 files changed +151 -57 +1 -1
+151 -57
--- src/json.c
+++ src/json.c
@@ -20,10 +20,18 @@
2020
** For notes regarding the public JSON interface, please see:
2121
**
2222
** https://docs.google.com/document/d/1fXViveNhDbiXgCuE7QDXQOKeFzf2qNUkBEgiUvoqFN4/edit
2323
**
2424
**
25
+** Notes for hackers...
26
+**
27
+** Here's how command/page dispatching works: json_page_top() (in HTTP mode) or
28
+** json_cmd_top() (in CLI mode) catch the "json" path/command. Those functions then
29
+** dispatch to a JSON-mode-specific command/page handler with the type fossil_json_f().
30
+** See the API docs for that typedef (below) for the semantics of the callbacks.
31
+**
32
+**
2533
*/
2634
#include "config.h"
2735
#include "VERSION.h"
2836
#include "json.h"
2937
#include <assert.h>
@@ -31,10 +39,34 @@
3139
3240
#if INTERFACE
3341
#include "cson_amalgamation.h"
3442
#include "json_detail.h" /* workaround for apparent enum limitation in makeheaders */
3543
#endif
44
+
45
+/*
46
+** Signature for JSON page/command callbacks. By the time the callback
47
+** is called, json_page_top() or json_cmd_top() will have set up the
48
+** JSON-related environment. Implementations may generate a "result
49
+** payload" of any JSON type by returning its value from this function
50
+** (ownership is tranferred to the caller). On error they should set
51
+** g.json.resultCode to one of the FossilJsonCodes values and return
52
+** either their payload object or NULL. Note that NULL is a legal
53
+** success value - it simply means the response will contain no
54
+** payload. If g.json.resultCode is non-zero when this function
55
+** returns then the top-level dispatcher will destroy any payload
56
+** returned by this function and will output a JSON error response
57
+** instead.
58
+**
59
+** All of the setup/response code is handled by the top dispatcher
60
+** functions and the callbacks concern themselves only with generating
61
+** the payload.
62
+**
63
+** It is imperitive that NO callback functions EVER output ANYTHING to
64
+** stdout, as that will effectively corrupt any HTTP output.
65
+*/
66
+typedef cson_value * (*fossil_json_f)();
67
+
3668
3769
/*
3870
** Holds keys used for various JSON API properties.
3971
*/
4072
static const struct FossilJsonKeys_{
@@ -217,17 +249,29 @@
217249
}
218250
}
219251
}
220252
return NULL;
221253
}
254
+
255
+
256
+/*
257
+** Returns the string form of a json_getenv() value, but ONLY
258
+** If that value is-a String. Non-strings are not converted
259
+** to strings for this purpose. Returned memory is owned by
260
+** g.json or fossil..
261
+*/
262
+static char const * json_getenv_cstr( char const * zKey ){
263
+ return cson_value_get_cstr( json_getenv(zKey) );
264
+}
265
+
222266
223267
/*
224
-** Adds v to g.json.param.o using the given key. May cause
225
-** any prior item with that key to be destroyed (depends on
226
-** current reference count for that value).
227
-** On succes, transfers ownership of v to g.json.param.o.
228
-** On error ownership of v is not modified.
268
+** Adds v to g.json.param.o using the given key. May cause any prior
269
+** item with that key to be destroyed (depends on current reference
270
+** count for that value). On success, transfers (or shares) ownership
271
+** of v to (or with) g.json.param.o. On error ownership of v is not
272
+** modified.
229273
*/
230274
int json_setenv( char const * zKey, cson_value * v ){
231275
return cson_object_set( g.json.param.o, zKey, v );
232276
}
233277
@@ -322,12 +366,13 @@
322366
*/
323367
char const * zCookie = P(login_cookie_name());
324368
if( zCookie && *zCookie ){
325369
/* Transfer fossil's cookie to JSON for downstream convenience... */
326370
cson_value * v = cson_value_new_string(zCookie, strlen(zCookie));
327
- json_setenv( FossilJsonKeys.authToken, v );
328
- g.json.authToken = v;
371
+ if(0 == json_gc_add( FossilJsonKeys.authToken, v, 1 )){
372
+ g.json.authToken = v;
373
+ }
329374
}
330375
}
331376
}
332377
return g.json.authToken;
333378
}
@@ -466,19 +511,17 @@
466511
do{/* set up JSON out formatting options. */
467512
unsigned char indent = g.isCGI ? 0 : 1;
468513
cson_value const * indentV = json_getenv("indent");
469514
if(indentV){
470515
if(cson_value_is_string(indentV)){
471
- int n = atoi(cson_string_cstr(cson_value_get_string(indentV)));
516
+ int const n = atoi(cson_string_cstr(cson_value_get_string(indentV)));
472517
indent = (n>0)
473518
? (unsigned char)n
474519
: 0;
475520
}else if(cson_value_is_number(indentV)){
476
- double n = cson_value_get_integer(indentV);
477
- indent = (n>0)
478
- ? (unsigned char)n
479
- : 0;
521
+ cson_int_t const n = cson_value_get_integer(indentV);
522
+ indent = (n>0) ? (unsigned char)n : 0;
480523
}
481524
}
482525
g.json.outOpt.indentation = indent;
483526
g.json.outOpt.addNewline = g.isCGI ? 0 : 1;
484527
}while(0);
@@ -553,10 +596,11 @@
553596
** if json_auth_token() returns NULL.
554597
*/
555598
char const * json_auth_token_cstr(){
556599
return cson_value_get_cstr( json_auth_token() );
557600
}
601
+
558602
559603
/*
560604
** Holds name-to-function mappings for JSON page/command dispatching.
561605
**
562606
*/
@@ -572,13 +616,15 @@
572616
char const * name;
573617
/*
574618
** Returns a payload object for the response. If it returns a
575619
** non-NULL value, the caller owns it. To trigger an error this
576620
** function should set g.json.resultCode to a value from the
577
- ** FossilJsonCodes enum.
621
+ ** FossilJsonCodes enum. If it sets an error value and returns
622
+ ** a payload, the payload will be destroyed (not sent with the
623
+ ** response).
578624
*/
579
- cson_value * (*func)();
625
+ fossil_json_f func;
580626
/*
581627
** Which mode(s) of execution does func() support:
582628
**
583629
** <0 = CLI only, >0 = HTTP only, 0==both
584630
*/
@@ -625,39 +671,30 @@
625671
}
626672
if( modulo ) code = code - (code % modulo);
627673
return code;
628674
}
629675
}
630
-
631
-#if 0
632
-static unsigned int json_timestamp(){
633
-
634
-}
635
-#endif
636
-
637676
638677
/*
639678
** Creates a new Fossil/JSON response envelope skeleton. It is owned
640679
** by the caller, who must eventually free it using cson_value_free(),
641680
** or add it to a cson container to transfer ownership. Returns NULL
642681
** on error.
643682
**
644683
** If payload is not NULL and resultCode is 0 then it is set as the
645
-** "payload" property of the returned object. If resultCode is non-0
646
-** then this function will destroy payload if it is not NULL. i.e.
647
-** onwership of payload is transfered to this function.
684
+** "payload" property of the returned object. If resultCode is
685
+** non-zero and payload is not NULL then this function calls
686
+** cson_value_free(payload) and does not insert the payload into the
687
+** response. In either case, onwership of payload is transfered to
688
+** this function.
648689
**
649
-** pMsg is an optional message string (resultText) property of the
690
+** pMsg is an optional message string property (resultText) of the
650691
** response. If resultCode is non-0 and pMsg is NULL then
651692
** json_err_str() is used to get the error string. The caller may
652693
** provide his own or may use an empty string to suppress the
653694
** resultText property.
654695
**
655
-** If resultCode is non-zero and payload is not NULL then this
656
-** function calls cson_value_free(payload) and does not insert the
657
-** payload into the response.
658
-**
659696
*/
660697
cson_value * json_create_response( int resultCode,
661698
cson_value * payload,
662699
char const * pMsg ){
663700
cson_value * v = NULL;
@@ -717,19 +754,24 @@
717754
SET("resultText");
718755
}
719756
tmp = json_getenv("requestId");
720757
if( tmp ) cson_object_set( o, "requestId", tmp );
721758
722
- if(0){
723
- if(g.json.cmd.v){/* this is only intended for my own testing...*/
759
+ if(0){/* these are only intended for my own testing...*/
760
+ if(g.json.cmd.v){
724761
tmp = g.json.cmd.v;
725762
SET("$commandPath");
726763
}
727
- if(g.json.param.v){/* this is only intended for my own testing...*/
764
+ if(g.json.param.v){
728765
tmp = g.json.param.v;
729766
SET("$params");
730767
}
768
+ if(0){/*Only for debuggering, add some info to the response.*/
769
+ tmp = cson_value_new_integer( g.json.cmd.offset );
770
+ cson_object_set( o, "cmd.offset", tmp );
771
+ cson_object_set( o, "isCGI", cson_value_new_bool( g.isCGI ) );
772
+ }
731773
}
732774
733775
/* Only add the payload to SUCCESS responses. Else delete it. */
734776
if( NULL != payload ){
735777
if( resultCode ){
@@ -740,42 +782,36 @@
740782
SET("payload");
741783
}
742784
}
743785
744786
#undef SET
745
-
746
- if(0){/*Only for debuggering, add some info to the response.*/
747
- tmp = cson_value_new_integer( g.json.cmd.offset );
748
- cson_object_set( o, "cmd.offset", tmp );
749
- cson_object_set( o, "isCGI", cson_value_new_bool( g.isCGI ) );
750
- }
751
-
752787
goto ok;
753788
cleanup:
754789
cson_value_free(v);
755790
v = NULL;
756791
ok:
757792
return v;
758793
}
759794
760795
/*
761
-** Outputs a JSON error response to g.httpOut. If rc is 0 then
762
-** g.json.resultCode is used. If that is also 0 then the "Unknown
796
+** Outputs a JSON error response to either the cgi_xxx() family of
797
+** buffers (in CGI/server mode) or stdout (in CLI mode). If rc is 0
798
+** then g.json.resultCode is used. If that is also 0 then the "Unknown
763799
** Error" code is used.
764800
**
765
-** If g.isCGI then the generated error object replaces any currently
766
-** buffered page output.
801
+** If g.isCGI then the generated JSON error response object replaces
802
+** any currently buffered page output. Because the output goes via
803
+** the cgi_xxx() family of functions, this function inherits any
804
+** compression which fossil does for its output.
767805
**
768
-** If alsoOutput is true AND g.isCGI then the cgi_reply() is called to
806
+** If alsoOutput is true AND g.isCGI then cgi_reply() is called to
769807
** flush the output (and headers). Generally only do this if you are
770808
** about to call exit().
771809
**
772810
** !g.isCGI then alsoOutput is ignored and all output is sent to
773811
** stdout immediately.
774812
**
775
-** This clears any previously buffered CGI content, replacing it with
776
-** JSON.
777813
*/
778814
void json_err( int code, char const * msg, char alsoOutput ){
779815
int rc = code ? code : (g.json.resultCode
780816
? g.json.resultCode
781817
: FSL_JSON_E_UNKNOWN);
@@ -783,10 +819,18 @@
783819
rc = json_dumbdown_rc(rc);
784820
if( rc && !msg ){
785821
msg = json_err_str(rc);
786822
}
787823
resp = json_create_response(rc, NULL, msg);
824
+ if(!resp){
825
+ /* about the only error case here is out-of-memory. DO NOT
826
+ call fossil_panic() here because that calls this function.
827
+ */
828
+ fprintf(stderr, "%s: Fatal error: could not allocate "
829
+ "response object.\n", fossil_nameofexe());
830
+ fossil_exit(1);
831
+ }
788832
if( g.isCGI ){
789833
Blob buf = empty_blob;
790834
cgi_reset_content();
791835
cson_output_Blob( resp, &buf, &g.json.outOpt );
792836
cgi_set_content(&buf);
@@ -821,20 +865,10 @@
821865
cson_object_set( jobj, "resultCodeParanoiaLevel",
822866
cson_value_new_integer(g.json.errorDetailParanoia) );
823867
return jval;
824868
}
825869
826
-/*
827
-** Returns the string form of a json_getenv() value, but ONLY
828
-** If that value is-a String. Non-strings are not converted
829
-** to strings for this purpose. Returned memory is owned by
830
-** g.json or fossil..
831
-*/
832
-static char const * json_getenv_cstr( char const * zKey ){
833
- return cson_value_get_cstr( json_getenv(zKey) );
834
-}
835
-
836870
837871
/*
838872
** Implementation for /json/cap
839873
**
840874
** Returned object contains details about the "capabilities" of the
@@ -1207,21 +1241,78 @@
12071241
{"login",json_page_login,1},
12081242
{"logout",json_page_logout,1},
12091243
{"stat",json_page_stat,0},
12101244
{"tag", json_page_nyi,0},
12111245
{"ticket", json_page_nyi,0},
1246
+{"timeline", json_page_nyi,0},
12121247
{"user", json_page_nyi,0},
12131248
{"version",json_page_version,0},
12141249
{"wiki",json_page_wiki,0},
12151250
/* Last entry MUST have a NULL name. */
12161251
{NULL,NULL,0}
12171252
};
1253
+
1254
+/*
1255
+** Mapping of /json/wiki/XXX commands/paths to callbacks.
1256
+*/
1257
+static const JsonPageDef JsonPageDefs_Wiki[] = {
1258
+{"get", json_page_nyi, 0},
1259
+{"list", json_page_nyi, 0},
1260
+{"save", json_page_nyi, 1},
1261
+/* Last entry MUST have a NULL name. */
1262
+{NULL,NULL,0}
1263
+};
1264
+
1265
+/*
1266
+** Mapping of /json/ticket/XXX commands/paths to callbacks.
1267
+*/
1268
+static const JsonPageDef JsonPageDefs_Ticket[] = {
1269
+{"get", json_page_nyi, 0},
1270
+{"list", json_page_nyi, 0},
1271
+{"save", json_page_nyi, 1},
1272
+{"create", json_page_nyi, 1},
1273
+/* Last entry MUST have a NULL name. */
1274
+{NULL,NULL,0}
1275
+};
1276
+
1277
+/*
1278
+** Mapping of /json/artifact/XXX commands/paths to callbacks.
1279
+*/
1280
+static const JsonPageDef JsonPageDefs_Artifact[] = {
1281
+{"vinfo", json_page_nyi, 0},
1282
+{"finfo", json_page_nyi, 0},
1283
+/* Last entry MUST have a NULL name. */
1284
+{NULL,NULL,0}
1285
+};
1286
+
1287
+/*
1288
+** Mapping of /json/branch/XXX commands/paths to callbacks.
1289
+*/
1290
+static const JsonPageDef JsonPageDefs_Branch[] = {
1291
+{"list", json_page_nyi, 0},
1292
+{"create", json_page_nyi, 1},
1293
+/* Last entry MUST have a NULL name. */
1294
+{NULL,NULL,0}
1295
+};
1296
+
1297
+/*
1298
+** Mapping of /json/tag/XXX commands/paths to callbacks.
1299
+*/
1300
+static const JsonPageDef JsonPageDefs_Tag[] = {
1301
+{"list", json_page_nyi, 0},
1302
+{"create", json_page_nyi, 1},
1303
+/* Last entry MUST have a NULL name. */
1304
+{NULL,NULL,0}
1305
+};
1306
+
12181307
12191308
/*
12201309
** WEBPAGE: json
12211310
**
12221311
** Pages under /json/... must be entered into JsonPageDefs.
1312
+** This function dispatches them, and is the HTTP equivalent of
1313
+** json_cmd_top().
12231314
*/
12241315
void json_page_top(void){
12251316
int rc = FSL_JSON_E_UNKNOWN_COMMAND;
12261317
Blob buf = empty_blob;
12271318
char const * cmd;
@@ -1251,10 +1342,13 @@
12511342
cgi_set_content(&buf)/*takes ownership of the buf memory*/;
12521343
}
12531344
}
12541345
12551346
/*
1347
+** This function dispatches json commands and is the CLI equivalent of
1348
+** json_page_top().
1349
+**
12561350
** COMMAND: json
12571351
**
12581352
** Usage: %fossil json SUBCOMMAND
12591353
**
12601354
** The commands include:
@@ -1274,11 +1368,11 @@
12741368
** ...
12751369
**
12761370
*/
12771371
void json_cmd_top(void){
12781372
char const * cmd = NULL;
1279
- int rc = 1002;
1373
+ int rc = FSL_JSON_E_UNKNOWN_COMMAND;
12801374
cson_value * payload = NULL;
12811375
JsonPageDef const * pageDef;
12821376
memset( &g.perm, 0xff, sizeof(g.perm) )
12831377
/* In CLI mode fossil does not use permissions
12841378
and they all default to false. We enable them
12851379
--- src/json.c
+++ src/json.c
@@ -20,10 +20,18 @@
20 ** For notes regarding the public JSON interface, please see:
21 **
22 ** https://docs.google.com/document/d/1fXViveNhDbiXgCuE7QDXQOKeFzf2qNUkBEgiUvoqFN4/edit
23 **
24 **
 
 
 
 
 
 
 
 
25 */
26 #include "config.h"
27 #include "VERSION.h"
28 #include "json.h"
29 #include <assert.h>
@@ -31,10 +39,34 @@
31
32 #if INTERFACE
33 #include "cson_amalgamation.h"
34 #include "json_detail.h" /* workaround for apparent enum limitation in makeheaders */
35 #endif
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
37 /*
38 ** Holds keys used for various JSON API properties.
39 */
40 static const struct FossilJsonKeys_{
@@ -217,17 +249,29 @@
217 }
218 }
219 }
220 return NULL;
221 }
 
 
 
 
 
 
 
 
 
 
 
 
222
223 /*
224 ** Adds v to g.json.param.o using the given key. May cause
225 ** any prior item with that key to be destroyed (depends on
226 ** current reference count for that value).
227 ** On succes, transfers ownership of v to g.json.param.o.
228 ** On error ownership of v is not modified.
229 */
230 int json_setenv( char const * zKey, cson_value * v ){
231 return cson_object_set( g.json.param.o, zKey, v );
232 }
233
@@ -322,12 +366,13 @@
322 */
323 char const * zCookie = P(login_cookie_name());
324 if( zCookie && *zCookie ){
325 /* Transfer fossil's cookie to JSON for downstream convenience... */
326 cson_value * v = cson_value_new_string(zCookie, strlen(zCookie));
327 json_setenv( FossilJsonKeys.authToken, v );
328 g.json.authToken = v;
 
329 }
330 }
331 }
332 return g.json.authToken;
333 }
@@ -466,19 +511,17 @@
466 do{/* set up JSON out formatting options. */
467 unsigned char indent = g.isCGI ? 0 : 1;
468 cson_value const * indentV = json_getenv("indent");
469 if(indentV){
470 if(cson_value_is_string(indentV)){
471 int n = atoi(cson_string_cstr(cson_value_get_string(indentV)));
472 indent = (n>0)
473 ? (unsigned char)n
474 : 0;
475 }else if(cson_value_is_number(indentV)){
476 double n = cson_value_get_integer(indentV);
477 indent = (n>0)
478 ? (unsigned char)n
479 : 0;
480 }
481 }
482 g.json.outOpt.indentation = indent;
483 g.json.outOpt.addNewline = g.isCGI ? 0 : 1;
484 }while(0);
@@ -553,10 +596,11 @@
553 ** if json_auth_token() returns NULL.
554 */
555 char const * json_auth_token_cstr(){
556 return cson_value_get_cstr( json_auth_token() );
557 }
 
558
559 /*
560 ** Holds name-to-function mappings for JSON page/command dispatching.
561 **
562 */
@@ -572,13 +616,15 @@
572 char const * name;
573 /*
574 ** Returns a payload object for the response. If it returns a
575 ** non-NULL value, the caller owns it. To trigger an error this
576 ** function should set g.json.resultCode to a value from the
577 ** FossilJsonCodes enum.
 
 
578 */
579 cson_value * (*func)();
580 /*
581 ** Which mode(s) of execution does func() support:
582 **
583 ** <0 = CLI only, >0 = HTTP only, 0==both
584 */
@@ -625,39 +671,30 @@
625 }
626 if( modulo ) code = code - (code % modulo);
627 return code;
628 }
629 }
630
631 #if 0
632 static unsigned int json_timestamp(){
633
634 }
635 #endif
636
637
638 /*
639 ** Creates a new Fossil/JSON response envelope skeleton. It is owned
640 ** by the caller, who must eventually free it using cson_value_free(),
641 ** or add it to a cson container to transfer ownership. Returns NULL
642 ** on error.
643 **
644 ** If payload is not NULL and resultCode is 0 then it is set as the
645 ** "payload" property of the returned object. If resultCode is non-0
646 ** then this function will destroy payload if it is not NULL. i.e.
647 ** onwership of payload is transfered to this function.
 
 
648 **
649 ** pMsg is an optional message string (resultText) property of the
650 ** response. If resultCode is non-0 and pMsg is NULL then
651 ** json_err_str() is used to get the error string. The caller may
652 ** provide his own or may use an empty string to suppress the
653 ** resultText property.
654 **
655 ** If resultCode is non-zero and payload is not NULL then this
656 ** function calls cson_value_free(payload) and does not insert the
657 ** payload into the response.
658 **
659 */
660 cson_value * json_create_response( int resultCode,
661 cson_value * payload,
662 char const * pMsg ){
663 cson_value * v = NULL;
@@ -717,19 +754,24 @@
717 SET("resultText");
718 }
719 tmp = json_getenv("requestId");
720 if( tmp ) cson_object_set( o, "requestId", tmp );
721
722 if(0){
723 if(g.json.cmd.v){/* this is only intended for my own testing...*/
724 tmp = g.json.cmd.v;
725 SET("$commandPath");
726 }
727 if(g.json.param.v){/* this is only intended for my own testing...*/
728 tmp = g.json.param.v;
729 SET("$params");
730 }
 
 
 
 
 
731 }
732
733 /* Only add the payload to SUCCESS responses. Else delete it. */
734 if( NULL != payload ){
735 if( resultCode ){
@@ -740,42 +782,36 @@
740 SET("payload");
741 }
742 }
743
744 #undef SET
745
746 if(0){/*Only for debuggering, add some info to the response.*/
747 tmp = cson_value_new_integer( g.json.cmd.offset );
748 cson_object_set( o, "cmd.offset", tmp );
749 cson_object_set( o, "isCGI", cson_value_new_bool( g.isCGI ) );
750 }
751
752 goto ok;
753 cleanup:
754 cson_value_free(v);
755 v = NULL;
756 ok:
757 return v;
758 }
759
760 /*
761 ** Outputs a JSON error response to g.httpOut. If rc is 0 then
762 ** g.json.resultCode is used. If that is also 0 then the "Unknown
 
763 ** Error" code is used.
764 **
765 ** If g.isCGI then the generated error object replaces any currently
766 ** buffered page output.
 
 
767 **
768 ** If alsoOutput is true AND g.isCGI then the cgi_reply() is called to
769 ** flush the output (and headers). Generally only do this if you are
770 ** about to call exit().
771 **
772 ** !g.isCGI then alsoOutput is ignored and all output is sent to
773 ** stdout immediately.
774 **
775 ** This clears any previously buffered CGI content, replacing it with
776 ** JSON.
777 */
778 void json_err( int code, char const * msg, char alsoOutput ){
779 int rc = code ? code : (g.json.resultCode
780 ? g.json.resultCode
781 : FSL_JSON_E_UNKNOWN);
@@ -783,10 +819,18 @@
783 rc = json_dumbdown_rc(rc);
784 if( rc && !msg ){
785 msg = json_err_str(rc);
786 }
787 resp = json_create_response(rc, NULL, msg);
 
 
 
 
 
 
 
 
788 if( g.isCGI ){
789 Blob buf = empty_blob;
790 cgi_reset_content();
791 cson_output_Blob( resp, &buf, &g.json.outOpt );
792 cgi_set_content(&buf);
@@ -821,20 +865,10 @@
821 cson_object_set( jobj, "resultCodeParanoiaLevel",
822 cson_value_new_integer(g.json.errorDetailParanoia) );
823 return jval;
824 }
825
826 /*
827 ** Returns the string form of a json_getenv() value, but ONLY
828 ** If that value is-a String. Non-strings are not converted
829 ** to strings for this purpose. Returned memory is owned by
830 ** g.json or fossil..
831 */
832 static char const * json_getenv_cstr( char const * zKey ){
833 return cson_value_get_cstr( json_getenv(zKey) );
834 }
835
836
837 /*
838 ** Implementation for /json/cap
839 **
840 ** Returned object contains details about the "capabilities" of the
@@ -1207,21 +1241,78 @@
1207 {"login",json_page_login,1},
1208 {"logout",json_page_logout,1},
1209 {"stat",json_page_stat,0},
1210 {"tag", json_page_nyi,0},
1211 {"ticket", json_page_nyi,0},
 
1212 {"user", json_page_nyi,0},
1213 {"version",json_page_version,0},
1214 {"wiki",json_page_wiki,0},
1215 /* Last entry MUST have a NULL name. */
1216 {NULL,NULL,0}
1217 };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1218
1219 /*
1220 ** WEBPAGE: json
1221 **
1222 ** Pages under /json/... must be entered into JsonPageDefs.
 
 
1223 */
1224 void json_page_top(void){
1225 int rc = FSL_JSON_E_UNKNOWN_COMMAND;
1226 Blob buf = empty_blob;
1227 char const * cmd;
@@ -1251,10 +1342,13 @@
1251 cgi_set_content(&buf)/*takes ownership of the buf memory*/;
1252 }
1253 }
1254
1255 /*
 
 
 
1256 ** COMMAND: json
1257 **
1258 ** Usage: %fossil json SUBCOMMAND
1259 **
1260 ** The commands include:
@@ -1274,11 +1368,11 @@
1274 ** ...
1275 **
1276 */
1277 void json_cmd_top(void){
1278 char const * cmd = NULL;
1279 int rc = 1002;
1280 cson_value * payload = NULL;
1281 JsonPageDef const * pageDef;
1282 memset( &g.perm, 0xff, sizeof(g.perm) )
1283 /* In CLI mode fossil does not use permissions
1284 and they all default to false. We enable them
1285
--- src/json.c
+++ src/json.c
@@ -20,10 +20,18 @@
20 ** For notes regarding the public JSON interface, please see:
21 **
22 ** https://docs.google.com/document/d/1fXViveNhDbiXgCuE7QDXQOKeFzf2qNUkBEgiUvoqFN4/edit
23 **
24 **
25 ** Notes for hackers...
26 **
27 ** Here's how command/page dispatching works: json_page_top() (in HTTP mode) or
28 ** json_cmd_top() (in CLI mode) catch the "json" path/command. Those functions then
29 ** dispatch to a JSON-mode-specific command/page handler with the type fossil_json_f().
30 ** See the API docs for that typedef (below) for the semantics of the callbacks.
31 **
32 **
33 */
34 #include "config.h"
35 #include "VERSION.h"
36 #include "json.h"
37 #include <assert.h>
@@ -31,10 +39,34 @@
39
40 #if INTERFACE
41 #include "cson_amalgamation.h"
42 #include "json_detail.h" /* workaround for apparent enum limitation in makeheaders */
43 #endif
44
45 /*
46 ** Signature for JSON page/command callbacks. By the time the callback
47 ** is called, json_page_top() or json_cmd_top() will have set up the
48 ** JSON-related environment. Implementations may generate a "result
49 ** payload" of any JSON type by returning its value from this function
50 ** (ownership is tranferred to the caller). On error they should set
51 ** g.json.resultCode to one of the FossilJsonCodes values and return
52 ** either their payload object or NULL. Note that NULL is a legal
53 ** success value - it simply means the response will contain no
54 ** payload. If g.json.resultCode is non-zero when this function
55 ** returns then the top-level dispatcher will destroy any payload
56 ** returned by this function and will output a JSON error response
57 ** instead.
58 **
59 ** All of the setup/response code is handled by the top dispatcher
60 ** functions and the callbacks concern themselves only with generating
61 ** the payload.
62 **
63 ** It is imperitive that NO callback functions EVER output ANYTHING to
64 ** stdout, as that will effectively corrupt any HTTP output.
65 */
66 typedef cson_value * (*fossil_json_f)();
67
68
69 /*
70 ** Holds keys used for various JSON API properties.
71 */
72 static const struct FossilJsonKeys_{
@@ -217,17 +249,29 @@
249 }
250 }
251 }
252 return NULL;
253 }
254
255
256 /*
257 ** Returns the string form of a json_getenv() value, but ONLY
258 ** If that value is-a String. Non-strings are not converted
259 ** to strings for this purpose. Returned memory is owned by
260 ** g.json or fossil..
261 */
262 static char const * json_getenv_cstr( char const * zKey ){
263 return cson_value_get_cstr( json_getenv(zKey) );
264 }
265
266
267 /*
268 ** Adds v to g.json.param.o using the given key. May cause any prior
269 ** item with that key to be destroyed (depends on current reference
270 ** count for that value). On success, transfers (or shares) ownership
271 ** of v to (or with) g.json.param.o. On error ownership of v is not
272 ** modified.
273 */
274 int json_setenv( char const * zKey, cson_value * v ){
275 return cson_object_set( g.json.param.o, zKey, v );
276 }
277
@@ -322,12 +366,13 @@
366 */
367 char const * zCookie = P(login_cookie_name());
368 if( zCookie && *zCookie ){
369 /* Transfer fossil's cookie to JSON for downstream convenience... */
370 cson_value * v = cson_value_new_string(zCookie, strlen(zCookie));
371 if(0 == json_gc_add( FossilJsonKeys.authToken, v, 1 )){
372 g.json.authToken = v;
373 }
374 }
375 }
376 }
377 return g.json.authToken;
378 }
@@ -466,19 +511,17 @@
511 do{/* set up JSON out formatting options. */
512 unsigned char indent = g.isCGI ? 0 : 1;
513 cson_value const * indentV = json_getenv("indent");
514 if(indentV){
515 if(cson_value_is_string(indentV)){
516 int const n = atoi(cson_string_cstr(cson_value_get_string(indentV)));
517 indent = (n>0)
518 ? (unsigned char)n
519 : 0;
520 }else if(cson_value_is_number(indentV)){
521 cson_int_t const n = cson_value_get_integer(indentV);
522 indent = (n>0) ? (unsigned char)n : 0;
 
 
523 }
524 }
525 g.json.outOpt.indentation = indent;
526 g.json.outOpt.addNewline = g.isCGI ? 0 : 1;
527 }while(0);
@@ -553,10 +596,11 @@
596 ** if json_auth_token() returns NULL.
597 */
598 char const * json_auth_token_cstr(){
599 return cson_value_get_cstr( json_auth_token() );
600 }
601
602
603 /*
604 ** Holds name-to-function mappings for JSON page/command dispatching.
605 **
606 */
@@ -572,13 +616,15 @@
616 char const * name;
617 /*
618 ** Returns a payload object for the response. If it returns a
619 ** non-NULL value, the caller owns it. To trigger an error this
620 ** function should set g.json.resultCode to a value from the
621 ** FossilJsonCodes enum. If it sets an error value and returns
622 ** a payload, the payload will be destroyed (not sent with the
623 ** response).
624 */
625 fossil_json_f func;
626 /*
627 ** Which mode(s) of execution does func() support:
628 **
629 ** <0 = CLI only, >0 = HTTP only, 0==both
630 */
@@ -625,39 +671,30 @@
671 }
672 if( modulo ) code = code - (code % modulo);
673 return code;
674 }
675 }
 
 
 
 
 
 
 
676
677 /*
678 ** Creates a new Fossil/JSON response envelope skeleton. It is owned
679 ** by the caller, who must eventually free it using cson_value_free(),
680 ** or add it to a cson container to transfer ownership. Returns NULL
681 ** on error.
682 **
683 ** If payload is not NULL and resultCode is 0 then it is set as the
684 ** "payload" property of the returned object. If resultCode is
685 ** non-zero and payload is not NULL then this function calls
686 ** cson_value_free(payload) and does not insert the payload into the
687 ** response. In either case, onwership of payload is transfered to
688 ** this function.
689 **
690 ** pMsg is an optional message string property (resultText) of the
691 ** response. If resultCode is non-0 and pMsg is NULL then
692 ** json_err_str() is used to get the error string. The caller may
693 ** provide his own or may use an empty string to suppress the
694 ** resultText property.
695 **
 
 
 
 
696 */
697 cson_value * json_create_response( int resultCode,
698 cson_value * payload,
699 char const * pMsg ){
700 cson_value * v = NULL;
@@ -717,19 +754,24 @@
754 SET("resultText");
755 }
756 tmp = json_getenv("requestId");
757 if( tmp ) cson_object_set( o, "requestId", tmp );
758
759 if(0){/* these are only intended for my own testing...*/
760 if(g.json.cmd.v){
761 tmp = g.json.cmd.v;
762 SET("$commandPath");
763 }
764 if(g.json.param.v){
765 tmp = g.json.param.v;
766 SET("$params");
767 }
768 if(0){/*Only for debuggering, add some info to the response.*/
769 tmp = cson_value_new_integer( g.json.cmd.offset );
770 cson_object_set( o, "cmd.offset", tmp );
771 cson_object_set( o, "isCGI", cson_value_new_bool( g.isCGI ) );
772 }
773 }
774
775 /* Only add the payload to SUCCESS responses. Else delete it. */
776 if( NULL != payload ){
777 if( resultCode ){
@@ -740,42 +782,36 @@
782 SET("payload");
783 }
784 }
785
786 #undef SET
 
 
 
 
 
 
 
787 goto ok;
788 cleanup:
789 cson_value_free(v);
790 v = NULL;
791 ok:
792 return v;
793 }
794
795 /*
796 ** Outputs a JSON error response to either the cgi_xxx() family of
797 ** buffers (in CGI/server mode) or stdout (in CLI mode). If rc is 0
798 ** then g.json.resultCode is used. If that is also 0 then the "Unknown
799 ** Error" code is used.
800 **
801 ** If g.isCGI then the generated JSON error response object replaces
802 ** any currently buffered page output. Because the output goes via
803 ** the cgi_xxx() family of functions, this function inherits any
804 ** compression which fossil does for its output.
805 **
806 ** If alsoOutput is true AND g.isCGI then cgi_reply() is called to
807 ** flush the output (and headers). Generally only do this if you are
808 ** about to call exit().
809 **
810 ** !g.isCGI then alsoOutput is ignored and all output is sent to
811 ** stdout immediately.
812 **
 
 
813 */
814 void json_err( int code, char const * msg, char alsoOutput ){
815 int rc = code ? code : (g.json.resultCode
816 ? g.json.resultCode
817 : FSL_JSON_E_UNKNOWN);
@@ -783,10 +819,18 @@
819 rc = json_dumbdown_rc(rc);
820 if( rc && !msg ){
821 msg = json_err_str(rc);
822 }
823 resp = json_create_response(rc, NULL, msg);
824 if(!resp){
825 /* about the only error case here is out-of-memory. DO NOT
826 call fossil_panic() here because that calls this function.
827 */
828 fprintf(stderr, "%s: Fatal error: could not allocate "
829 "response object.\n", fossil_nameofexe());
830 fossil_exit(1);
831 }
832 if( g.isCGI ){
833 Blob buf = empty_blob;
834 cgi_reset_content();
835 cson_output_Blob( resp, &buf, &g.json.outOpt );
836 cgi_set_content(&buf);
@@ -821,20 +865,10 @@
865 cson_object_set( jobj, "resultCodeParanoiaLevel",
866 cson_value_new_integer(g.json.errorDetailParanoia) );
867 return jval;
868 }
869
 
 
 
 
 
 
 
 
 
 
870
871 /*
872 ** Implementation for /json/cap
873 **
874 ** Returned object contains details about the "capabilities" of the
@@ -1207,21 +1241,78 @@
1241 {"login",json_page_login,1},
1242 {"logout",json_page_logout,1},
1243 {"stat",json_page_stat,0},
1244 {"tag", json_page_nyi,0},
1245 {"ticket", json_page_nyi,0},
1246 {"timeline", json_page_nyi,0},
1247 {"user", json_page_nyi,0},
1248 {"version",json_page_version,0},
1249 {"wiki",json_page_wiki,0},
1250 /* Last entry MUST have a NULL name. */
1251 {NULL,NULL,0}
1252 };
1253
1254 /*
1255 ** Mapping of /json/wiki/XXX commands/paths to callbacks.
1256 */
1257 static const JsonPageDef JsonPageDefs_Wiki[] = {
1258 {"get", json_page_nyi, 0},
1259 {"list", json_page_nyi, 0},
1260 {"save", json_page_nyi, 1},
1261 /* Last entry MUST have a NULL name. */
1262 {NULL,NULL,0}
1263 };
1264
1265 /*
1266 ** Mapping of /json/ticket/XXX commands/paths to callbacks.
1267 */
1268 static const JsonPageDef JsonPageDefs_Ticket[] = {
1269 {"get", json_page_nyi, 0},
1270 {"list", json_page_nyi, 0},
1271 {"save", json_page_nyi, 1},
1272 {"create", json_page_nyi, 1},
1273 /* Last entry MUST have a NULL name. */
1274 {NULL,NULL,0}
1275 };
1276
1277 /*
1278 ** Mapping of /json/artifact/XXX commands/paths to callbacks.
1279 */
1280 static const JsonPageDef JsonPageDefs_Artifact[] = {
1281 {"vinfo", json_page_nyi, 0},
1282 {"finfo", json_page_nyi, 0},
1283 /* Last entry MUST have a NULL name. */
1284 {NULL,NULL,0}
1285 };
1286
1287 /*
1288 ** Mapping of /json/branch/XXX commands/paths to callbacks.
1289 */
1290 static const JsonPageDef JsonPageDefs_Branch[] = {
1291 {"list", json_page_nyi, 0},
1292 {"create", json_page_nyi, 1},
1293 /* Last entry MUST have a NULL name. */
1294 {NULL,NULL,0}
1295 };
1296
1297 /*
1298 ** Mapping of /json/tag/XXX commands/paths to callbacks.
1299 */
1300 static const JsonPageDef JsonPageDefs_Tag[] = {
1301 {"list", json_page_nyi, 0},
1302 {"create", json_page_nyi, 1},
1303 /* Last entry MUST have a NULL name. */
1304 {NULL,NULL,0}
1305 };
1306
1307
1308 /*
1309 ** WEBPAGE: json
1310 **
1311 ** Pages under /json/... must be entered into JsonPageDefs.
1312 ** This function dispatches them, and is the HTTP equivalent of
1313 ** json_cmd_top().
1314 */
1315 void json_page_top(void){
1316 int rc = FSL_JSON_E_UNKNOWN_COMMAND;
1317 Blob buf = empty_blob;
1318 char const * cmd;
@@ -1251,10 +1342,13 @@
1342 cgi_set_content(&buf)/*takes ownership of the buf memory*/;
1343 }
1344 }
1345
1346 /*
1347 ** This function dispatches json commands and is the CLI equivalent of
1348 ** json_page_top().
1349 **
1350 ** COMMAND: json
1351 **
1352 ** Usage: %fossil json SUBCOMMAND
1353 **
1354 ** The commands include:
@@ -1274,11 +1368,11 @@
1368 ** ...
1369 **
1370 */
1371 void json_cmd_top(void){
1372 char const * cmd = NULL;
1373 int rc = FSL_JSON_E_UNKNOWN_COMMAND;
1374 cson_value * payload = NULL;
1375 JsonPageDef const * pageDef;
1376 memset( &g.perm, 0xff, sizeof(g.perm) )
1377 /* In CLI mode fossil does not use permissions
1378 and they all default to false. We enable them
1379
+1 -1
--- src/main.c
+++ src/main.c
@@ -443,11 +443,11 @@
443443
mainInFatalError = 1;
444444
va_start(ap, zFormat);
445445
z = vmprintf(zFormat, ap);
446446
va_end(ap);
447447
if( g.json.isJsonMode ){
448
- json_err( 0, z, 1 );
448
+ json_err( g.json.resultCode, z, 1 );
449449
if( g.isCGI ){
450450
rc = 0 /* avoid HTTP 500 */;
451451
}
452452
}
453453
else if( g.cgiOutput ){
454454
--- src/main.c
+++ src/main.c
@@ -443,11 +443,11 @@
443 mainInFatalError = 1;
444 va_start(ap, zFormat);
445 z = vmprintf(zFormat, ap);
446 va_end(ap);
447 if( g.json.isJsonMode ){
448 json_err( 0, z, 1 );
449 if( g.isCGI ){
450 rc = 0 /* avoid HTTP 500 */;
451 }
452 }
453 else if( g.cgiOutput ){
454
--- src/main.c
+++ src/main.c
@@ -443,11 +443,11 @@
443 mainInFatalError = 1;
444 va_start(ap, zFormat);
445 z = vmprintf(zFormat, ap);
446 va_end(ap);
447 if( g.json.isJsonMode ){
448 json_err( g.json.resultCode, z, 1 );
449 if( g.isCGI ){
450 rc = 0 /* avoid HTTP 500 */;
451 }
452 }
453 else if( g.cgiOutput ){
454

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button