Generalized Parsing: Part 2

Episode #125 • Nov 16, 2020 • Subscriber-Only

Now that we have generalized the parser type it’s time to parse things that aren’t just plain strings. In just a few lines of code the parser type can parse environment variable dictionaries and even become a fully capable URL router.

Part 2
Introduction
00:05
Parsing dictionaries
00:43
Parsing URL requests: defining a parsable type
12:18
Parsing URL requests: defining parsers
21:28
Optional parsers
34:21
Next time: what’s the point?
49:55

Unlock This Episode

Our Free plan includes 1 subscriber-only episode of your choice, plus weekly updates from our newsletter.

Introduction

Now although it is a little annoying that we have to sprinkle a little bit of extra syntax to let the compiler know exactly what type it is working with, it is also forcing a very good pattern on us. The prefix parser only works for collections whose SubSequence is equal to itself. Such collections are able to mutate themselves in an efficient manner because they operate on views of a shared storage rather than make needless copies all over the place.

This is why we are forced to provide array slices and substrings as input to these parsers, because it allows them to do their work in the most efficient way possible.

This episode is for subscribers only.

Subscribe to Point-Free

Access this episode, plus all past and future episodes when you become a subscriber.

See plans and pricing

Already a subscriber? Log in

Exercises

  1. Write a function that transforms a URLRequest into the parser-friendly RequestData type.

    Solution
    extension RequestData {
      init(urlRequest: URLRequest) {
        self.body = urlRequest.httpBody
        self.headers = urlRequest.allHTTPHeaderFields?.mapValues { $0[...] } ?? [:]
        self.method = urlRequest.httpMethod
        self.pathComponents = urlRequest.url?.path.split(separator: "/")[...] ?? []
        self.queryItems = urlRequest.url
          .flatMap {
            URLComponents(url: $0, resolvingAgainstBaseURL: false)?.queryItems?
              .map { ($0.name, $0.value?[...] ?? "") }
          }
          ?? []
      }
    }
    
  2. Implement a header parser that can parse a value from a given header name. For example:

    Parser.header(name: "Content-Length", .int)
    // Parser<RequestData, Int>
    
    Solution
    extension Parser where Input == RequestData {
      static func header(name: String, _ parser: Parser<Substring, Output>) -> Self {
        .init { input in
          guard
            var value = input.headers[name],
            let output = parser.run(&value),
            value.isEmpty
          else { return nil }
    
          input.headers[name] = nil
          return output
        }
      }
    }
    
  3. Implement a JSON body parser that can decode a particular value from a request body. For example:

    struct User: Decodable {
      var id: Int
      var name: String
    }
    
    Parser.body(as: User.self, decoder: JSONDecoder())
    // Parser<RequestData, User>
    
    Solution
    extension Parser where Input == RequestData {
      static func body(
        as decodable: Output.Type = Output.self,
        decoder: JSONDecoder = .init()
      ) -> Self where Output: Decodable {
        .init { input in
          guard
            let data = input.body,
            let output = try? decoder.decode(decodable, from: data)
          else { return nil }
    
          input.body = nil
          return output
        }
      }
    }
    

Downloads