From ac7b25e9bf77809afeb62512868ae5bdac52ae12 Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Tue, 29 Sep 2020 12:33:17 +0200 Subject: [PATCH] [MIRROR] JATUM (#1045) * JATUM (#53831) Adds the JATUM serialization system. Inspired by @ RemieRichards. Allows for easy serialization and deserialization of trivial datums. Also adds a missing html_encode. * JATUM Co-authored-by: Jordan Brown --- .github/CODEOWNERS | 1 + code/__HELPERS/jatum.dm | 221 ++++++++++++++++++++++++ code/modules/admin/callproc/callproc.dm | 2 +- tgstation.dme | 1 + 4 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 code/__HELPERS/jatum.dm diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 94d9b42a2d9..beee9228028 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -14,6 +14,7 @@ # Cyberboss +/code/__HELPERS/jatum.dm @Cyberboss /code/controllers/subsystem/atoms.dm @Cyberboss /code/controllers/subsystem/mapping.dm @Cyberboss /code/controllers/globals.dm @Cyberboss diff --git a/code/__HELPERS/jatum.dm b/code/__HELPERS/jatum.dm new file mode 100644 index 00000000000..c8a34035a0d --- /dev/null +++ b/code/__HELPERS/jatum.dm @@ -0,0 +1,221 @@ +// JATUM. System for making the serializing and deserializing of PoD types easier + +/** + * Gets the flat list that can be passed in a `new /type(argslist(retval))` expression to recreate the datum. Must only return a list containing values that can be JATUM serialized + */ +/datum/proc/jatum_new_arglist() + return null + +/// The JATUM version used for serializing and deserializing +#define JATUM_VERSION 1 + +/** + * Attempt to serialize a given value to the JATUM format. + * + * * value - The var to serialize. + */ +/world/proc/jatum_serialize(value) + var/list/json_structure + try + json_structure = _jatum_serialize_value(value, list()) + catch(var/exception/e) + CRASH(e) + + return json_encode(list( + "jatum\\version" = JATUM_VERSION, + "content" = json_structure + )) + +/world/proc/_jatum_serialize_value(value, list/seen_references) + if(isnull(value)) + return null + + if(isnum(value) || istext(value)) + return list( + "type" = "jatum\\raw", + "value" = value + ) + + if(ispath(value)) + return list( + "type" = "jatum\\path", + "path" = value + ) + + var/ref = "\ref[value]" + var/existing_ref = seen_references[ref] + if(existing_ref) + return list( + "type" = "jatum\\ref", + "jatum\\id" = existing_ref) + + // Simple incrementing ID system + var/ref_id = seen_references.len + 1 + seen_references[ref] = ref_id + + if(istype(value, /matrix)) // matricies work with json_encode so use a custom arglist + return list( + "jatum\\id" = ref_id, + "type" = "/matrix", + "jatum\\new_arglist" = _jatum_serialize_value(json_decode(json_encode(value))), seen_references) + + if(istype(value, /savefile)) // Just store the path, rely on BYOND for the rest + var/savefile/save = value + return list( + "jatum\\id" = ref_id, + "type" = "/savefile", + "jatum\\new_arglist" = _jatum_serialize_value(list(save.name), seen_references) + ) + + if(istype(value, /regex)) // store construct params + var/regex/rx = value + return list( + "jatum\\id" = ref_id, + "type" = "/regex", + "name" = rx.name, + "flags" = rx.flags + ) + + if(islist(value)) + // Serialize all lists as dicts, list("a") and list("a" = null) can't be differentiated in DM + var/list_contents = list() + for(var/key in value) + var/got_l_value + var/l_value + try + l_value = value[key] + got_l_value = TRUE + catch + // Expected, indicates a flat list + + if(got_l_value) + list_contents += list(list( + "key" = _jatum_serialize_value(key, seen_references), + "value" = _jatum_serialize_value(l_value, seen_references) + )) + else + list_contents += list(list( + "value" = _jatum_serialize_value(key, seen_references) + )) + return list( + "jatum\\id" = ref_id, + "type" = "/list", + "contents" = list_contents) + + // JATUM is really only meant for PoD types + if(!istype(value, /datum)\ + || istype(value, /image)\ + || istype(value, /icon)\ + || istype(value, /sound)\ + || istype(value, /atom)\ + || istype(value, /mutable_appearance)\ + || istype(value, /client)\ + || istype(value, /database)) + CRASH("Incompatible type for JATUM: [value]([ref])") + + // Confirmed /datum type + var/datum/D = value + var/json_structure = list( + "jatum\\id" = ref_id, + "type" = "[D.type]" + ) + var/new_arglist = D.jatum_new_arglist() + if(new_arglist) + if(!islist(new_arglist)) + CRASH("Non-list return from jatum_new_arglist from [D.type]!") + json_structure["jatum\\new_arglist"] = _jatum_serialize_value(new_arglist, seen_references) + + for(var/var_name in D.vars) + if(var_name == "vars" || var_name == "parent_type" || var_name == "type") + continue + + var/d_value = D.vars[var_name] + json_structure[var_name] = _jatum_serialize_value(d_value, seen_references) + + return json_structure + +/** + * Attempt to create a value from a JATUN JSON. + * + * * json - The JSON to deserialize. + */ +/world/proc/jatum_deserialize(json) + if(!istext(json)) + CRASH("Non-text passed!") + + var/list/structure = json_decode(json) + if(!structure) + CRASH("Invalid JSON!") + + var/jatum_version = structure["jatum\\version"] + if(isnull(jatum_version)) + CRASH("Not JATUM JSON!") + + if(jatum_version != JATUM_VERSION) + CRASH("Incompatible JATUM_VERSION") + + try + return _jatum_deserialize_value(structure["content"], list()) + catch(var/exception/e) + CRASH(e) + +/world/proc/_jatum_deserialize_value(list/structure, list/active_references) + if(!structure) + return null + + var/ref_id = structure["jatum\\id"] + var/entry_type = structure["type"] + switch(entry_type) + if("jatum\\raw") + return structure["value"] + if("jatum\\path") + var/string_path = structure["path"] + var/path = text2path(string_path) + if(!path) + CRASH("Invalid path: [string_path]") + return path + if("jatum\\ref") + var/result = active_references[ref_id] + if(!result) + CRASH("Missing reference ID [ref_id]!") + return result + if("/regex") + var/name = structure["name"] + var/flags = structure["flags"] + if(flags) + . = new /regex(name, flags) + else + . = new /regex(name) + if("/list") + var/list_contents = structure["contents"] + . = list() + for(var/entry in list_contents) + var/key = entry["key"] + var/l_value = entry["value"] + if(key) + .[_jatum_deserialize_value(key, active_references)] = _jatum_deserialize_value(l_value, active_references) + else + . += _jatum_deserialize_value(l_value, active_references) + + else + // everything else is a /datum and instantiates via reflection + var/list/jatum_arglist = structure["jatum\\new_arglist"] + var/full_type = text2path(entry_type) + if(!full_type) + CRASH("Invalid type: [entry_type]") + if(jatum_arglist) + var/list/new_arglist = _jatum_deserialize_value(jatum_arglist, active_references) + . = new full_type(arglist(new_arglist)) + else + . = new full_type + + var/datum/D = . + for(var/var_name in structure) + if(var_name == "type"\ + || var_name == "jatum\\id"\ + || var_name == "jatum\\new_arglist") + continue + var/value = structure[var_name] + D.vars[var_name] = _jatum_deserialize_value(value, active_references) + + active_references["[ref_id]"] = . diff --git a/code/modules/admin/callproc/callproc.dm b/code/modules/admin/callproc/callproc.dm index daff5ffeb16..097008cf8b9 100644 --- a/code/modules/admin/callproc/callproc.dm +++ b/code/modules/admin/callproc/callproc.dm @@ -199,4 +199,4 @@ GLOBAL_PROTECT(LastAdminCalledProc) . += "" else - . = "[procname] returned: [!isnull(returnval) ? returnval : "null"]" + . = "[procname] returned: [!isnull(returnval) ? html_encode(returnval) : "null"]" diff --git a/tgstation.dme b/tgstation.dme index f3c63949c5b..bd44b1a6d15 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -158,6 +158,7 @@ #include "code\__HELPERS\hearted.dm" #include "code\__HELPERS\icon_smoothing.dm" #include "code\__HELPERS\icons.dm" +#include "code\__HELPERS\jatum.dm" #include "code\__HELPERS\level_traits.dm" #include "code\__HELPERS\matrices.dm" #include "code\__HELPERS\mobs.dm"