Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/shacl2code/lang/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ def get_all_named_individuals(cls):
"concrete_classes": concrete_classes,
"abstract_classes": abstract_classes,
"context": model.context,
"jss_signature": model.jss_signature,
**self.get_additional_render_args(model),
}

Expand Down
15 changes: 15 additions & 0 deletions src/shacl2code/lang/templates/cpp/jsonld-source.j2
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,21 @@ void JSONLDDeserializer::read(std::istream& input, SHACLObjectSet& objectSet,
errorHandler.handleDeserializeError("Invalid context URLs", path);
return;
}
{%- if jss_signature %}
{
const auto jss_signature = "{{ jss_signature }}";

if (d.contains(jss_signature)) {
path.pushMember(jss_signature, [&] {
if (!d[jss_signature].is_array()) {
errorHandler.handleDeserializeError("Signature {{ jss_signature }} must be an array", path);
return;
}
});
d.erase(jss_signature);
}
}
{%- endif %}

if (!d.contains("@graph")) {
JSONData data;
Expand Down
12 changes: 12 additions & 0 deletions src/shacl2code/lang/templates/golang/shaclobjectset.go.j2
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ var CONTEXT_URLS = []string{
{{ '/' }}{{ '*' }}*/
}

var JSS_SIGNATURE = "{{ jss_signature or '' }}"

func makeshaclObjectList(lst []SHACLObject) shaclObjectList {
idx := make(map[SHACLObject]int)
for i, o := range lst {
Expand Down Expand Up @@ -187,6 +189,16 @@ func (self *SHACLObjectSetObject) Decode(decoder *json.Decoder) error {
return DecodeSHACLObject[SHACLObject](data, path, context, nil, state)
}

if JSS_SIGNATURE != "" {
if signature, found := data[JSS_SIGNATURE]; found {
_, ok := signature.([]interface{})
if !ok {
return &DecodeError{path, "Bad type for signature '" + JSS_SIGNATURE + "'"}
}
}
delete(data, JSS_SIGNATURE)
}

_, has_graph := data["@graph"]
if has_graph {
for k, v := range data {
Expand Down
75 changes: 73 additions & 2 deletions src/shacl2code/lang/templates/jsonschema.j2
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,87 @@
"$ref": "#/$defs/AnyClass",
"unevaluatedProperties": false
}
}
}{%- if jss_signature %},
"{{ jss_signature }}": {
"$ref": "#/$defs/JSSsignatures"
}{%- endif %}
},
"required": ["@graph"]
},
"else": {
"$ref": "#/$defs/AnyClass"
"allOf": [
{
"$ref": "#/$defs/AnyClass"
}{%- if jss_signature %},
{
"properties": {
"{{ jss_signature }}": { "$ref": "#/$defs/JSSsignatures" }
}
}{%- endif %}
]
},
"unevaluatedProperties": false,

"$defs": {
"JSSsignatures": {
"type": "array",
"items": {
"type": "object",
"anyOf": [
{ "required": [ "public_key" ] },
{ "required": [ "public_cert_chain" ] },
{ "required": [ "cert_url" ] },
{ "required": [ "thumbprint" ] }
],
"required": [ "hash_algorithm", "algorithm", "value" ],
"properties": {
"id": { "$ref": "#/$defs/JSSidentifier" },
"type": { "const": "x590" },
"algorithm": { "type": "string" },
"cert_url": { "type": "string" },
"created": { "$ref": "#/$defs/JSStimestamp" },
"hash_algorithm": { "type": "string" },
"modified": { "$ref": "#/$defs/JSStimestamp" },
"public_cert_chain": {
"type": "array",
"items": {
"type": "string",
"contentEncoding": "base64url"
}
},
"public_key": {
"type": "string",
"contentEncoding": "base64url"
},
"related_to": { "type": "string" },
"related_version": { "type": "string" },
"revoked": {
"default": false,
"type": "boolean"
},
"signatures": { "$ref": "#/$defs/JSSsignatures" },
"signee": { "type": "string" },
"thumbprint": {
"type": "string",
"contentEncoding": "base64url"
},
"valid_from": { "$ref": "#/$defs/JSStimestamp" },
"valid_until": { "$ref": "#/$defs/JSStimestamp" },
"value": {
"type": "string",
"contentEncoding": "base64url"
}
}
}
},
"JSSidentifier": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
},
"JSStimestamp": {
"type": "string",
"pattern": "^(\\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])T([01]\\d|2[0-3]):([0-5]\\d):([0-5]\\d)(\\.\\d+)?Z$"
},
{%- for class in classes %}
{#- Classes are divided into 2 parts. The properties are separated into a separate object #}
{#- so that a object can references the properties of its parent without needing the const #}
Expand Down
46 changes: 35 additions & 11 deletions src/shacl2code/lang/templates/python/model.py.j2
Original file line number Diff line number Diff line change
Expand Up @@ -1275,19 +1275,27 @@ class SHACLObject(metaclass=SHACLObjectMeta):
if key in ("@id", self.ID_ALIAS):
return True

expanded_key = state.expand_iri(key)
if expanded_key and expanded_key in self._OBJ_IRI_PROPS:
p = self._OBJ_IRI_PROPS[expanded_key]
elif key in self._OBJ_IRI_PROPS:
p = self._OBJ_IRI_PROPS[key]
elif key in self._OBJ_COMPACT_PROPS:
p = self._OBJ_COMPACT_PROPS[key]
else:
return False

with decoder.read_property(key) as prop_d:
if prop_d is None:
raise TypeError(f"Property decoder for key '{key}' cannot be None")

if _JSS_SIGNATURE and key == _JSS_SIGNATURE:
if not prop_d.is_list():
raise TypeError(
f"Property '{key}' must be a list with signature information",
)
return True

expanded_key = state.expand_iri(key)
if expanded_key and expanded_key in self._OBJ_IRI_PROPS:
p = self._OBJ_IRI_PROPS[expanded_key]
elif key in self._OBJ_IRI_PROPS:
p = self._OBJ_IRI_PROPS[key]
elif key in self._OBJ_COMPACT_PROPS:
p = self._OBJ_COMPACT_PROPS[key]
else:
return False

v = p.prop.decode(prop_d, state)
self.__set(p, v)
return True
Expand Down Expand Up @@ -2215,9 +2223,23 @@ class JSONLDDeserializer(object):
if context_prop:
decode_context(context_prop, objectset)

if _JSS_SIGNATURE:
with h.read_property(_JSS_SIGNATURE) as signature_prop:
if signature_prop and not signature_prop.is_list():
raise ValueError(
f"Expected signature property '{_JSS_SIGNATURE}' to be an list, got {type(signature_prop.data)}"
)

state = DecodeState(objectset)
with h.read_property("@graph") as graph_prop:
objectset.decode(graph_prop if graph_prop else h, state)
if graph_prop:
for k in h.object_keys():
if k in {"@graph", "@context", _JSS_SIGNATURE}:
continue
raise KeyError(f"Unexpected property '{k}'")
objectset.decode(graph_prop, state)
else:
objectset.decode(h, state)

def read(self, f: BinaryIO, objectset: SHACLObjectSet) -> None:
"""Parse a JSON-LD file and deserialize its objects into the given object set."""
Expand Down Expand Up @@ -2979,6 +3001,8 @@ except ImportError:

# fmt: off
"""Format Guard{{ '"' }}{{ '"' }}{{ '"' }}
_JSS_SIGNATURE: Optional[str] = {% if jss_signature %}"{{ jss_signature }}"{% else %}None{% endif %}

CONTEXT_URLS: List[str] = [
{%- for url in context.urls %}
"{{ url }}",
Expand Down
38 changes: 37 additions & 1 deletion src/shacl2code/lang/templates/rust/lib.rs.j2
Original file line number Diff line number Diff line change
Expand Up @@ -1562,6 +1562,19 @@ fn decode_ref(
}
}

//{% if jss_signature %}
// ============================================================
// Check a JSS Signature
// ============================================================

fn check_jss_signature(v: &Value, path: &Path) -> Result<bool, Error> {
if v.is_array() {
return Ok(true);
}
return Err(Error::Decode(path.to_string(), "Signature value must be an array".to_string()));
}
//{% endif %}

// ============================================================
// Decode a SHACL object from JSON-LD
// ============================================================
Expand Down Expand Up @@ -1647,6 +1660,12 @@ fn decode_shacl_object(
if k == "@type" || k == "@context" {
continue;
}
{% if jss_signature %}
if k == "{{ jss_signature }}" {
check_jss_signature(v, &path.push_path(k))?;
continue;
}
{%- endif %}
match obj_mut.decode_property(k, v, path, state) {
Ok(true) => {}
_ => {
Expand Down Expand Up @@ -1687,6 +1706,12 @@ fn decode_shacl_object(
if k == "@type" || k == "@context" {
continue;
}
{% if jss_signature %}
if k == "{{ jss_signature }}" {
check_jss_signature(v, &path.push_path(k))?;
continue;
}
{%- endif %}
match obj_mut.decode_property(k, v, path, state) {
Ok(true) => {}
_ => {
Expand Down Expand Up @@ -1717,6 +1742,12 @@ fn decode_shacl_object(
if k == "@type" || k == "@context" {
continue;
}
{% if jss_signature %}
if k == "{{ jss_signature }}" {
check_jss_signature(v, &path.push_path(k))?;
continue;
}
{%- endif %}

let found = obj.as_shacl_object_mut().decode_property(k, v, path, state)?;
if !found {
Expand Down Expand Up @@ -1908,6 +1939,11 @@ impl SHACLObjectSet {
"Wrong context URL(s)".to_string(),
));
}
//{% if jss_signature %}
if let Some(signature_value) = dict.get("{{ jss_signature }}") {
check_jss_signature(signature_value, &path.push_path("{{ jss_signature }}"))?;
}
//{% endif %}

let state = DecodeState {
context: self.context.clone(),
Expand All @@ -1916,7 +1952,7 @@ impl SHACLObjectSet {
if let Some(graph_val) = dict.get("@graph") {
// Check for unknown root fields
for k in dict.keys() {
if k != "@context" && k != "@graph" {
if k != "@context" && k != "@graph" {% if jss_signature %}&& k != "{{ jss_signature }}"{% endif %} {
return Err(Error::Decode(
path.to_string(),
format!("Unknown property '{}'", k),
Expand Down
12 changes: 11 additions & 1 deletion src/shacl2code/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,12 @@ def handle_generate(parser, args):
data = json.load(f)
contexts.append(ContextData(data, url))

m = Model(graph, UrlContext(contexts), is_prerelease=args.pre_release)
m = Model(
graph,
UrlContext(contexts),
is_prerelease=args.pre_release,
jss_signature=args.jss_signature,
)

render = args.lang(args)
render.output(m)
Expand Down Expand Up @@ -125,6 +130,11 @@ def handle_version(parser, args):
action=argparse.BooleanOptionalAction,
help="Mark the generated binding as pre-release. Overrides any ontology annotations",
)
generate_parser.add_argument(
"--jss-signature",
metavar="NAME",
help="Allow ITU-T X.590 JSON Signature Schema (JSS) property with name 'NAME'. Recommended name is 'signatures'",
)
generate_parser.set_defaults(func=handle_generate)

lang_subparser = generate_parser.add_subparsers(
Expand Down
3 changes: 2 additions & 1 deletion src/shacl2code/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,14 @@ class Class:


class Model(object):
def __init__(self, graph, context=None, is_prerelease=None):
def __init__(self, graph, context=None, is_prerelease=None, jss_signature=None):
self.model = graph
self.context = context
self.compact_ids = {}
self.objects = {}
self.classes = []
self.ontologies = []
self.jss_signature = jss_signature
class_iris = set()
classes_by_iri = {}

Expand Down
Loading
Loading