diff --git a/invenio_rdm_records/resources/serializers/__init__.py b/invenio_rdm_records/resources/serializers/__init__.py index 3b67a347a..ccd9d6264 100644 --- a/invenio_rdm_records/resources/serializers/__init__.py +++ b/invenio_rdm_records/resources/serializers/__init__.py @@ -34,7 +34,10 @@ ) from .marcxml import MARCXMLSerializer from .schemaorg import SchemaorgJSONLDSerializer -from .signposting import FAIRSignpostingProfileLvl2Serializer +from .signposting import ( + FAIRSignpostingProfileLvl1Serializer, + FAIRSignpostingProfileLvl2Serializer, +) from .ui import UIJSONSerializer __all__ = ( @@ -47,6 +50,7 @@ "DataPackageSerializer", "DublinCoreJSONSerializer", "DublinCoreXMLSerializer", + "FAIRSignpostingProfileLvl1Serializer", "FAIRSignpostingProfileLvl2Serializer", "GeoJSONSerializer", "IIIFCanvasV2JSONSerializer", diff --git a/invenio_rdm_records/resources/serializers/signposting/__init__.py b/invenio_rdm_records/resources/serializers/signposting/__init__.py index 910686b85..a6216b970 100644 --- a/invenio_rdm_records/resources/serializers/signposting/__init__.py +++ b/invenio_rdm_records/resources/serializers/signposting/__init__.py @@ -8,9 +8,35 @@ """Signposting serializers.""" from flask_resources import BaseListSchema, MarshmallowSerializer -from flask_resources.serializers import JSONSerializer +from flask_resources.serializers import JSONSerializer, SimpleSerializer -from .schema import FAIRSignpostingProfileLvl2Schema +from .schema import FAIRSignpostingProfileLvl2Schema, LandingPageLvl1Schema + + +class FAIRSignpostingProfileLvl1Serializer(MarshmallowSerializer): + """FAIR Signposting Profile level 1 serializer.""" + + def __init__(self): + """Initialise Serializer.""" + super().__init__( + format_serializer_cls=SimpleSerializer, + object_schema_cls=LandingPageLvl1Schema, + list_schema_cls=BaseListSchema, + encoder=self.fair_signposting_tostring, + ) + + @classmethod + def fair_signposting_tostring(cls, record): + """Stringify a FAIR Signposting record.""" + links = [] + for rel, values in record.items(): + # if rel not in excluded_keys: + for value in values: + link = f'<{value["href"]}> ; rel="{rel}"' + if "type" in value: + link += f' ; type="{value["type"]}"' + links.append(link) + return " , ".join(links) class FAIRSignpostingProfileLvl2Serializer(MarshmallowSerializer): diff --git a/invenio_rdm_records/resources/serializers/signposting/schema.py b/invenio_rdm_records/resources/serializers/signposting/schema.py index 47909c582..32eebd6d7 100644 --- a/invenio_rdm_records/resources/serializers/signposting/schema.py +++ b/invenio_rdm_records/resources/serializers/signposting/schema.py @@ -20,7 +20,6 @@ class LandingPageSchema(Schema): Serialization input (`obj`) is a whole record dict projection. """ - anchor = fields.Method(serialize="serialize_anchor") author = fields.Method(serialize="serialize_author") cite_as = fields.Method(data_key="cite-as", serialize="serialize_cite_as") describedby = fields.Method(serialize="serialize_describedby") @@ -28,10 +27,6 @@ class LandingPageSchema(Schema): license = fields.Method(serialize="serialize_license") type = fields.Method(serialize="serialize_type") - def serialize_anchor(self, obj, **kwargs): - """Seralize to landing page URL.""" - return obj["links"]["self_html"] - def serialize_author(self, obj, **kwargs): """Serialize author(s). @@ -144,6 +139,37 @@ def serialize_type(self, obj, **kwargs): return result +class LandingPageLvl1Schema(LandingPageSchema): + """Schema for serialization of link context object for the landing page. + + Serialization input (`obj`) is a whole record dict projection. + """ + + linkset = fields.Method(serialize="serialize_linkset") + + def serialize_linkset(self, obj, **kwargs): + """Serialize the linkset URL.""" + return [ + { + "href": obj["links"]["self"], + "type": "application/linkset+json", + } + ] + + +class LandingPageLvl2Schema(LandingPageSchema): + """Schema for serialization of link context object for the landing page. + + Serialization input (`obj`) is a whole record dict projection. + """ + + anchor = fields.Method(serialize="serialize_anchor") + + def serialize_anchor(self, obj, **kwargs): + """Serialize to landing page URL.""" + return obj["links"]["self_html"] + + class ContentResourceSchema(Schema): """Schema for serialization of link context object for the content resource. @@ -205,7 +231,7 @@ class FAIRSignpostingProfileLvl2Schema(Schema): def serialize_linkset(self, obj, **kwargs): """Serialize linkset.""" - result = [LandingPageSchema().dump(obj)] + result = [LandingPageLvl2Schema().dump(obj)] content_resource_schema = ContentResourceSchema(context={"record_dict": obj}) result += [ diff --git a/tests/resources/serializers/test_signposting_serializer.py b/tests/resources/serializers/test_signposting_serializer.py index 9e6467bca..d1a0e14d2 100644 --- a/tests/resources/serializers/test_signposting_serializer.py +++ b/tests/resources/serializers/test_signposting_serializer.py @@ -8,6 +8,7 @@ """Resources serializers tests.""" from invenio_rdm_records.resources.serializers import ( + FAIRSignpostingProfileLvl1Serializer, FAIRSignpostingProfileLvl2Serializer, ) @@ -125,6 +126,43 @@ def test_signposting_serializer_full(running_app, full_record_to_dict): assert expected == serialized +def test_signposting_lvl1_serializer_full(running_app, full_record_to_dict): + ui_url = "https://127.0.0.1:5000/records/12345-abcde" + api_url = "https://127.0.0.1:5000/api/records/12345-abcde" + filename = "test.txt" + + expected = [ + f' ; rel="author"', + f' ; rel="cite-as"', + f'<{api_url}> ; rel="describedby" ; type="application/dcat+xml"', + f'<{api_url}> ; rel="describedby" ; type="application/json"', + f'<{api_url}> ; rel="describedby" ; type="application/ld+json"', + f'<{api_url}> ; rel="describedby" ; type="application/marcxml+xml"', + f'<{api_url}> ; rel="describedby" ; type="application/vnd.citationstyles.csl+json"', + f'<{api_url}> ; rel="describedby" ; type="application/vnd.datacite.datacite+json"', + f'<{api_url}> ; rel="describedby" ; type="application/vnd.datacite.datacite+xml"', + f'<{api_url}> ; rel="describedby" ; type="application/vnd.geo+json"', + f'<{api_url}> ; rel="describedby" ; type="application/vnd.inveniordm.v1+json"', + f'<{api_url}> ; rel="describedby" ; type="application/vnd.inveniordm.v1.full+csv"', + f'<{api_url}> ; rel="describedby" ; type="application/vnd.inveniordm.v1.simple+csv"', + f'<{api_url}> ; rel="describedby" ; type="application/x-bibtex"', + f'<{api_url}> ; rel="describedby" ; type="application/x-dc+xml"', + f'<{api_url}> ; rel="describedby" ; type="text/x-bibliography"', + f'<{ui_url}/files/{filename}> ; rel="item" ; type="text/plain"', + ' ; rel="license"', + ' ; rel="license"', + ' ; rel="type"', + ' ; rel="type"', + f'<{api_url}> ; rel="linkset" ; type="application/linkset+json"', + ] + + serialized = FAIRSignpostingProfileLvl1Serializer().serialize_object( + full_record_to_dict + ) + + assert expected == serialized.split(" , ") + + def test_signposting_serializer_minimal(running_app, minimal_record_to_dict): expected = { "linkset": [ @@ -218,3 +256,37 @@ def test_signposting_serializer_minimal(running_app, minimal_record_to_dict): serialized = FAIRSignpostingProfileLvl2Serializer().dump_obj(minimal_record_to_dict) assert expected == serialized + + +def test_signposting_lvl1_serializer_minimal(running_app, minimal_record_to_dict): + api_url = "https://127.0.0.1:5000/api/records/67890-fghij" + + expected = [ + # No author since no associated PID + # No cite-as since no DOI + f'<{api_url}> ; rel="describedby" ; type="application/dcat+xml"', + f'<{api_url}> ; rel="describedby" ; type="application/json"', + f'<{api_url}> ; rel="describedby" ; type="application/ld+json"', + f'<{api_url}> ; rel="describedby" ; type="application/marcxml+xml"', + f'<{api_url}> ; rel="describedby" ; type="application/vnd.citationstyles.csl+json"', + f'<{api_url}> ; rel="describedby" ; type="application/vnd.datacite.datacite+json"', + f'<{api_url}> ; rel="describedby" ; type="application/vnd.datacite.datacite+xml"', + f'<{api_url}> ; rel="describedby" ; type="application/vnd.geo+json"', + f'<{api_url}> ; rel="describedby" ; type="application/vnd.inveniordm.v1+json"', + f'<{api_url}> ; rel="describedby" ; type="application/vnd.inveniordm.v1.full+csv"', + f'<{api_url}> ; rel="describedby" ; type="application/vnd.inveniordm.v1.simple+csv"', + f'<{api_url}> ; rel="describedby" ; type="application/x-bibtex"', + f'<{api_url}> ; rel="describedby" ; type="application/x-dc+xml"', + f'<{api_url}> ; rel="describedby" ; type="text/x-bibliography"', + # No files + # No license + ' ; rel="type"', + ' ; rel="type"', + f'<{api_url}> ; rel="linkset" ; type="application/linkset+json"', + ] + + serialized = FAIRSignpostingProfileLvl1Serializer().serialize_object( + minimal_record_to_dict + ) + + assert expected == serialized.split(" , ")