diff --git a/compiler/test/stdlib/uri.test.gr b/compiler/test/stdlib/uri.test.gr index 64c8abc8d..f417aacb4 100644 --- a/compiler/test/stdlib/uri.test.gr +++ b/compiler/test/stdlib/uri.test.gr @@ -281,23 +281,23 @@ testResolve("a://a.com?a#a", "", "a://a.com?a") // make -assert Uri.make(scheme=Some("+"), percentEncodeComponents=false) == +assert Uri.make(scheme=Some("+"), encodeComponents=false) == Err(Uri.InvalidSchemeError) assert Uri.make( userinfo=Some("%"), host=Some("a"), - percentEncodeComponents=false + encodeComponents=false ) == Err(Uri.InvalidUserinfoError) -assert Uri.make(host=Some("#"), percentEncodeComponents=false) == +assert Uri.make(host=Some("#"), encodeComponents=false) == Err(Uri.InvalidHostError) -assert Uri.make(port=Some(-1), host=Some("a"), percentEncodeComponents=false) == +assert Uri.make(port=Some(-1), host=Some("a"), encodeComponents=false) == Err(Uri.InvalidPortError) -assert Uri.make(path="%2", percentEncodeComponents=false) == +assert Uri.make(path="%2", encodeComponents=false) == Err(Uri.InvalidPathError) -assert Uri.make(query=Some("#"), percentEncodeComponents=false) == +assert Uri.make(query=Some("#"), encodeComponents=false) == Err(Uri.InvalidQueryError) -assert Uri.make(fragment=Some("%"), percentEncodeComponents=false) == +assert Uri.make(fragment=Some("%"), encodeComponents=false) == Err(Uri.InvalidFragmentError) assert Uri.make(userinfo=Some("me")) == Err(Uri.UserinfoWithNoHost) assert Uri.make(port=Some(80)) == Err(Uri.PortWithNoHost) @@ -324,7 +324,7 @@ assert Result.map( path="/%20d:o'c#s!", query=Some("/a?b#c=d:ef"), fragment=Some("Ur#i-m/ake"), - percentEncodeComponents=true + encodeComponents=true ) ) == Ok( @@ -335,7 +335,7 @@ assert Result.map( Uri.make( scheme=Some("http"), host=Some("[1::1]"), - percentEncodeComponents=true + encodeComponents=true ) ) == Ok("http://[1::1]") @@ -343,29 +343,29 @@ assert Result.map( // update let orig = Result.unwrap(Uri.make()) -assert Uri.update(orig, scheme=Some(Some("+")), percentEncodeComponents=false) == +assert Uri.update(orig, scheme=Some(Some("+")), encodeComponents=false) == Err(Uri.InvalidSchemeError) assert Uri.update( orig, userinfo=Some(Some("%")), host=Some(Some("a")), - percentEncodeComponents=false + encodeComponents=false ) == Err(Uri.InvalidUserinfoError) -assert Uri.update(orig, host=Some(Some("#")), percentEncodeComponents=false) == +assert Uri.update(orig, host=Some(Some("#")), encodeComponents=false) == Err(Uri.InvalidHostError) assert Uri.update( orig, port=Some(Some(1.1)), host=Some(Some("a")), - percentEncodeComponents=false + encodeComponents=false ) == Err(Uri.InvalidPortError) -assert Uri.update(orig, path=Some("%2"), percentEncodeComponents=false) == +assert Uri.update(orig, path=Some("%2"), encodeComponents=false) == Err(Uri.InvalidPathError) -assert Uri.update(orig, query=Some(Some("#")), percentEncodeComponents=false) == +assert Uri.update(orig, query=Some(Some("#")), encodeComponents=false) == Err(Uri.InvalidQueryError) -assert Uri.update(orig, fragment=Some(Some("%")), percentEncodeComponents=false) == +assert Uri.update(orig, fragment=Some(Some("%")), encodeComponents=false) == Err(Uri.InvalidFragmentError) assert Uri.update(orig, port=Some(Some(80))) == Err(Uri.PortWithNoHost) @@ -398,7 +398,7 @@ assert Result.map( path=Some("/%20d:o'c#s!"), query=Some(Some("/a?b#c=d:ef")), fragment=Some(Some("Ur#i-m/ake")), - percentEncodeComponents=true + encodeComponents=true ) ) == Ok( @@ -406,7 +406,7 @@ assert Result.map( ) assert Result.map( Uri.toString, - Uri.update(orig, host=Some(Some("[1::1]")), percentEncodeComponents=true) + Uri.update(orig, host=Some(Some("[1::1]")), encodeComponents=true) ) == Ok("https://me:pw@[1::1]:80/docs?k=v#frag") @@ -420,7 +420,7 @@ let decoded = "🌾" assert Uri.decode(encoded) == Ok(decoded) assert Uri.encode(decoded) == encoded -assert Uri.decode("%2") == Err(Uri.InvalidPercentEncoding) +assert Uri.decode("%2") == Err(Uri.InvalidEncoding) // encodeQuery/decodeQuery @@ -429,4 +429,4 @@ let decoded = [("val", "🌾"), ("val🧱2", "x=y&a=b")] assert Uri.encodeQuery(decoded) == encoded assert Uri.decodeQuery(encoded) == Ok(decoded) -assert Uri.decodeQuery("%2") == Err(Uri.InvalidPercentEncoding) +assert Uri.decodeQuery("%2") == Err(Uri.InvalidEncoding) diff --git a/stdlib/uri.gr b/stdlib/uri.gr index 5c8713bfb..089d2dc6b 100644 --- a/stdlib/uri.gr +++ b/stdlib/uri.gr @@ -7,17 +7,16 @@ */ module Uri -from "string" include String -from "char" include Char -from "uint8" include Uint8 -from "number" include Number -from "bytes" include Bytes +from "array" include Array from "buffer" include Buffer +from "bytes" include Bytes +from "char" include Char from "list" include List -from "array" include Array -from "map" include Map +from "number" include Number from "option" include Option from "result" include Result +from "string" include String +from "uint8" include Uint8 /** * Represents a parsed RFC 3986 URI. @@ -64,14 +63,14 @@ provide enum ResolveReferenceError { /** * Represents an error encountered while attempting to percent-decode a string. */ -provide enum PercentDecodingError { - InvalidPercentEncoding, +provide enum DecodingError { + InvalidEncoding, } /** * Used to specify which characters to percent-encode from a string. */ -provide enum PercentEncodeSet { +provide enum EncodeSet { EncodeNonUnreserved, EncodeUserinfo, EncodeRegisteredHost, @@ -108,7 +107,7 @@ let isPchar = char => { isUnreservedChar(char) || isSubDelim(char) || char == ':' || char == '@' } -let makePercentEncoder = (encodeSet: PercentEncodeSet) => { +let makeEncoder = (encodeSet: EncodeSet) => { let shouldEncodeForNonUnreserved = char => !isUnreservedChar(char) let shouldEncodeForUserinfo = char => { @@ -159,18 +158,18 @@ let hexValueToChar = val => { } } -let percentDecodeValid = (str, onlyUnreserved=false) => { +let decodeValid = (str, onlyUnreserved=false) => { let bytes = String.encode(str, String.UTF8) let len = Bytes.length(bytes) let out = Buffer.make(len) - let cAt = i => Char.fromCode(Uint8.toNumber(Bytes.getUint8(i, bytes))) + let charAt = i => Char.fromCode(Uint8.toNumber(Bytes.getUint8(i, bytes))) for (let mut i = 0; i < len; i += 1) { - if (i >= len - 2 || cAt(i) != '%') { + if (i >= len - 2 || charAt(i) != '%') { let byte = Bytes.getUint8(i, bytes) Buffer.addUint8(byte, out) } else { - let next = cAt(i + 1) - let nextNext = cAt(i + 2) + let next = charAt(i + 1) + let nextNext = charAt(i + 2) let pctDecodedVal = charToHexValue(next) * 16 + charToHexValue(nextNext) if (onlyUnreserved && !isUnreservedChar(Char.fromCode(pctDecodedVal))) { Buffer.addChar('%', out) @@ -185,7 +184,7 @@ let percentDecodeValid = (str, onlyUnreserved=false) => { Buffer.toString(out) } -let isValidPercentEncoding = str => { +let isValidEncoding = str => { let chars = String.explode(str) let len = Array.length(chars) for (let mut i = 0; i < len; i += 1) { @@ -201,7 +200,7 @@ let isValidPercentEncoding = str => { // Lowercase all non-percent-encoded alphabetical characters let normalizeHost = str => { - let str = percentDecodeValid(str, onlyUnreserved=true) + let str = decodeValid(str, onlyUnreserved=true) let chars = String.explode(str) let rec getChars = (i, acc) => { @@ -259,7 +258,7 @@ let removeDotSegments = path => { } /** - * Percent-encodes characters in a string based on the specified `PercentEncodeSet`. + * Percent-encodes characters in a string based on the specified `EncodeSet`. * * @param str: The string to encode * @param encodeSet: An indication for which characters to percent-encode. `EncodeNonUnreserved` by default @@ -272,7 +271,8 @@ let removeDotSegments = path => { * @since v0.6.0 */ provide let encode = (str, encodeSet=EncodeNonUnreserved) => { - let shouldEncode = makePercentEncoder(encodeSet) + let shouldEncode = makeEncoder(encodeSet) + // TODO(#2053): use String.map when implemented let chars = String.explode(str) let rec getChars = (i, acc) => { if (i < 0) { @@ -308,10 +308,10 @@ provide let encode = (str, encodeSet=EncodeNonUnreserved) => { * @since v0.6.0 */ provide let decode = str => { - if (!isValidPercentEncoding(str)) { - Err(InvalidPercentEncoding) + if (isValidEncoding(str)) { + Ok(decodeValid(str)) } else { - Ok(percentDecodeValid(str)) + Err(InvalidEncoding) } } @@ -342,9 +342,7 @@ provide let encodeQuery = (urlVals, encodeSet=EncodeNonUnreserved) => { * @since v0.6.0 */ provide let decodeQuery = str => { - if (!isValidPercentEncoding(str)) { - Err(InvalidPercentEncoding) - } else { + if (isValidEncoding(str)) { let parts = Array.toList(String.split("&", str)) Ok(List.map(part => { match (String.indexOf("=", part)) { @@ -353,10 +351,12 @@ provide let decodeQuery = str => { Some(i) => { let name = String.slice(0, end=i, part) let val = String.slice(i + 1, part) - (percentDecodeValid(name), percentDecodeValid(val)) + (decodeValid(name), decodeValid(val)) }, } }, parts)) + } else { + Err(InvalidEncoding) } } @@ -625,14 +625,14 @@ let parsePath = (i, str, isAbsolute, hasAuthority) => { if (hasAuthority) { let endI = Option.unwrap(pathAbempty(i, str)) let path = processPath( - percentDecodeValid(String.slice(i, end=endI, str), onlyUnreserved=true) + decodeValid(String.slice(i, end=endI, str), onlyUnreserved=true) ) (endI, path) } else { let extraOption = if (isAbsolute) pathRootless else pathNoScheme let endI = Option.unwrap(any([pathAbsolute, extraOption, empty])(i, str)) let path = processPath( - percentDecodeValid(String.slice(i, end=endI, str), onlyUnreserved=true) + decodeValid(String.slice(i, end=endI, str), onlyUnreserved=true) ) (endI, path) } @@ -665,7 +665,7 @@ let parseQuery = (i, str, withDelim=false) => { ( endI, Some( - percentDecodeValid( + decodeValid( String.slice(i + (if (withDelim) 1 else 0), end=endI, str), onlyUnreserved=true ), @@ -682,7 +682,7 @@ let parseFragment = (i, str, withDelim=false) => { ( endI, Some( - percentDecodeValid( + decodeValid( String.slice(i + (if (withDelim) 1 else 0), end=endI, str), onlyUnreserved=true ), @@ -784,11 +784,11 @@ provide let resolveReference = (base, ref) => { * @param path: The desired path for the URI. `""` by default * @param query: `Some(query)` containing the desired query string component or `None` for a query-less URI * @param fragment: `Some(fragment)` containing the desired fragment component or `None` for a fragment-less URI - * @param percentEncodeComponents: Whether or not to apply percent encoding for each component to remove unsafe characters for each component + * @param encodeComponents: Whether or not to apply percent encoding for each component to remove unsafe characters for each component * * @example Uri.make(scheme=Some("https"), host=Some("grain-lang.org")) // https://grain-lang.org - * @example Uri.make(host=Some("g/r@in"), percentEncodeComponents=false) // Err(Uri.InvalidHostError) - * @example Uri.make(scheme=Some("abc"), host=Some("g/r@in"), query=Some("k/ey=v^@l"), percentEncodeComponents=true) // abc://g%2Fr%40in?k/ey=v%5E@l + * @example Uri.make(host=Some("g/r@in"), encodeComponents=false) // Err(Uri.InvalidHostError) + * @example Uri.make(scheme=Some("abc"), host=Some("g/r@in"), query=Some("k/ey=v^@l"), encodeComponents=true) // abc://g%2Fr%40in?k/ey=v%5E@l * @example Uri.make(port=Some(80)) // Err(Uri.PortWithNoHost) * * @since v0.6.0 @@ -801,7 +801,7 @@ provide let make = ( path="", query=None, fragment=None, - percentEncodeComponents=false, + encodeComponents=false, ) => { match ((host, userinfo, port)) { (None, Some(_), None) => return Err(UserinfoWithNoHost), @@ -833,7 +833,7 @@ provide let make = ( } } - let (userinfo, host, path, query, fragment) = if (percentEncodeComponents) { + let (userinfo, host, path, query, fragment) = if (encodeComponents) { let encodeOption = (val, encodeSet) => Option.map(val => encode(val, encodeSet=encodeSet), val) @@ -915,12 +915,12 @@ enum UpdateAction { * @param path: `Some(path)` containing the desired updated path component or `None` to maintain the base URI's path * @param query: `Some(query)` containing the desired updated query string component or `None` to maintain the base URI's query * @param fragment: `Some(fragment)` containing the desired updated fragment component or `None` to maintain the base URI's fragment - * @param percentEncodeComponents: Whether or not to apply percent encoding for each updated component to remove unsafe characters + * @param encodeComponents: Whether or not to apply percent encoding for each updated component to remove unsafe characters * * @example let uri = Result.unwrap(Uri.parse("https://grain-lang.org/docs?k=v")) // Base URI for following examples * @example Uri.update(uri, scheme=Some(Some("ftp"))) // ftp://grain-lang.org/docs?k=v * @example Uri.update(uri, query=Some(None)) // https://grain-lang.org/docs - * @example Uri.update(uri, host=Some(Some("g/r@in")), percentEncodeComponents=true) // https://g%2Fr%40in/docs?k=v + * @example Uri.update(uri, host=Some(Some("g/r@in")), encodeComponents=true) // https://g%2Fr%40in/docs?k=v * @example Uri.update(uri, host=Some(None), port=Some(Some(80))) // Err(Uri.PortWithNoHost) * * @since v0.6.0 @@ -934,7 +934,7 @@ provide let update = ( path=None, query=None, fragment=None, - percentEncodeComponents=false, + encodeComponents=false, ) => { let (??) = (new, old) => Option.unwrapWithDefault(old, new) match ((host ?? uri.host, userinfo ?? uri.userinfo, port ?? uri.port)) { @@ -972,7 +972,7 @@ provide let update = ( } } - let (userinfo, host, path, query, fragment) = if (percentEncodeComponents) { + let (userinfo, host, path, query, fragment) = if (encodeComponents) { let encodeOption = (val, encodeSet) => match (val) { Some(Some(val)) => Some(Some(encode(val, encodeSet=encodeSet))), val => val,