From f01e02b0d44c9d23f71c301920e08d935580e65a Mon Sep 17 00:00:00 2001 From: Sven Fillinger Date: Thu, 22 Jan 2026 07:54:42 +0100 Subject: [PATCH 1/4] Level 2 examples --- doc/level2-linkset-discovery.md | 75 +++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 doc/level2-linkset-discovery.md diff --git a/doc/level2-linkset-discovery.md b/doc/level2-linkset-discovery.md new file mode 100644 index 0000000..dd31f45 --- /dev/null +++ b/doc/level2-linkset-discovery.md @@ -0,0 +1,75 @@ +# FAIR Signposting Level 2 Link Set discovery + +> [!NOTE] +> This example follows the specification of +> [FAIR Signposting Level 2](https://signposting.org/FAIR/#level2). +> Familiarity with Level 1 Signposting is recommended. + +FAIR Signposting Level 2 extends Level 1 by introducing **Link Sets** +([RFC 9264](https://www.rfc-editor.org/rfc/rfc9264.html)) to describe +relationships that cannot be expressed reliably using inline HTTP `Link` headers alone. + +Instead of embedding all relations directly in the response headers, +a resource may advertise one or more **external Link Set resources** +using the `rel="linkset"` relation. + +Compass supports **Level 2 discovery** by: +- detecting advertised Link Sets, +- parsing Link Set representations, +- validating their structure and semantics, +- and exposing their content in a machine-actionable way. + +Compass does **not** perform network requests — Link Sets must be retrieved +by client code and passed in explicitly. + +--- + +## Conceptual overview + +At Level 2, Signposting is split into two steps: + +1. **Discovery** + - Inline HTTP `Link` headers advertise one or more Link Sets using `rel="linkset"`. +2. **Interpretation** + - The Link Set documents contain the full Signposting graph + (Landing Pages, Content Resources, Metadata Resources, etc.). + +This separation allows: +- richer metadata descriptions, +- multiple origins per scholarly object, +- reuse of Signposting information across representations. + +--- + +## Step 1: Discovering Link Sets + +A Level 2-enabled resource advertises Link Sets via the HTTP `Link` header: + +```http +HTTP/1.1 200 OK +Link: ; rel="linkset" ; type="application/linkset+json" +``` + +Using Linksmith, the Link header is parsed into WebLink objects: + +```java +WebLinkProcessor processor = new WebLinkProcessor.Builder().build(); +ValidationResult parsingResult = processor.process(linkHeader); + +List webLinks = parsingResult.weblinks(); +``` + +Compass can now identify advertised Link Sets: +```java +SignPostingProcessor compass = new SignPostingProcessor.Builder().build(); +SignPostingResult result = compass.process(webLinks); + +SignPostingView view = result.signPostingView(); + +// Discover advertised Link Sets +List linksets = view.linkSet(); +``` +Each returned WebLink identifies: +- the Link Set URI (link.target()), +- its media type (link.type()), +- optional profile or anchor parameters. From 821199ae444e6ef7fd77fc41e6d77d0ec9acb5d3 Mon Sep 17 00:00:00 2001 From: Sven Fillinger Date: Thu, 22 Jan 2026 10:43:17 +0100 Subject: [PATCH 2/4] Provide Level 2 example --- doc/level2-linkset-discovery.md | 142 ++++++++++++++++++++++++++++++++ doc/linkset-example.json | 38 +++++++++ 2 files changed, 180 insertions(+) create mode 100644 doc/linkset-example.json diff --git a/doc/level2-linkset-discovery.md b/doc/level2-linkset-discovery.md index dd31f45..07685b8 100644 --- a/doc/level2-linkset-discovery.md +++ b/doc/level2-linkset-discovery.md @@ -14,6 +14,7 @@ a resource may advertise one or more **external Link Set resources** using the `rel="linkset"` relation. Compass supports **Level 2 discovery** by: + - detecting advertised Link Sets, - parsing Link Set representations, - validating their structure and semantics, @@ -35,6 +36,7 @@ At Level 2, Signposting is split into two steps: (Landing Pages, Content Resources, Metadata Resources, etc.). This separation allows: + - richer metadata descriptions, - multiple origins per scholarly object, - reuse of Signposting information across representations. @@ -60,6 +62,7 @@ List webLinks = parsingResult.weblinks(); ``` Compass can now identify advertised Link Sets: + ```java SignPostingProcessor compass = new SignPostingProcessor.Builder().build(); SignPostingResult result = compass.process(webLinks); @@ -68,8 +71,147 @@ SignPostingView view = result.signPostingView(); // Discover advertised Link Sets List linksets = view.linkSet(); +for( +WebLink linkset :linksets){ + System.out. + +println("Link Set URI: "+linkset.target()); + System.out. + +println("Media type : "+linkset.type(). + +orElse("")); + } ``` + Each returned WebLink identifies: + - the Link Set URI (link.target()), - its media type (link.type()), - optional profile or anchor parameters. + +At this point you have the Link Set URIs and media types, but **you still need to retrieve the Link +Set content**. + +> [!NOTE] +> Compass does not perform any network requests, so you have to query the Link Set URI yourself with +> a client of your choice. To follow through the example, an example Link Set encoded in JSON of +> MIME type ``application/linkset+json`` is available for you in [ +`doc/linkset-example.json`](linkset-example.json). + +--- + +## Step 2: Parsing an application/linkset+json Link Set (RFC 9264) + +RFC 9264 defines a JSON serialization for Link Sets using this general structure: + +- Top-level object contains a "linkset" array +- Each entry can specify an "anchor" URI +- Link relation types become JSON member names whose values are arrays of link objects +- Link objects at minimum contain "href", and may include link parameters like "type", "hreflang", " + title", etc.## + +--- + +## Example file: a minimal FAIR Signposting Level 2 Link Set (application/linkset+json) + +Save the following JSON as a file next to this documentation, for example: + +- [`doc/linkset-example.json`](linkset-example.json) + +
+Click to expand: linkset-example.json + +```json +{ + "linkset": [ + { + "anchor": "https://example.org/landing/123", + "cite-as": [ + { + "href": "https://doi.org/10.1234/example.123" + } + ], + "describedby": [ + { + "href": "https://example.org/metadata/123", + "type": "application/ld+json" + } + ], + "item": [ + { + "href": "https://example.org/content/123/article.pdf", + "type": "application/pdf" + }, + { + "href": "https://example.org/content/123/data.csv", + "type": "text/csv" + } + ], + "license": [ + { + "href": "https://creativecommons.org/licenses/by/4.0/" + } + ], + "type": [ + { + "href": "https://schema.org/Dataset" + } + ] + } + ] +} +``` +
+ +This Link Set contains: +- 1x Landing Page recipe anchor: https://example.org/landing/123 +- 1x Metadata Resource (rel="describedby") +- 2x Content Resources (rel="item") +- plus cite-as, license, and type links commonly used in Signposting graphs + +--- + +--- + +## Step 3: Validating a Link Set and accessing Level 2 views + +After parsing a Link Set document, Compass can validate its structure and semantics +using one or more **Level 2 validators**. + +> [!IMPORTANT] +> Not every Level 2 validator produces a `Level2LinksetView`. +> +> Only validators that *interpret complete Level 2 recipes* expose a semantic +> view of the Link Set contents. + +Currently, this is provided by: + +- `Level2RecipeValidator` + +Other Level 2 validators may: +- validate structural or normative constraints, +- report issues, +- or enforce profile-specific rules + **without** constructing a Link Set view. + +### Running a Level 2 recipe validator + +```java +LinkSetParser parser = new LinkSetJsonParser(); + +// Link Set content retrieved by client code (e.g., HTTP GET) +// String rawLinkSetJson = ... +List linksetLinks = parser.parse(rawLinkSetJson); + +// Run a Level 2 validator that produces a Level2LinksetView +SignPostingProcessor processor = new SignPostingProcessor.Builder() + .withValidators(Level2RecipeValidator.create()) + .build(); + +SignPostingResult result = processor.process(linksetLinks); + +// The Level 2 view is attached to the result (if produced by the validator) +Level2LinksetView view = Optional.ofNullable(result.level2LinksetView()) + .orElseThrow(() -> new IllegalStateException("No Level2LinksetView produced")); +``` diff --git a/doc/linkset-example.json b/doc/linkset-example.json new file mode 100644 index 0000000..047cc15 --- /dev/null +++ b/doc/linkset-example.json @@ -0,0 +1,38 @@ +{ + "linkset": [ + { + "anchor": "https://example.org/landing/123", + "cite-as": [ + { + "href": "https://doi.org/10.1234/example.123" + } + ], + "describedby": [ + { + "href": "https://example.org/metadata/123", + "type": "application/ld+json" + } + ], + "item": [ + { + "href": "https://example.org/content/123/article.pdf", + "type": "application/pdf" + }, + { + "href": "https://example.org/content/123/data.csv", + "type": "text/csv" + } + ], + "license": [ + { + "href": "https://creativecommons.org/licenses/by/4.0/" + } + ], + "type": [ + { + "href": "https://schema.org/Dataset" + } + ] + } + ] +} From 149b15b39f8d1eae89354df9e258b16207fb299f Mon Sep 17 00:00:00 2001 From: Sven Fillinger Date: Thu, 22 Jan 2026 10:44:41 +0100 Subject: [PATCH 3/4] Add small explanation paragragh --- doc/level2-linkset-discovery.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/doc/level2-linkset-discovery.md b/doc/level2-linkset-discovery.md index 07685b8..391004b 100644 --- a/doc/level2-linkset-discovery.md +++ b/doc/level2-linkset-discovery.md @@ -162,9 +162,11 @@ Save the following JSON as a file next to this documentation, for example: ] } ``` + This Link Set contains: + - 1x Landing Page recipe anchor: https://example.org/landing/123 - 1x Metadata Resource (rel="describedby") - 2x Content Resources (rel="item") @@ -190,6 +192,7 @@ Currently, this is provided by: - `Level2RecipeValidator` Other Level 2 validators may: + - validate structural or normative constraints, - report issues, - or enforce profile-specific rules @@ -215,3 +218,23 @@ SignPostingResult result = processor.process(linksetLinks); Level2LinksetView view = Optional.ofNullable(result.level2LinksetView()) .orElseThrow(() -> new IllegalStateException("No Level2LinksetView produced")); ``` + +This design allows Compass to support: + +- lightweight Level 2 validation without interpretation, +- multiple independent validation strategies, +- future extensions that introduce new view types or recipes. + +### Why the Link Set view is optional + +FAIR Signposting Level 2 covers multiple concerns: +- discovery of Link Sets, +- validation of Link Set structure, +- interpretation of complete Signposting recipes. + +Compass deliberately separates these concerns. +A `Level2LinksetView` is only constructed when a validator explicitly +chooses to interpret a full recipe. + +This avoids forcing all validators into a single representation +and allows clients to extract only the information they need. From 98eb9759c4647185c2eb5582f4f71dd5602fea06 Mon Sep 17 00:00:00 2001 From: Sven Fillinger Date: Thu, 22 Jan 2026 10:47:08 +0100 Subject: [PATCH 4/4] Reference discovery example in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f756e07..104ab1e 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ FAIR Signposting validation scenarios step by step: - FAIR Signposting **Level 1** validation: [`doc/level1-basic-validation.md`](doc/level1-basic-validation.md) -- FAIR Signposting **Level 2 discovery** using Link Sets (RFC 9264) +- FAIR Signposting **Level 2 discovery** using Link Sets (RFC 9264): [`doc/level2-discovery.md`](doc/level2-linkset-discovery.md) - Handling **multiple Link Sets** with different aggregation strategies