From c659fe826f54319d93da32d4b11ae6c672df2a65 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Tue, 12 Mar 2019 21:08:12 -0500 Subject: [PATCH] If trying to import from IPFS, fallback to gateway --- .rubocop.yml | 3 ++ Gemfile | 1 + lib/dhall/ast.rb | 6 +++- lib/dhall/resolve.rb | 72 ++++++++++++++++++++++++++++++++++++++++++-- test/test_resolve.rb | 30 ++++++++++++++++++ 5 files changed, 108 insertions(+), 4 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 890bdce..2298bfa 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -67,6 +67,9 @@ Style/StringLiteralsInInterpolation: Style/SymbolArray: EnforcedStyle: brackets +Style/WordArray: + EnforcedStyle: brackets + Naming/UncommunicativeBlockParamName: Enabled: false diff --git a/Gemfile b/Gemfile index 83fe124..e384e6e 100644 --- a/Gemfile +++ b/Gemfile @@ -5,3 +5,4 @@ source "https://rubygems.org" gem "cbor" gem "promise.rb" gem "value_semantics" +gem "webmock" diff --git a/lib/dhall/ast.rb b/lib/dhall/ast.rb index d73af4b..2bb6d0e 100644 --- a/lib/dhall/ast.rb +++ b/lib/dhall/ast.rb @@ -572,7 +572,7 @@ module Dhall (uri.scheme == "https" ? Https : Http).new( nil, "#{uri.host}:#{uri.port}", - uri.path.split(/\//)[1..-1], + *uri.path.split(/\//)[1..-1], uri.query, nil ) @@ -636,6 +636,10 @@ module Dhall def pathname Pathname.new("/").join(*path) end + + def to_uri(scheme, authority) + scheme.new(nil, authority, *path, nil, nil) + end end class RelativePath < Path diff --git a/lib/dhall/resolve.rb b/lib/dhall/resolve.rb index 837e541..7b677ff 100644 --- a/lib/dhall/resolve.rb +++ b/lib/dhall/resolve.rb @@ -21,7 +21,9 @@ module Dhall ReadHttpSources = lambda do |sources| sources.map do |source| Promise.resolve(nil).then do - Net::HTTP.get(source.uri) + r = Net::HTTP.get_response(source.uri) + raise ImportFailedException, source if r.code != "200" + r.body end end end @@ -32,6 +34,50 @@ module Dhall end end + class ReadPathAndIPFSSources + def initialize( + path_reader: ReadPathSources, + http_reader: ReadHttpSources, + https_reader: http_reader, + public_gateway: "cloudflare-ipfs.com" + ) + @path_reader = path_reader + @http_reader = http_reader + @https_reader = https_reader + @public_gateway = public_gateway + end + + def call(sources) + @path_reader.call(sources).map.with_index do |promise, idx| + source = sources[idx] + if source.is_a?(Import::AbsolutePath) && + ["ipfs", "ipns"].include?(source.path.first) + gateway_fallback(source, promise) + else + promise + end + end + end + + def to_proc + method(:call).to_proc + end + + protected + + def gateway_fallback(source, promise) + promise.catch { + @http_reader.call([ + source.to_uri(Import::Http, "localhost:8000") + ]).first + }.catch do + @https_reader.call([ + source.to_uri(Import::Https, @public_gateway) + ]).first + end + end + end + class ResolutionSet attr_reader :reader @@ -66,7 +112,7 @@ module Dhall end end - class Default + class Standard def initialize( path_reader: ReadPathSources, http_reader: ReadHttpSources, @@ -111,7 +157,27 @@ module Dhall end end - class LocalOnly < Default + class Default < Standard + def initialize( + path_reader: ReadPathSources, + http_reader: ReadHttpSources, + https_reader: http_reader, + ipfs_public_gateway: "cloudflare-ipfs.com" + ) + super( + path_reader: ReadPathAndIPFSSources.new( + path_reader: path_reader, + http_reader: http_reader, + https_reader: https_reader, + public_gateway: ipfs_public_gateway + ), + http_reader: http_reader, + https_reader: https_reader + ) + end + end + + class LocalOnly < Standard def initialize(path_reader: ReadPathSources) super( path_reader: path_reader, diff --git a/test/test_resolve.rb b/test/test_resolve.rb index 080df84..8b16380 100644 --- a/test/test_resolve.rb +++ b/test/test_resolve.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "base64" +require "webmock/minitest" require "minitest/autorun" require "dhall/resolve" @@ -172,6 +173,35 @@ class TestResolve < Minitest::Test assert_equal Dhall::Variable["_"], expr.resolve(@resolver).sync end + def test_ipfs + stub_request(:get, "http://localhost:8000/ipfs/TESTCID") + .to_return(status: 200, body: "\x00") + + expr = Dhall::Import.new( + nil, + Dhall.method(:from_binary), + Dhall::Import::AbsolutePath.new("ipfs", "TESTCID") + ) + + assert_equal Dhall::Variable["_"], expr.resolve.sync + end + + def test_ipfs_public_gateway + stub_request(:get, "http://localhost:8000/ipfs/TESTCID") + .to_return(status: 500) + + stub_request(:get, "https://cloudflare-ipfs.com/ipfs/TESTCID") + .to_return(status: 200, body: "\x00") + + expr = Dhall::Import.new( + nil, + Dhall.method(:from_binary), + Dhall::Import::AbsolutePath.new("ipfs", "TESTCID") + ) + + assert_equal Dhall::Variable["_"], expr.resolve.sync + end + # Sanity check that all expressions can pass through the resolver Pathname.glob(TESTS + "**/*A.dhallb").each do |path| test = path.relative_path_from(TESTS).to_s.sub(/A\.dhallb$/, "") -- 2.38.5