1 /** 2 * Authors: Tomoya Tanjo 3 * Copyright: © 2021 Tomoya Tanjo 4 * License: Apache-2.0 5 */ 6 module salad.resolver; 7 8 import dyaml : Node, NodeType; 9 10 import salad.context : LoadingContext; 11 12 /** 13 * See_Also: https://www.commonwl.org/v1.2/SchemaSalad.html#Link_resolution 14 */ 15 auto resolveLink(string link, in LoadingContext context) nothrow pure @safe 16 { 17 import std.algorithm : canFind, endsWith, findSplitAfter, findSplitBefore, startsWith; 18 19 auto pathPortionOf(string uri) 20 { 21 if (auto split = uri.findSplitBefore("#")) 22 { 23 return split[0]; 24 } 25 else 26 { 27 return uri; 28 } 29 } 30 31 if (link.startsWith("#")) 32 { 33 // rerlative fragment identifier 34 return pathPortionOf(context.baseURI)~link; 35 } 36 else if (auto split = link.findSplitBefore(":")) 37 { 38 import std.algorithm : canFind; 39 40 if (auto ns = split[0] in context.namespaces) 41 { 42 // resolved with namespaces 43 return *ns ~ split[1][1..$]; 44 } 45 else if (link.canFind("://")) 46 { 47 // assumption: an absolute URI contains "://" 48 return link; 49 } 50 else 51 { 52 // unresolved link 53 return link; 54 } 55 } 56 else 57 { 58 // path relative reference 59 string pathPortionOfRefURI = pathPortionOf(link); 60 string pathPortionOfBaseURI = pathPortionOf(context.baseURI); 61 if (pathPortionOfBaseURI.endsWith("/")) 62 { 63 return context.baseURI ~ pathPortionOfRefURI; 64 } 65 else 66 { 67 import std.path : buildPath, dirName; 68 return context.baseURI.dirName.buildPath(link); 69 } 70 } 71 } 72 73 /** 74 * See_Also: https://www.commonwl.org/v1.2/SchemaSalad.html#Link_resolution_example 75 */ 76 unittest 77 { 78 auto context = LoadingContext( 79 "http://example.com/base", 80 [ 81 "acid": "http://example.com/acid#", 82 ] 83 ); 84 85 enum zero = "http://example.com/base/zero"; 86 assert(zero.resolveLink(context) == zero); 87 88 assert("one".resolveLink(context) == "http://example.com/one"); 89 assert("two".resolveLink(context) == "http://example.com/two"); 90 assert("#three".resolveLink(context) == "http://example.com/base#three"); 91 assert("four#five".resolveLink(context) == "http://example.com/four#five"); 92 assert("acid:six".resolveLink(context) == "http://example.com/acid#six"); 93 } 94 95 /** 96 * See_Also: https://www.commonwl.org/v1.2/SchemaSalad.html#Import 97 * See_Also: https://www.commonwl.org/v1.2/SchemaSalad.html#Include 98 */ 99 auto preprocess(in Node node, in LoadingContext context) 100 { 101 import salad.fetcher : fetchNode, fetchText; 102 103 if (node.type != NodeType.mapping) 104 { 105 return node; 106 } 107 else if (auto link = "$import" in node) 108 { 109 auto resolved = resolveLink(link.as!string, context); 110 return resolved.fetchNode; 111 } 112 else if (auto link = "$include" in node) 113 { 114 auto resolved = resolveLink(link.as!string, context); 115 return Node(resolved.fetchText); 116 } 117 else 118 { 119 return node; 120 } 121 } 122 123 /** 124 * See_Also: https://www.commonwl.org/v1.2/SchemaSalad.html#Import_example 125 */ 126 unittest 127 { 128 import dyaml : Loader; 129 import std.path : absolutePath; 130 131 auto context = LoadingContext( 132 "file://"~"examples/import/parent.json".absolutePath, 133 ); 134 135 enum str = q"EOS 136 "bar": { 137 "$import": "import.json" 138 } 139 EOS"; 140 141 auto node = Loader.fromString(str).load; 142 auto processed = node["bar"].preprocess(context); 143 assert("hello" in processed); 144 assert(processed["hello"] == "world"); 145 } 146 147 /** 148 * See_Also: https://www.commonwl.org/v1.2/SchemaSalad.html#Include_example 149 */ 150 unittest 151 { 152 import dyaml : Loader; 153 import std.path : absolutePath; 154 155 auto context = LoadingContext( 156 "file://"~"examples/include/parent.json".absolutePath, 157 ); 158 159 enum str = q"EOS 160 "bar": { 161 "$include": "include.txt" 162 } 163 EOS"; 164 165 auto node = Loader.fromString(str).load; 166 auto processed = node["bar"].preprocess(context); 167 assert(processed == "hello world"); 168 }