Avoid network request to DFC context on export

This protects us from the DFC website going down or the DFC updating
the context with breaking changes. We are in control of updating the
context now (opt-in to newer versions).
This commit is contained in:
Maikel Linke
2023-07-21 16:19:35 +10:00
parent fdb80fe87c
commit 3b5b9ec54d
9 changed files with 447 additions and 16 deletions

View File

@@ -8,7 +8,7 @@ module DfcProvider
def index
person = PersonBuilder.person(current_user)
render json: DfcLoader.connector.export(
render json: DfcIo.export(
person,
*person.affiliatedOrganizations,
*person.affiliatedOrganizations.flat_map(&:catalogItems),
@@ -20,7 +20,7 @@ module DfcProvider
def show
catalog_item = DfcBuilder.catalog_item(variant)
offers = catalog_item.offers
render json: DfcLoader.connector.export(catalog_item, *offers)
render json: DfcIo.export(catalog_item, *offers)
end
def update

View File

@@ -7,7 +7,7 @@ module DfcProvider
def show
enterprise = EnterpriseBuilder.enterprise(current_enterprise)
render json: DfcLoader.connector.export(
render json: DfcIo.export(
enterprise,
*enterprise.suppliedProducts,
*enterprise.catalogItems,

View File

@@ -7,7 +7,7 @@ module DfcProvider
def show
person = PersonBuilder.person(user)
render json: DfcLoader.connector.export(person)
render json: DfcIo.export(person)
end
private

View File

@@ -23,12 +23,12 @@ module DfcProvider
supplied_product = SuppliedProductBuilder.supplied_product(
product.variants.first
)
render json: DfcLoader.connector.export(supplied_product)
render json: DfcIo.export(supplied_product)
end
def show
product = SuppliedProductBuilder.supplied_product(variant)
render json: DfcLoader.connector.export(product)
render json: DfcIo.export(product)
end
def update

View File

@@ -0,0 +1,60 @@
# frozen_string_literal: true
# Our interface to the DFC Connector library.
module DfcIo
CONTEXT = JSON.parse <<~JSON
{
"rdfs": "http://www.w3.org/2000/01/rdf-schema#",
"skos" : "http://www.w3.org/2004/02/skos/core#",
"dfc": "http://static.datafoodconsortium.org/ontologies/DFC_FullModel.owl#",
"dc": "http://purl.org/dc/elements/1.1/#",
"dfc-b": "http://static.datafoodconsortium.org/ontologies/DFC_BusinessOntology.owl#",
"dfc-p": "http://static.datafoodconsortium.org/ontologies/DFC_ProductOntology.owl#",
"dfc-t": "http://static.datafoodconsortium.org/ontologies/DFC_TechnicalOntology.owl#",
"dfc-m": "http://static.datafoodconsortium.org/data/measures.rdf#",
"dfc-pt": "http://static.datafoodconsortium.org/data/productTypes.rdf#",
"dfc-f": "http://static.datafoodconsortium.org/data/facets.rdf#",
"dfc-p:hasUnit":{ "@type":"@id" },
"dfc-b:hasUnit":{ "@type":"@id" },
"dfc-b:hasQuantity":{ "@type":"@id" },
"dfc-p:hasType":{ "@type":"@id" },
"dfc-b:hasType":{ "@type":"@id" },
"dfc-b:references":{ "@type":"@id" },
"dfc-b:referencedBy":{ "@type":"@id" },
"dfc-b:offeres":{ "@type":"@id" },
"dfc-b:supplies":{ "@type":"@id" },
"dfc-b:defines":{ "@type":"@id" },
"dfc-b:affiliates":{ "@type":"@id" },
"dfc-b:hasQuantity":{ "@type":"@id" },
"dfc-b:manages":{ "@type":"@id" },
"dfc-b:offeredThrough":{ "@type":"@id" },
"dfc-b:hasBrand":{ "@type":"@id" },
"dfc-b:hasGeographicalOrigin":{ "@type":"@id" },
"dfc-b:hasClaim":{ "@type":"@id" },
"dfc-b:hasAllergenDimension":{ "@type":"@id" },
"dfc-b:hasNutrimentDimension":{ "@type":"@id" },
"dfc-b:hasPhysicalDimension":{ "@type":"@id" },
"dfc:owner":{ "@type":"@id" },
"dfc-t:hostedBy":{ "@type":"@id" },
"dfc-t:hasPivot":{ "@type":"@id" },
"dfc-t:represent":{ "@type":"@id" }
}
JSON
# Serialise DFC Connector subjects as JSON-LD string.
#
# This is a re-implementation of the Connector.export to provide our own context.
def self.export(*subjects)
return "" if subjects.empty?
serializer = VirtualAssembly::Semantizer::HashSerializer.new(CONTEXT)
hashes = subjects.map do |subject|
# JSON::LD needs a context on every input using prefixes.
subject.serialize(serializer).merge("@context" => CONTEXT)
end
json_ld = JSON::LD::API.compact(hashes, CONTEXT)
JSON.generate(json_ld)
end
end

View File

@@ -0,0 +1,37 @@
# frozen_string_literal: true
require DfcProvider::Engine.root.join("spec/spec_helper")
describe DfcIo do
let(:person) do
DataFoodConsortium::Connector::Person.new("Pete")
end
let(:enterprise) do
DataFoodConsortium::Connector::Enterprise.new("Pete's Pumpkins")
end
describe ".export" do
it "exports nothing" do
expect(DfcIo.export).to eq ""
end
it "embeds the context" do
json = DfcIo.export(person)
result = JSON.parse(json)
expect(result["@context"]).to be_a Hash
end
it "uses the context to shorten URIs" do
person.affiliatedOrganizations << enterprise
json = DfcIo.export(person, enterprise)
result = JSON.parse(json)
expect(result["@graph"].count).to eq 2
expect(result["@graph"].first.keys).to include(
*%w(@id @type dfc-b:affiliates)
)
end
end
end

View File

@@ -4,8 +4,6 @@ require DfcProvider::Engine.root.join("spec/spec_helper")
describe DfcLoader do
it "prepares the DFC Connector to provide DFC object classes for export" do
connector = DfcLoader.connector
tomato = DataFoodConsortium::Connector::SuppliedProduct.new(
"https://openfoodnetwork.org/tomato",
name: "Tomato",
@@ -15,7 +13,7 @@ describe DfcLoader do
expect(tomato.name).to eq "Tomato"
expect(tomato.description).to eq "Awesome tomato"
json = connector.export(tomato)
json = DfcIo.export(tomato)
result = JSON.parse(json)
expect(result.keys).to include(

View File

@@ -91,7 +91,7 @@ RSpec.configure do |config|
expectations.syntax = :expect
end
config.around(:each) do |example|
config.around(:each, vcr: true) do |example|
# The DFC Connector fetches the context when loaded.
VCR.use_cassette("dfc-context") do
example.run

View File

@@ -57,7 +57,63 @@ paths:
examples:
test_example:
value:
"@context": http://static.datafoodconsortium.org/ontologies/context.json
"@context":
rdfs: http://www.w3.org/2000/01/rdf-schema#
skos: http://www.w3.org/2004/02/skos/core#
dfc: http://static.datafoodconsortium.org/ontologies/DFC_FullModel.owl#
dc: http://purl.org/dc/elements/1.1/#
dfc-b: http://static.datafoodconsortium.org/ontologies/DFC_BusinessOntology.owl#
dfc-p: http://static.datafoodconsortium.org/ontologies/DFC_ProductOntology.owl#
dfc-t: http://static.datafoodconsortium.org/ontologies/DFC_TechnicalOntology.owl#
dfc-m: http://static.datafoodconsortium.org/data/measures.rdf#
dfc-pt: http://static.datafoodconsortium.org/data/productTypes.rdf#
dfc-f: http://static.datafoodconsortium.org/data/facets.rdf#
dfc-p:hasUnit:
"@type": "@id"
dfc-b:hasUnit:
"@type": "@id"
dfc-b:hasQuantity:
"@type": "@id"
dfc-p:hasType:
"@type": "@id"
dfc-b:hasType:
"@type": "@id"
dfc-b:references:
"@type": "@id"
dfc-b:referencedBy:
"@type": "@id"
dfc-b:offeres:
"@type": "@id"
dfc-b:supplies:
"@type": "@id"
dfc-b:defines:
"@type": "@id"
dfc-b:affiliates:
"@type": "@id"
dfc-b:manages:
"@type": "@id"
dfc-b:offeredThrough:
"@type": "@id"
dfc-b:hasBrand:
"@type": "@id"
dfc-b:hasGeographicalOrigin:
"@type": "@id"
dfc-b:hasClaim:
"@type": "@id"
dfc-b:hasAllergenDimension:
"@type": "@id"
dfc-b:hasNutrimentDimension:
"@type": "@id"
dfc-b:hasPhysicalDimension:
"@type": "@id"
dfc:owner:
"@type": "@id"
dfc-t:hostedBy:
"@type": "@id"
dfc-t:hasPivot:
"@type": "@id"
dfc-t:represent:
"@type": "@id"
"@graph":
- "@id": http://test.host/api/dfc-v1.7/persons/12345
"@type": dfc-b:Person
@@ -116,7 +172,63 @@ paths:
examples:
test_example:
value:
"@context": http://static.datafoodconsortium.org/ontologies/context.json
"@context":
rdfs: http://www.w3.org/2000/01/rdf-schema#
skos: http://www.w3.org/2004/02/skos/core#
dfc: http://static.datafoodconsortium.org/ontologies/DFC_FullModel.owl#
dc: http://purl.org/dc/elements/1.1/#
dfc-b: http://static.datafoodconsortium.org/ontologies/DFC_BusinessOntology.owl#
dfc-p: http://static.datafoodconsortium.org/ontologies/DFC_ProductOntology.owl#
dfc-t: http://static.datafoodconsortium.org/ontologies/DFC_TechnicalOntology.owl#
dfc-m: http://static.datafoodconsortium.org/data/measures.rdf#
dfc-pt: http://static.datafoodconsortium.org/data/productTypes.rdf#
dfc-f: http://static.datafoodconsortium.org/data/facets.rdf#
dfc-p:hasUnit:
"@type": "@id"
dfc-b:hasUnit:
"@type": "@id"
dfc-b:hasQuantity:
"@type": "@id"
dfc-p:hasType:
"@type": "@id"
dfc-b:hasType:
"@type": "@id"
dfc-b:references:
"@type": "@id"
dfc-b:referencedBy:
"@type": "@id"
dfc-b:offeres:
"@type": "@id"
dfc-b:supplies:
"@type": "@id"
dfc-b:defines:
"@type": "@id"
dfc-b:affiliates:
"@type": "@id"
dfc-b:manages:
"@type": "@id"
dfc-b:offeredThrough:
"@type": "@id"
dfc-b:hasBrand:
"@type": "@id"
dfc-b:hasGeographicalOrigin:
"@type": "@id"
dfc-b:hasClaim:
"@type": "@id"
dfc-b:hasAllergenDimension:
"@type": "@id"
dfc-b:hasNutrimentDimension:
"@type": "@id"
dfc-b:hasPhysicalDimension:
"@type": "@id"
dfc:owner:
"@type": "@id"
dfc-t:hostedBy:
"@type": "@id"
dfc-t:hasPivot:
"@type": "@id"
dfc-t:represent:
"@type": "@id"
"@graph":
- "@id": http://test.host/api/dfc-v1.7/enterprises/10000/catalog_items/10001
"@type": dfc-b:CatalogItem
@@ -217,7 +329,63 @@ paths:
examples:
test_example:
value:
"@context": http://static.datafoodconsortium.org/ontologies/context.json
"@context":
rdfs: http://www.w3.org/2000/01/rdf-schema#
skos: http://www.w3.org/2004/02/skos/core#
dfc: http://static.datafoodconsortium.org/ontologies/DFC_FullModel.owl#
dc: http://purl.org/dc/elements/1.1/#
dfc-b: http://static.datafoodconsortium.org/ontologies/DFC_BusinessOntology.owl#
dfc-p: http://static.datafoodconsortium.org/ontologies/DFC_ProductOntology.owl#
dfc-t: http://static.datafoodconsortium.org/ontologies/DFC_TechnicalOntology.owl#
dfc-m: http://static.datafoodconsortium.org/data/measures.rdf#
dfc-pt: http://static.datafoodconsortium.org/data/productTypes.rdf#
dfc-f: http://static.datafoodconsortium.org/data/facets.rdf#
dfc-p:hasUnit:
"@type": "@id"
dfc-b:hasUnit:
"@type": "@id"
dfc-b:hasQuantity:
"@type": "@id"
dfc-p:hasType:
"@type": "@id"
dfc-b:hasType:
"@type": "@id"
dfc-b:references:
"@type": "@id"
dfc-b:referencedBy:
"@type": "@id"
dfc-b:offeres:
"@type": "@id"
dfc-b:supplies:
"@type": "@id"
dfc-b:defines:
"@type": "@id"
dfc-b:affiliates:
"@type": "@id"
dfc-b:manages:
"@type": "@id"
dfc-b:offeredThrough:
"@type": "@id"
dfc-b:hasBrand:
"@type": "@id"
dfc-b:hasGeographicalOrigin:
"@type": "@id"
dfc-b:hasClaim:
"@type": "@id"
dfc-b:hasAllergenDimension:
"@type": "@id"
dfc-b:hasNutrimentDimension:
"@type": "@id"
dfc-b:hasPhysicalDimension:
"@type": "@id"
dfc:owner:
"@type": "@id"
dfc-t:hostedBy:
"@type": "@id"
dfc-t:hasPivot:
"@type": "@id"
dfc-t:represent:
"@type": "@id"
"@graph":
- "@id": http://test.host/api/dfc-v1.7/enterprises/10000
"@type": dfc-b:Enterprise
@@ -264,7 +432,63 @@ paths:
examples:
test_example:
value:
"@context": http://static.datafoodconsortium.org/ontologies/context.json
"@context":
rdfs: http://www.w3.org/2000/01/rdf-schema#
skos: http://www.w3.org/2004/02/skos/core#
dfc: http://static.datafoodconsortium.org/ontologies/DFC_FullModel.owl#
dc: http://purl.org/dc/elements/1.1/#
dfc-b: http://static.datafoodconsortium.org/ontologies/DFC_BusinessOntology.owl#
dfc-p: http://static.datafoodconsortium.org/ontologies/DFC_ProductOntology.owl#
dfc-t: http://static.datafoodconsortium.org/ontologies/DFC_TechnicalOntology.owl#
dfc-m: http://static.datafoodconsortium.org/data/measures.rdf#
dfc-pt: http://static.datafoodconsortium.org/data/productTypes.rdf#
dfc-f: http://static.datafoodconsortium.org/data/facets.rdf#
dfc-p:hasUnit:
"@type": "@id"
dfc-b:hasUnit:
"@type": "@id"
dfc-b:hasQuantity:
"@type": "@id"
dfc-p:hasType:
"@type": "@id"
dfc-b:hasType:
"@type": "@id"
dfc-b:references:
"@type": "@id"
dfc-b:referencedBy:
"@type": "@id"
dfc-b:offeres:
"@type": "@id"
dfc-b:supplies:
"@type": "@id"
dfc-b:defines:
"@type": "@id"
dfc-b:affiliates:
"@type": "@id"
dfc-b:manages:
"@type": "@id"
dfc-b:offeredThrough:
"@type": "@id"
dfc-b:hasBrand:
"@type": "@id"
dfc-b:hasGeographicalOrigin:
"@type": "@id"
dfc-b:hasClaim:
"@type": "@id"
dfc-b:hasAllergenDimension:
"@type": "@id"
dfc-b:hasNutrimentDimension:
"@type": "@id"
dfc-b:hasPhysicalDimension:
"@type": "@id"
dfc:owner:
"@type": "@id"
dfc-t:hostedBy:
"@type": "@id"
dfc-t:hasPivot:
"@type": "@id"
dfc-t:represent:
"@type": "@id"
"@id": http://test.host/api/dfc-v1.7/persons/10000
"@type": dfc-b:Person
'404':
@@ -289,7 +513,63 @@ paths:
examples:
test_example:
value:
"@context": http://static.datafoodconsortium.org/ontologies/context.json
"@context":
rdfs: http://www.w3.org/2000/01/rdf-schema#
skos: http://www.w3.org/2004/02/skos/core#
dfc: http://static.datafoodconsortium.org/ontologies/DFC_FullModel.owl#
dc: http://purl.org/dc/elements/1.1/#
dfc-b: http://static.datafoodconsortium.org/ontologies/DFC_BusinessOntology.owl#
dfc-p: http://static.datafoodconsortium.org/ontologies/DFC_ProductOntology.owl#
dfc-t: http://static.datafoodconsortium.org/ontologies/DFC_TechnicalOntology.owl#
dfc-m: http://static.datafoodconsortium.org/data/measures.rdf#
dfc-pt: http://static.datafoodconsortium.org/data/productTypes.rdf#
dfc-f: http://static.datafoodconsortium.org/data/facets.rdf#
dfc-p:hasUnit:
"@type": "@id"
dfc-b:hasUnit:
"@type": "@id"
dfc-b:hasQuantity:
"@type": "@id"
dfc-p:hasType:
"@type": "@id"
dfc-b:hasType:
"@type": "@id"
dfc-b:references:
"@type": "@id"
dfc-b:referencedBy:
"@type": "@id"
dfc-b:offeres:
"@type": "@id"
dfc-b:supplies:
"@type": "@id"
dfc-b:defines:
"@type": "@id"
dfc-b:affiliates:
"@type": "@id"
dfc-b:manages:
"@type": "@id"
dfc-b:offeredThrough:
"@type": "@id"
dfc-b:hasBrand:
"@type": "@id"
dfc-b:hasGeographicalOrigin:
"@type": "@id"
dfc-b:hasClaim:
"@type": "@id"
dfc-b:hasAllergenDimension:
"@type": "@id"
dfc-b:hasNutrimentDimension:
"@type": "@id"
dfc-b:hasPhysicalDimension:
"@type": "@id"
dfc:owner:
"@type": "@id"
dfc-t:hostedBy:
"@type": "@id"
dfc-t:hasPivot:
"@type": "@id"
dfc-t:represent:
"@type": "@id"
"@id": http://test.host/api/dfc-v1.7/enterprises/10000/supplied_products/10001
"@type": dfc-b:SuppliedProduct
dfc-b:name: Apple
@@ -347,7 +627,63 @@ paths:
examples:
test_example:
value:
"@context": http://static.datafoodconsortium.org/ontologies/context.json
"@context":
rdfs: http://www.w3.org/2000/01/rdf-schema#
skos: http://www.w3.org/2004/02/skos/core#
dfc: http://static.datafoodconsortium.org/ontologies/DFC_FullModel.owl#
dc: http://purl.org/dc/elements/1.1/#
dfc-b: http://static.datafoodconsortium.org/ontologies/DFC_BusinessOntology.owl#
dfc-p: http://static.datafoodconsortium.org/ontologies/DFC_ProductOntology.owl#
dfc-t: http://static.datafoodconsortium.org/ontologies/DFC_TechnicalOntology.owl#
dfc-m: http://static.datafoodconsortium.org/data/measures.rdf#
dfc-pt: http://static.datafoodconsortium.org/data/productTypes.rdf#
dfc-f: http://static.datafoodconsortium.org/data/facets.rdf#
dfc-p:hasUnit:
"@type": "@id"
dfc-b:hasUnit:
"@type": "@id"
dfc-b:hasQuantity:
"@type": "@id"
dfc-p:hasType:
"@type": "@id"
dfc-b:hasType:
"@type": "@id"
dfc-b:references:
"@type": "@id"
dfc-b:referencedBy:
"@type": "@id"
dfc-b:offeres:
"@type": "@id"
dfc-b:supplies:
"@type": "@id"
dfc-b:defines:
"@type": "@id"
dfc-b:affiliates:
"@type": "@id"
dfc-b:manages:
"@type": "@id"
dfc-b:offeredThrough:
"@type": "@id"
dfc-b:hasBrand:
"@type": "@id"
dfc-b:hasGeographicalOrigin:
"@type": "@id"
dfc-b:hasClaim:
"@type": "@id"
dfc-b:hasAllergenDimension:
"@type": "@id"
dfc-b:hasNutrimentDimension:
"@type": "@id"
dfc-b:hasPhysicalDimension:
"@type": "@id"
dfc:owner:
"@type": "@id"
dfc-t:hostedBy:
"@type": "@id"
dfc-t:hasPivot:
"@type": "@id"
dfc-t:represent:
"@type": "@id"
"@id": http://test.host/api/dfc-v1.7/enterprises/10000/supplied_products/10001
"@type": dfc-b:SuppliedProduct
dfc-b:name: Pesto