Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
util-registry: Naive implementation to get the ball rolling
Problem We want to have a low-level API for the R* Registry, which codifies the rules that we expect the "client libraries" that we actually ship to people will use. Solution Nail down the API with a naive implementation first. The naive implementation is partially there because the data model is exactly what I imagine it being in my head. RB_ID=568274
- Loading branch information
Showing
7 changed files
with
187 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
maven_layout() | ||
|
||
jar_library(name='util-registry', | ||
dependencies=[ | ||
'util/util-core/src/main/scala', | ||
] | ||
) | ||
|
||
jar_library(name='tests', | ||
dependencies=[ | ||
'util/util-core/src/test/scala' | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
scala_library(name='scala', | ||
provides = scala_artifact( | ||
org = 'com.twitter', | ||
name = 'util-registry', | ||
repo = artifactory, | ||
), | ||
sources=rglobs('*.scala') | ||
) |
61 changes: 61 additions & 0 deletions
61
util-registry/src/main/scala/com/twitter/registry/Registry.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package com.twitter.registry | ||
|
||
import java.util.NoSuchElementException | ||
|
||
private[registry] final case class Entry(key: Seq[String], value: String) | ||
|
||
private[registry] object Entry { | ||
val TupledMethod: ((Seq[String], String)) => Entry = (Entry.apply _).tupled | ||
} | ||
|
||
/** | ||
* This is an expert-level API; it is not meant for end-users. | ||
* | ||
* The registry is a hierarchical key/value store, where all keys are sequences | ||
* of Strings, and values are Strings. | ||
* | ||
* Keys and values must be non-control ascii, and must not contain the '/' | ||
* character. If you pass in a key or value with an invalid character, it will | ||
* silently be removed. If this makes your key clash with another key, it will | ||
* overwrite. | ||
*/ | ||
private[registry] trait Registry extends Iterable[Entry] { | ||
/** | ||
* Provides an iterator over the registry. | ||
* | ||
* It is the responsibility of the caller to synchronize if they would like to | ||
* iterate in multiple threads, but the iterator is guaranteed not to change as | ||
* it is called. | ||
*/ | ||
def iterator(): Iterator[Entry] | ||
|
||
/** | ||
* Registers a value in the registry, and returns the old value (if any). | ||
*/ | ||
def put(key: Seq[String], value: String): Option[String] | ||
} | ||
|
||
private[registry] class NaiveRegistry extends Registry { | ||
private[this] var registry = Map.empty[Seq[String], String] | ||
|
||
def iterator(): Iterator[Entry] = synchronized(registry).iterator.map(Entry.TupledMethod) | ||
|
||
def put(key: Seq[String], value: String): Option[String] = { | ||
val sanitizedKey = key.map(sanitize) | ||
val sanitizedValue = sanitize(value) | ||
synchronized { | ||
val result = registry.get(sanitizedKey) | ||
registry += sanitizedKey -> sanitizedValue | ||
result | ||
} | ||
} | ||
|
||
private[this] def sanitize(key: String): String = | ||
key.filter { char => char > 31 && char <= 127 && char != '/' } | ||
} | ||
|
||
private[registry] object GlobalRegistry { | ||
private[this] val registry = new NaiveRegistry | ||
|
||
def get: Registry = registry | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
junit_tests(name='scala', | ||
dependencies=[ | ||
'3rdparty/jvm/junit', | ||
'3rdparty/jvm/org/scalatest', | ||
'util/util-registry/src/main/scala', | ||
], | ||
sources=rglobs('*.scala') | ||
) |
10 changes: 10 additions & 0 deletions
10
util-registry/src/test/scala/com/twitter/registry/NaiveRuntimeRegistry.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package com.twitter.registry | ||
|
||
import org.junit.runner.RunWith | ||
import org.scalatest.junit.JUnitRunner | ||
|
||
@RunWith(classOf[JUnitRunner]) | ||
class NaiveRegistryTest extends RegistryTest { | ||
def mkRegistry(): Registry = new NaiveRegistry() | ||
def name: String = "NaiveRegistry" | ||
} |
76 changes: 76 additions & 0 deletions
76
util-registry/src/test/scala/com/twitter/registry/RegistryTest.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
package com.twitter.registry | ||
|
||
import java.lang.{Character => JCharacter} | ||
import org.scalatest.FunSuite | ||
|
||
abstract class RegistryTest extends FunSuite { | ||
def mkRegistry(): Registry | ||
def name: String | ||
|
||
test(s"$name can insert a key/value pair and then read it") { | ||
val registry = mkRegistry() | ||
registry.put(Seq("foo"), "bar") | ||
assert(registry.toSet == Set(Entry(Seq("foo"), "bar"))) | ||
} | ||
|
||
test(s"$name's iterator is not affected by adding an element") { | ||
val registry = mkRegistry() | ||
registry.put(Seq("foo"), "bar") | ||
val iter = registry.iterator() | ||
registry.put(Seq("foo"), "baz") | ||
assert(iter.next() == Entry(Seq("foo"), "bar")) | ||
assert(!iter.hasNext) | ||
} | ||
|
||
test(s"$name can overwrite old element") { | ||
val registry = mkRegistry() | ||
registry.put(Seq("foo"), "bar") | ||
registry.put(Seq("foo"), "baz") | ||
assert((registry.toSet) == Set(Entry(Seq("foo"), "baz"))) | ||
} | ||
|
||
test(s"$name can return the old element when replacing") { | ||
val registry = mkRegistry() | ||
registry.put(Seq("foo"), "bar") | ||
assert(registry.put(Seq("foo"), "baz") == Some("bar")) | ||
assert(registry.toSet == Set(Entry(Seq("foo"), "baz"))) | ||
} | ||
|
||
test(s"$name can support multiple elements") { | ||
val registry = mkRegistry() | ||
registry.put(Seq("foo"), "bar") | ||
registry.put(Seq("baz"), "qux") | ||
assert(registry.toSet == Set(Entry(Seq("foo"), "bar"), Entry(Seq("baz"), "qux"))) | ||
} | ||
|
||
test(s"$name can support nontrivial keys") { | ||
val registry = mkRegistry() | ||
registry.put(Seq("foo", "bar", "baz"), "qux") | ||
assert(registry.toSet == Set(Entry(Seq("foo", "bar", "baz"), "qux"))) | ||
} | ||
|
||
test(s"$name can support empty keys") { | ||
val registry = mkRegistry() | ||
registry.put(Seq(), "qux") | ||
assert(registry.toSet == Set(Entry(Seq(), "qux"))) | ||
} | ||
|
||
test(s"$name can sanitize bad values") { | ||
val registry = mkRegistry() | ||
registry.put(Seq("foo"), "q/ux") | ||
assert(registry.toSet == Set(Entry(Seq("foo"), "qux"))) | ||
} | ||
|
||
test(s"$name can sanitize bad keys") { | ||
val registry = mkRegistry() | ||
registry.put(Seq("fo☃o", s"bar${JCharacter.toString(31)}"), "qux") | ||
assert(registry.toSet == Set(Entry(Seq("foo", "bar"), "qux"))) | ||
} | ||
|
||
test(s"$name can support keys that are subsequences of other keys") { | ||
val registry = mkRegistry() | ||
registry.put(Seq("foo"), "bar") | ||
registry.put(Seq("foo", "baz"), "qux") | ||
assert(registry.toSet == Set(Entry(Seq("foo"), "bar"), Entry(Seq("foo", "baz"), "qux"))) | ||
} | ||
} |