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}