Browse the docs

silk.uri.percent.

Percent-encode bytes for a URI component, and decode %XX escapes.

When to use#

Use UriPercent.encodeOwned to put data, such as a search term or a file name, into a component of a URI. Use UriPercent.decodeOwned or UriPercent.decodeOrBorrow to get the bytes back. The ...Into, appendEncoded, and decodeInPlace functions reuse storage that you already have, and UriPercent.encodeToWriter writes to a Writer.

Details#

The PercentComponent selects which bytes stay as they are. All other bytes become %XX with uppercase hexadecimal digits. A % in the input is always encoded, so the input must be raw data, not text that is already encoded. Decoding is strict: each % must start a valid escape. The decoded bytes are not always valid UTF-8. Check them with String.fromUtf8 if you need text.

Errors#

  • DecodeError if the text has a % without two hexadecimal digits after it. offset is the position of the %.

  • OutOfMemoryError from the functions that allocate.

  • WriterError from UriPercent.encodeToWriter.

Gotchas#

This is generic URI encoding, not HTML form encoding. A + is not decoded to a space, and a space is encoded as %20. Query keeps &, =, and +, so use Unreserved to encode one key or value of a query string.

Examples#

Encode a query value and decode it again#

silk
import silk.mem.allocator { Allocator, OutOfMemoryError }
import silk.collections.bytes { Bytes }
import silk.core.effect { Effect }
import silk.core.result { Result }
import silk.text.string { String }
import silk.uri.percent { DecodeError, PercentComponent, UriPercent }

pub effect fn main() -> i32
  ! OutOfMemoryError {
  let mut allocator = Allocator.systemAllocatorProvider()
  let encoded = run UriPercent.encodeOwned(b"tea & cake", PercentComponent.Unreserved)
    |> Effect.provideMut<Allocator>(&mut allocator)
  if String.view(&encoded) != "tea%20%26%20cake" {
    return 1
  }
  let decoded = run UriPercent.decodeOwned(String.view(&encoded))
    |> Effect.provideMut<Allocator>(&mut allocator)
  return match move decoded {
    Result<Bytes, DecodeError>.Success { value } => {
      let bytes = Bytes.asSlice(&value)
      if bytes.length != 10 || bytes[4] != 38 {
        return 1
      }
      return 0
    }
    Result<Bytes, DecodeError>.Failure { error: _ } => 1
  }
}

Import as UriPercent with import silk.uri.percent { UriPercent }.

Public declarations: 4.

PercentComponent#

silk
pub enum PercentComponent

The URI component that the encoded bytes go into. It selects the bytes that stay as they are.

All components keep ASCII letters, digits, -, ., _, and ~. The other components also keep the sub-delimiters !$&'()*+,;=, except where the variant says otherwise.

Gotchas#

Query keeps &, =, and +, so use Unreserved to encode one key or value of a query string.

Userinfo#

silk
Userinfo

User information. It keeps : and encodes @.

RegName#

silk
RegName

A registered host name. It encodes : and @.

PathSegment#

silk
PathSegment

One path segment. It keeps : and @, and encodes /.

FirstPathSegment#

silk
FirstPathSegment

The first segment of a relative path. It encodes : and /, so the segment cannot look like a scheme.

Path#

silk
Path

A full path. It keeps /, :, and @, and encodes ? and #.

Unreserved#

silk
Unreserved

Only the unreserved bytes. It encodes all sub-delimiters. Use it for one key or value of a query string.

Query#

silk
Query

A full query. It keeps /, ?, :, @, &, and =.

Fragment#

silk
Fragment

A full fragment. It keeps /, ?, :, and @.

DecodeError#

silk
pub struct DecodeError

A % that does not start a valid escape.

Field offset#

silk
pub offset: usize

The position of the %, from the start of the input.

Implementation Debug for DecodeError#

silk
impl Debug for DecodeError

Operation debug#

silk
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut Writer

Writes the fields, as DecodeError { offset: ... }.

Decoded#

silk
pub union Decoded<'input>

The result of UriPercent.decodeOrBorrow: the input itself, or new decoded bytes.

The bytes are not always valid UTF-8.

Borrowed#

silk
Borrowed

The input has no %, so it is returned as it is. Nothing is allocated.

Field values#

silk
pub values: &'input [u8]

The input bytes.

Owned#

silk
Owned

The input has escapes. The decoded bytes are in new storage.

Field value#

silk
pub value: Bytes

The decoded bytes.

UriPercent#

silk
pub struct UriPercent

Functions that percent-encode and decode URI components.

Details#

The PercentComponent selects which bytes stay as they are. All other bytes become %XX with uppercase hexadecimal digits. A % in the input is always encoded, so the input must be raw data, not text that is already encoded. Decoding is strict: each % must start a valid escape. The decoded bytes are not always valid UTF-8.

Gotchas#

This is generic URI encoding, not HTML form encoding. A + is not decoded to a space, and a space is encoded as %20.

Implementation UriPercent#

silk
impl UriPercent

Associated function encodeOwned#

silk
pub effect fn encodeOwned(values: &[u8], component: PercentComponent) -> String
  ! OutOfMemoryError
  ? &mut Allocator

Returns values encoded for component, in a new string.

It allocates exactly the needed size, and nothing for an empty result.

Errors#

  • OutOfMemoryError if the Allocator fails or the size does not fit in usize.

Gotchas#

Path keeps :. If the path of a relative reference can start with a segment that contains :, encode that segment with FirstPathSegment.

Associated function encodeInto#

silk
pub effect fn encodeInto(output: &mut Bytes, values: &[u8], component: PercentComponent) -> ()
  ! OutOfMemoryError
  ? &mut Allocator

Replaces the contents of output with values encoded for component.

If output has enough capacity, it does not allocate.

Errors#

  • OutOfMemoryError if the Allocator fails or the size does not fit in usize. output does not change.

Associated function encodedLength#

silk
pub fn encodedLength(values: &[u8], component: PercentComponent) -> Option<usize>

Returns the length of values encoded for component, or None if it does not fit in usize.

Each encoded byte counts as 3. Use it to reserve space for a URI that you build from more than one component.

Associated function appendEncoded#

silk
pub effect fn appendEncoded(output: &mut Bytes, values: &[u8], component: PercentComponent) -> ()
  ! OutOfMemoryError
  ? &mut Allocator

Adds values encoded for component to the end of output.

The existing bytes of output do not change. If output has enough capacity, it does not allocate.

Errors#

  • OutOfMemoryError if the Allocator fails or the size does not fit in usize. output does not change.

Associated function encodeToWriter#

silk
pub effect fn encodeToWriter(values: &[u8], component: PercentComponent) -> ()
  ! WriterError
  ? &mut Writer

Writes values encoded for component to the ambient Writer. It does not allocate.

Errors#

  • WriterError if the Writer fails. The bytes that it accepted stay written.

Gotchas#

It does not flush the Writer.

Associated function decodeOwned#

silk
pub effect fn decodeOwned(text: string) -> Result<Bytes, DecodeError>
  ! OutOfMemoryError
  ? &mut Allocator

Returns the bytes of text with each %XX escape decoded, in new storage.

A + stays a +. It allocates exactly the needed size, and nothing for an empty result.

Errors#

  • Failure with a DecodeError if an escape is not valid. Nothing is allocated.

  • OutOfMemoryError if the Allocator fails.

Associated function decodeInto#

silk
pub effect fn decodeInto(output: &mut Bytes, text: string) -> Result<(), DecodeError>
  ! OutOfMemoryError
  ? &mut Allocator

Replaces the contents of output with the decoded bytes of text.

A + stays a +. If output has enough capacity, it does not allocate.

Errors#

  • Failure with a DecodeError if an escape is not valid. output does not change.

  • OutOfMemoryError if the Allocator fails. output does not change.

Associated function decodeInPlace#

silk
pub fn decodeInPlace(values: &mut [u8]) -> Result<usize, DecodeError>

Decodes the escapes of values in place, and returns the length of the decoded bytes.

The decoded bytes are at the start of values. It does not allocate.

Errors#

  • Failure with a DecodeError if an escape is not valid. values does not change.

Gotchas#

The bytes after the returned length are not specified. The length of the slice does not change.

Associated function decodeOrBorrow#

silk
pub effect fn decodeOrBorrow<'input>(text: string<'input>) -> Result<Decoded<'input>, DecodeError>
  ! OutOfMemoryError
  ? &mut Allocator

Returns text as it is if it has no %, or else the decoded bytes in new storage.

Use it when most input has no escapes, to skip the allocation and the copy.

Errors#

  • Failure with a DecodeError if an escape is not valid. Nothing is allocated.

  • OutOfMemoryError if the Allocator fails.

Category: Data formats.

Profiles: aarch64-apple-darwin, aarch64-unknown-linux-gnu, aarch64-unknown-linux-gnu-no-libc, x86_64-unknown-linux-gnu, x86_64-unknown-linux-gnu-no-libc, wasm32-unknown-unknown, wasm32-wasip1, wasm32-wasip2.

View the source of this page