// A directory's export and its changes: read, written back, and the changes between two exports,
// ready for ldapmodify.
//
//   quillo run ldif.qwe      then open http://localhost:8080/read, /changes, /write, /dn, /diff and /broken

use "ldif.qwer"

let TEXT = read_file("ldif_sample.ldif")

route GET /read {
    let d = ldif_read_file("ldif_sample.ldif")?
    let ada = d.records[0]
    {
        version: d.version, count: len(d.records), dn: ada.dn, classes: ada.attributes.objectClass,
        lang: ada.attributes["cn;lang-fr"], folded: ada.attributes.description,
        binary: ada.attributes.jpegPhoto, url: ada.attributes["userCertificate;binary"], unicode: ada.attributes.sn, empty: ada.attributes.displayName,
        encoded_dn: d.records[1].dn,
    }
}

route GET /changes = (ldif_parse(TEXT)?.records | filter((r) => r.change != null))

let PEOPLE = [
    { dn: "cn=Zoë Müller,dc=example", attributes: { cn: ["Zoë Müller", "Zoe"], description: " starts with a space", note: "ends with a space ", odd: ":colon", lt: "<angle", plain: "x: y", photo: { base64: "/9j/4AAQ" }, link: { url: "http://example.org/a" }, empty: "", age: 41, text: trim(repeat("long ", 40)) } },
    { dn: "uid=a,dc=example", change: "modify", modifications: [{ op: "replace", attribute: "mail", values: ["a@example.org"] }, { op: "delete", attribute: "phone" }] },
    { dn: "uid=b,dc=example", change: "moddn", new_rdn: "uid=c", delete_old_rdn: false, new_superior: "dc=other" },
    { dn: "uid=d,dc=example", change: "delete", controls: [{ oid: "1.2.3", critical: true, value: "v" }] },
    { dn: "uid=e,dc=example", change: "add", attributes: { cn: "E" } },
]

route GET /write {
    let text = ldif_write(PEOPLE)?
    let back = ldif_parse(text)?
    {
        text: split(text, "\n") | filter((l) => contains(l, "::") || starts_with(l, "photo") || starts_with(l, "version")),
        widest: max(split(text, "\n") | map((l) => len(l))),
        same: json.stringify(back.records[0].attributes.text) == json.stringify([trim(repeat("long ", 40))]),
        tail: slice(back.records, 1, 5),
        again: ldif_write(back.records)? == text,
        no_dn: ldif_write([{ attributes: {} }]).error,
        no_body: ldif_write([{ dn: "a=b" }]).error,
        bad_op: ldif_write([{ dn: "a=b", change: "modify", modifications: [{ op: "wipe", attribute: "x" }] }]).error,
        bad_kind: ldif_write([{ dn: "a=b", change: "rename" }]).error,
    }
}

route GET /dn = {
    plain: ldif_dn_parse("cn=Doe\\, John,ou=Sales+l=Oslo , dc=example")?,
    hex: ldif_dn_parse("cn=caf\\C3\\A9")?[0][0].value,
    spaces: ldif_dn_parse("cn= a b ,o=x\\ ")? | map((r) => r[0].value),
    hash: ldif_dn_parse("1.3.6.1=#04024869")?[0][0].value,
    root: ldif_dn_parse("")?,
    bad: [ldif_dn_parse("nothing").error, ldif_dn_parse("=x").error, ldif_dn_parse("a=b\\").error],
}

route GET /diff {
    let a = "dn: cn=a,dc=x\ncn: a\nmail: 1@x\nmail: 2@x\nsn: old\n\ndn: cn=gone,dc=x\ncn: gone\n\ndn: cn=child,cn=gone,dc=x\ncn: child\n"
    let b = "dn: CN=A, DC=X\ncn: a\nmail: 2@x\nmail: 1@x\nphone: 5\n\ndn: cn=new,dc=x\ncn: new\n"
    let d = ldif_diff(a, b)?
    { changes: d, text: ldif_write(d)?, nothing: ldif_diff(a, a)? }
}

route GET /broken = {
    no_dn: ldif_parse("cn: x\n").error,
    no_colon: ldif_parse("dn: a=b\nnonsense\n").error,
    bad_b64: ldif_parse("dn: a=b\ncn:: ***\n").error,
    bad_version: ldif_parse("version: 2\n").error,
    bad_change: ldif_parse("dn: a=b\nchangetype: rename\n").error,
    unclosed: ldif_parse("dn: a=b\nchangetype: modify\nadd: x\nx: 1\n").error,
    wrong_attr: ldif_parse("dn: a=b\nchangetype: modify\nadd: x\ny: 1\n-\n").error,
    no_rdn: ldif_parse("dn: a=b\nchangetype: modrdn\n").error,
    bad_mod: ldif_parse("dn: a=b\nchangetype: modify\nfrob: x\n-\n").error,
    no_file: ldif_read_file("nothing.ldif").error != null,
}
