/
Header.scala
135 lines (113 loc) · 4.32 KB
/
Header.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/*
* Derived from https://github.com/spray/spray/blob/v1.1-M7/spray-http/src/main/scala/spray/http/HttpHeader.scala
*
* Copyright (C) 2011-2012 spray.io
* Based on code copyright (C) 2010-2011 by the BlueEyes Web Framework Team (http://github.com/jdegoes/blueeyes)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.http4s
import cats._
import cats.data.NonEmptyList
import cats.implicits.{catsSyntaxEither => _, _}
import org.http4s.syntax.string._
import org.http4s.util._
import scala.util.hashing.MurmurHash3
/**
* Abstract representation o the HTTP header
* @see org.http4s.HeaderKey
*/
sealed trait Header extends Renderable with Product {
import Header.Raw
def name: CaseInsensitiveString
def parsed: Header
def renderValue(writer: Writer): writer.type
def value: String = {
val w = new StringWriter
renderValue(w).result
}
def is(key: HeaderKey): Boolean = key.matchHeader(this).isDefined
def isNot(key: HeaderKey): Boolean = !is(key)
override def toString: String = name.toString + ": " + value
def toRaw: Raw = Raw(name, value)
final def render(writer: Writer): writer.type = {
writer << name << ':' << ' '
renderValue(writer)
}
final override def hashCode(): Int =
MurmurHash3.mixLast(name.hashCode, MurmurHash3.productHash(parsed))
final override def equals(that: Any): Boolean = that match {
case h: AnyRef if this eq h => true
case h: Header =>
(name == h.name) &&
(parsed.productArity == h.parsed.productArity) &&
(parsed.productIterator.sameElements(h.parsed.productIterator))
case _ => false
}
}
object Header {
def unapply(header: Header): Option[(CaseInsensitiveString, String)] =
Some((header.name, header.value))
def apply(name: String, value: String): Raw = Raw(name.ci, value)
/**
* Raw representation of the Header
*
* This can be considered the simplest representation where the header is specified as the product of
* a key and a value
* @param name case-insensitive string used to identify the header
* @param value String representation of the header value
*/
final case class Raw(name: CaseInsensitiveString, override val value: String) extends Header {
private[this] var _parsed: Header = null
final override def parsed: Header = {
if (_parsed == null) {
_parsed = parser.HttpHeaderParser.parseHeader(this).getOrElse(this)
}
_parsed
}
override def renderValue(writer: Writer): writer.type = writer.append(value)
}
/** A Header that is already parsed from its String representation. */
trait Parsed extends Header {
def key: HeaderKey
def name: CaseInsensitiveString = key.name
def parsed: this.type = this
}
/**
* A recurring header that satisfies this clause of the Spec:
*
* Multiple message-header fields with the same field-name MAY be present in a message if and only if the entire
* field-value for that header field is defined as a comma-separated list [i.e., #(values)]. It MUST be possible
* to combine the multiple header fields into one "field-name: field-value" pair, without changing the semantics
* of the message, by appending each subsequent field-value to the first, each separated by a comma.
*/
trait Recurring extends Parsed {
type Value
def values: NonEmptyList[Value]
}
/** Simple helper trait that provides a default way of rendering the value */
trait RecurringRenderable extends Recurring {
type Value <: Renderable
override def renderValue(writer: Writer): writer.type = {
values.head.render(writer)
values.tail.foreach(writer << ", " << _)
writer
}
}
implicit val HeaderShow: Show[Header] = Show.show[Header] {
_.toString
}
implicit val HeaderEq: Eq[Header] = Eq.instance[Header] { (a, b) =>
a.name === b.name && a.value === b.value
}
}