code wiki / _hdl_build / nx_cms_api_gate.nx

nx_cms_api_gate.nx source

↩ module page · 140 lines · 7654 B

1// nx_cms_api_gate.nx -- CMS REST + GraphQL API gate (the rest-graphql-api class). Proves nx_cms_api over 2// the REAL nx_cms_store: a REST render carries every requested field, a GraphQL-style projection returns 3// ONLY the asked fields, RFC 8259 escaping makes a quote/newline-bearing value injection-safe, an unknown 4// field is dropped gracefully, a list endpoint emits a JSON array, and an empty projection is {} (defined, 5// not garbage). Appends "CMSGATE row=nx_cms_api ... verdict=PASS" to cms_gate.log ONLY if all rows pass. 6// license_tier: ORIGINAL 7import "nx_cms_api.nx" 8import "nx_cms_store.nx" 9import "nx_syscalls.nx" 10 11func ag_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 12func ag_num(v: i64) -> i64 { let bb: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m}; let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48;k=1}; while m>0{t[k]=48+(m%10);m=m/10;k=k+1}; var i: i64=0; while i<k{bb[i]=t[k-1-i];i=i+1}; sys_write(1,bb,k); return 0 } 13func ag_cat(dst: *u8, off: i64, s: *u8) -> i64 { var o: i64=off; var k: i64=0; while s[k]!=(0 as u8){dst[o]=s[k];o=o+1;k=k+1} return o } 14func ag_catnum(dst: *u8, off: i64, v: i64) -> i64 { 15 var o: i64=off; let t: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m}; var k: i64=0 16 if m==0 {t[0]=48 as u8;k=1}; while m>0 {t[k]=(48+(m%10)) as u8; m=m/10; k=k+1} 17 var i: i64=0; while i<k {dst[o]=t[k-1-i]; o=o+1; i=i+1} return o 18} 19func ag_cpy(dst: *u8, src: *u8) -> i64 { var k: i64=0; while src[k]!=(0 as u8){dst[k]=src[k];k=k+1} return k } 20 21// substring search over buf[0..n) for NUL-terminated needle; -1 if absent 22func ag_find(buf: *u8, n: i64, needle: *u8) -> i64 { 23 var nl: i64=0; while needle[nl]!=(0 as u8){nl=nl+1} 24 var i: i64=0 25 while i + nl <= n { 26 var q: i64=0; var ok: i64=1 27 while q<nl { if (buf[i+q] as i64) != (needle[q] as i64) { ok=0; q=nl } q=q+1 } 28 if ok==1 { return i } 29 i=i+1 30 } 31 return 0 - 1 32} 33func ag_has(buf: *u8, n: i64, needle: *u8) -> i64 { if ag_find(buf,n,needle)>=0 { return 1 } return 0 } 34// does buf[0..n) contain a raw byte b? 35func ag_hasbyte(buf: *u8, n: i64, b: i64) -> i64 { var i: i64=0; while i<n { if (buf[i] as i64)==b { return 1 } i=i+1 } return 0 } 36 37func ag_row(id: i64, ok: i64, what: *u8) -> i64 { 38 ag_w("APIROW " as *u8); ag_num(id); ag_w(" " as *u8) 39 if ok==1 { ag_w("PASS " as *u8) } else { ag_w("FAIL " as *u8) } 40 ag_w(what); ag_w("\n" as *u8) 41 return ok 42} 43 44func main() -> i64 { 45 var pass: i64 = 0 46 var rows: i64 = 0 47 var ok: i64 = 0 48 49 let store: *u8 = sys_mmap(8192) 50 let sn: i64 = ag_cpy(store, "@title\nAutumn Sale\n@status\nscheduled\n@body\nBig autumn news.\n" as *u8) 51 let out: *u8 = sys_mmap(8192) 52 53 // field-name arrays (each entry is a *u8 cast to i64) 54 let all3: *i64 = sys_mmap(64) as *i64 55 all3[0] = "title" as *u8 as i64 56 all3[1] = "status" as *u8 as i64 57 all3[2] = "body" as *u8 as i64 58 let just_title: *i64 = sys_mmap(64) as *i64 59 just_title[0] = "title" as *u8 as i64 60 61 // R0: REST render -> all requested fields present with correct values 62 var on: i64 = cms_api_render_obj(store, sn, all3, 3, out, 8192) 63 ok = 0 64 if ag_has(out, on, "\"title\":\"Autumn Sale\"" as *u8) == 1 { 65 if ag_has(out, on, "\"status\":\"scheduled\"" as *u8) == 1 { 66 if ag_has(out, on, "\"body\":\"Big autumn news.\"" as *u8) == 1 { ok = 1 } } } 67 rows=rows+1; pass=pass+ag_row(0, ok, "REST render carries all requested fields+values" as *u8) 68 69 // R1: GraphQL-style projection -> ONLY the asked field, others ABSENT 70 on = cms_api_render_obj(store, sn, just_title, 1, out, 8192) 71 ok = 0 72 if ag_has(out, on, "\"title\":\"Autumn Sale\"" as *u8) == 1 { 73 if ag_has(out, on, "status" as *u8) == 0 { if ag_has(out, on, "body" as *u8) == 0 { ok = 1 } } } 74 rows=rows+1; pass=pass+ag_row(1, ok, "GraphQL projection returns ONLY requested field" as *u8) 75 76 // R2 + R3: RFC 8259 escaping -- a value with a quote and a newline is injection-safe 77 let store2: *u8 = sys_mmap(8192) 78 let sn2: i64 = ag_cpy(store2, "@body\nShe said \"hi\"\nsecond line\n@z\nx\n" as *u8) 79 let bodyf: *i64 = sys_mmap(64) as *i64 80 bodyf[0] = "body" as *u8 as i64 81 on = cms_api_render_obj(store2, sn2, bodyf, 1, out, 8192) 82 // R2: the embedded quote became \" (bytes 0x5C 0x22) 83 let bs_q: *u8 = sys_mmap(4); bs_q[0]=92 as u8; bs_q[1]=34 as u8; bs_q[2]=0 as u8 84 ok = 0; if ag_has(out, on, bs_q) == 1 { ok = 1 } 85 rows=rows+1; pass=pass+ag_row(2, ok, "escaping: embedded quote -> backslash-quote" as *u8) 86 // R3: the embedded newline became \n (bytes 0x5C 0x6E) AND there is NO raw newline byte in the JSON 87 let bs_n: *u8 = sys_mmap(4); bs_n[0]=92 as u8; bs_n[1]=110 as u8; bs_n[2]=0 as u8 88 ok = 0; if ag_has(out, on, bs_n) == 1 { if ag_hasbyte(out, on, 10) == 0 { ok = 1 } } 89 rows=rows+1; pass=pass+ag_row(3, ok, "escaping: embedded newline -> backslash-n, no raw newline" as *u8) 90 91 // R4: unknown field requested -> dropped gracefully; object still valid, present fields kept 92 let mixed: *i64 = sys_mmap(64) as *i64 93 mixed[0] = "title" as *u8 as i64 94 mixed[1] = "ghostfield" as *u8 as i64 95 mixed[2] = "status" as *u8 as i64 96 on = cms_api_render_obj(store, sn, mixed, 3, out, 8192) 97 ok = 0 98 if ag_has(out, on, "ghostfield" as *u8) == 0 { 99 if ag_has(out, on, "\"title\":\"Autumn Sale\"" as *u8) == 1 { 100 if ag_has(out, on, "\"status\":\"scheduled\"" as *u8) == 1 { 101 if (out[on-1] as i64) == 125 { ok = 1 } } } } // ends with '}' 102 rows=rows+1; pass=pass+ag_row(4, ok, "unknown field dropped gracefully, object stays valid" as *u8) 103 104 // R5: list endpoint -> JSON array of projected objects 105 let storeB: *u8 = sys_mmap(8192) 106 let snB: i64 = ag_cpy(storeB, "@title\nWinter Clearance\n@status\nscheduled\n@body\nb.\n" as *u8) 107 let stores: *i64 = sys_mmap(64) as *i64 108 stores[0] = store as i64 109 stores[1] = storeB as i64 110 let counts: *i64 = sys_mmap(64) as *i64 111 counts[0] = sn 112 counts[1] = snB 113 on = cms_api_render_list(stores, counts, 2, just_title, 1, out, 8192) 114 ok = 0 115 if (out[0] as i64) == 91 { if (out[on-1] as i64) == 93 { // '[' ... ']' 116 if ag_has(out, on, "\"title\":\"Autumn Sale\"" as *u8) == 1 { 117 if ag_has(out, on, "\"title\":\"Winter Clearance\"" as *u8) == 1 { ok = 1 } } } } 118 rows=rows+1; pass=pass+ag_row(5, ok, "list endpoint emits JSON array of projected objects" as *u8) 119 120 // R6: negative control -- empty projection is a well-defined {} (not garbage) 121 on = cms_api_render_obj(store, sn, all3, 0, out, 8192) 122 ok = 0; if on == 2 { if (out[0] as i64) == 123 { if (out[1] as i64) == 125 { ok = 1 } } } // "{}" 123 rows=rows+1; pass=pass+ag_row(6, ok, "empty projection -> {} (defined, not garbage)" as *u8) 124 125 ag_w("CMS-API-GATE rows=" as *u8); ag_num(rows); ag_w(" pass=" as *u8); ag_num(pass); ag_w("\n" as *u8) 126 127 if pass == rows { 128 let line: *u8 = sys_mmap(256) 129 var lo: i64 = ag_cat(line, 0, "CMSGATE row=nx_cms_api rest-graphql-api rows=" as *u8) 130 lo = ag_catnum(line, lo, rows) 131 lo = ag_cat(line, lo, " pass=" as *u8); lo = ag_catnum(line, lo, pass) 132 lo = ag_cat(line, lo, " verdict=PASS\n" as *u8) 133 let gf: i64 = sys_openat_append("knowledge/status/cms_gate.log" as *u8, 0x1a4) 134 if gf >= 0 { sys_write(gf, line, lo); sys_close(gf) } 135 ag_w("CMS-API-GATE verdict=PASS -- rest-graphql-api recorded in cms_gate.log\n" as *u8) 136 sys_exit(0); return 0 137 } 138 ag_w("CMS-API-GATE verdict=FAIL -- NOT recorded (no fake-green)\n" as *u8) 139 sys_exit(1); return 1 140}