diff --git a/app/controllers/index_controller.rb b/app/controllers/index_controller.rb index 52b457384..e2600d3f8 100644 --- a/app/controllers/index_controller.rb +++ b/app/controllers/index_controller.rb @@ -14,7 +14,7 @@ def show respond_to do |format| format.html do # forward to URL registered in handle system for no content negotiation - redirect_to doi.url, status: :see_other + redirect_to doi.url, status: :see_other, allow_other_host: true end format.citation do # extract optional style and locale from header @@ -37,8 +37,10 @@ def show :datacite, :datacite_json, :jats, + :rdf_xml, :ris, :schema_org, + :turtle, ) { render request.format.to_sym => doi } header = %w[ doi diff --git a/app/models/doi.rb b/app/models/doi.rb index 3b87366f0..cf5888e2e 100644 --- a/app/models/doi.rb +++ b/app/models/doi.rb @@ -2826,6 +2826,29 @@ def handle_resource_type(types) end end + # Ensure Bolognese metadata is populated from stored DataCite XML before + # calling Bolognese RDF writers. This is local-only (no network calls). + def ensure_bolognese_meta! + return meta if meta.present? + return meta if xml.blank? + + @meta = parse_xml(xml, doi: doi) + end + + def rdf_xml + ensure_bolognese_meta! + raise ActionController::UnknownFormat, "RDF representation is not available for this DOI" if graph.nil? + + super + end + + def turtle + ensure_bolognese_meta! + raise ActionController::UnknownFormat, "RDF representation is not available for this DOI" if graph.nil? + + super + end + private def update_publisher_from_hash symbolized_publisher_hash = publisher_before_type_cast.symbolize_keys diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb index d4efacfcf..17135a77f 100644 --- a/config/initializers/mime_types.rb +++ b/config/initializers/mime_types.rb @@ -35,6 +35,8 @@ Mime::Type.register "application/x-bibtex", :bibtex Mime::Type.register "application/x-research-info-systems", :ris Mime::Type.register "text/x-bibliography", :citation +Mime::Type.register "application/rdf+xml", :rdf_xml +Mime::Type.register "text/turtle", :turtle, %w[application/x-turtle] # register renderers for these Mime types # :citation and :datacite is handled differently @@ -83,3 +85,11 @@ ActionController::Renderers.add :csv do |obj, options| options[:header].to_csv + Array.wrap(obj).map { |o| o.send("csv") }.join("") end + +ActionController::Renderers.add :rdf_xml do |obj, _options| + Array.wrap(obj).map { |o| o.rdf_xml }.join("\n") +end + +ActionController::Renderers.add :turtle do |obj, _options| + Array.wrap(obj).map { |o| o.turtle }.join("\n") +end diff --git a/config/routes.rb b/config/routes.rb index 083987e2e..784e98dc7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -53,6 +53,12 @@ to: "index#show", constraints: { id: /.+/ }, defaults: { format: :bibtex } get "/application/x-research-info-systems/:id", to: "index#show", constraints: { id: /.+/ }, defaults: { format: :ris } + get "/application/rdf+xml/:id", + to: "index#show", constraints: { id: /.+/ }, defaults: { format: :rdf_xml } + get "/application/x-turtle/:id", + to: "index#show", constraints: { id: /.+/ }, defaults: { format: :turtle } + get "/text/turtle/:id", + to: "index#show", constraints: { id: /.+/ }, defaults: { format: :turtle } get "/text/csv/:id", to: "index#show", constraints: { id: /.+/ }, defaults: { format: :csv } get "/text/x-bibliography/:id", diff --git a/spec/factories/doi.rb b/spec/factories/doi.rb index 870ffc72b..bed743622 100644 --- a/spec/factories/doi.rb +++ b/spec/factories/doi.rb @@ -27,6 +27,11 @@ "lang": "en", } end + + trait :with_datacite_xml do + xml { file_fixture("datacite.xml").read } + end + creators do [ { diff --git a/spec/requests/index_spec.rb b/spec/requests/index_spec.rb index f39f3e11b..efa24c5af 100644 --- a/spec/requests/index_spec.rb +++ b/spec/requests/index_spec.rb @@ -4,6 +4,7 @@ describe IndexController, type: :request do let(:doi) { create(:doi, aasm_state: "findable") } + let(:doi_with_xml) { create(:doi, :with_datacite_xml, aasm_state: "findable") } describe "content_negotation" do context "application/vnd.jats+xml" do @@ -337,5 +338,102 @@ expect(last_response.headers["Location"]).to eq(doi.url) end end + + context "wildcard Accept: */*" do + it "redirects without 500" do + get "/#{doi.doi}", nil, { "HTTP_ACCEPT" => "*/*" } + + expect(last_response.status).not_to eq(500) + expect([200, 302, 303]).to include(last_response.status) + end + end + + context "application/rdf+xml" do + it "returns the Doi as RDF/XML" do + get "/#{doi_with_xml.doi}", nil, { "HTTP_ACCEPT" => "application/rdf+xml" } + + expect(last_response.status).to eq(200) + expect(last_response.headers["Content-Type"]).to include("application/rdf+xml") + expect(last_response.body).to include("rdf:RDF") + end + + it "returns 406 when RDF representation is not available" do + get "/#{doi.doi}", nil, { "HTTP_ACCEPT" => "application/rdf+xml" } + + expect(last_response.status).to eq(406) + expect(json["errors"]).to eq( + [ + { + "status" => "406", + "title" => "RDF representation is not available for this DOI", + }, + ], + ) + end + end + + context "application/rdf+xml link" do + it "returns the Doi as RDF/XML" do + get "/application/rdf+xml/#{doi_with_xml.doi}" + + expect(last_response.status).to eq(200) + expect(last_response.headers["Content-Type"]).to include("application/rdf+xml") + expect(last_response.body).to include("rdf:RDF") + end + end + + context "text/turtle" do + it "returns the Doi as Turtle" do + get "/#{doi_with_xml.doi}", nil, { "HTTP_ACCEPT" => "text/turtle" } + + expect(last_response.status).to eq(200) + expect(last_response.headers["Content-Type"]).to include("text/turtle") + expect(last_response.body).to include("@prefix schema:") + end + + it "returns 406 when RDF representation is not available" do + get "/#{doi.doi}", nil, { "HTTP_ACCEPT" => "text/turtle" } + + expect(last_response.status).to eq(406) + expect(json["errors"]).to eq( + [ + { + "status" => "406", + "title" => "RDF representation is not available for this DOI", + }, + ], + ) + end + end + + context "application/x-turtle" do + it "returns the Doi as Turtle" do + get "/#{doi_with_xml.doi}", nil, { "HTTP_ACCEPT" => "application/x-turtle" } + + expect(last_response.status).to eq(200) + expect(last_response.headers["Content-Type"]).to include("turtle") + expect(last_response.body).to include("@prefix schema:") + end + end + + context "text/turtle link" do + it "returns the Doi as Turtle" do + get "/text/turtle/#{doi_with_xml.doi}" + + expect(last_response.status).to eq(200) + expect(last_response.headers["Content-Type"]).to include("text/turtle") + expect(last_response.body).to include("@prefix schema:") + end + end + + context "application/x-turtle link" do + it "returns the Doi as Turtle" do + get "/application/x-turtle/#{doi_with_xml.doi}" + + expect(last_response.status).to eq(200) + expect(last_response.headers["Content-Type"]).to include("turtle") + expect(last_response.body).to include("@prefix schema:") + end + end end end