CORDA-880 Make contractsdsl java interop functions behave same as inline functions (#2275)

* Make java interop functions same as inline functions and add tests

* Fixing docs

* Move unspecifiedCountry to internal. (#2274)

* Review changes

* Call java interop functions from inline functions

* Use correct test assertion
This commit is contained in:
Anthony Keenan 2017-12-21 09:42:09 +00:00 committed by GitHub
parent 313d21f068
commit cb703476a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 201 additions and 16 deletions

View File

@ -12,7 +12,6 @@ import java.util.*
* Defines a simple domain specific language for the specification of financial contracts. Currently covers:
*
* - Some utilities for working with commands.
* - An Amount type that represents a positive quantity of a specific token.
* - A simple language extension for specifying requirements in English, along with logic to enforce them.
*/
@ -30,37 +29,44 @@ inline fun <R> requireThat(body: Requirements.() -> R) = Requirements.body()
//// Authenticated commands ///////////////////////////////////////////////////////////////////////////////////////////
// TODO: Provide a version of select that interops with Java
/** Filters the command list by type, party and public key all at once. */
inline fun <reified T : CommandData> Collection<CommandWithParties<CommandData>>.select(signer: PublicKey? = null,
party: AbstractParty? = null) =
filter { it.value is T }.
select(T::class.java, signer, party)
/** Filters the command list by type, party and public key all at once. */
fun <C : CommandData> Collection<CommandWithParties<CommandData>>.select(klass: Class<C>,
signer: PublicKey? = null,
party: AbstractParty? = null) =
mapNotNull { if (klass.isInstance(it.value)) uncheckedCast<CommandWithParties<CommandData>, CommandWithParties<C>>(it) else null }.
filter { if (signer == null) true else signer in it.signers }.
filter { if (party == null) true else party in it.signingParties }.
map { CommandWithParties(it.signers, it.signingParties, it.value as T) }
// TODO: Provide a version of select that interops with Java
map { CommandWithParties(it.signers, it.signingParties, it.value) }
/** Filters the command list by type, parties and public keys all at once. */
inline fun <reified T : CommandData> Collection<CommandWithParties<CommandData>>.select(signers: Collection<PublicKey>?,
parties: Collection<Party>?) =
filter { it.value is T }.
select(T::class.java, signers, parties)
/** Filters the command list by type, parties and public keys all at once. */
fun <C : CommandData> Collection<CommandWithParties<CommandData>>.select(klass: Class<C>,
signers: Collection<PublicKey>?,
parties: Collection<Party>?) =
mapNotNull { if (klass.isInstance(it.value)) uncheckedCast<CommandWithParties<CommandData>, CommandWithParties<C>>(it) else null }.
filter { if (signers == null) true else it.signers.containsAll(signers) }.
filter { if (parties == null) true else it.signingParties.containsAll(parties) }.
map { CommandWithParties(it.signers, it.signingParties, it.value as T) }
map { CommandWithParties(it.signers, it.signingParties, it.value) }
/** Ensures that a transaction has only one command that is of the given type, otherwise throws an exception. */
inline fun <reified T : CommandData> Collection<CommandWithParties<CommandData>>.requireSingleCommand() = try {
select<T>().single()
inline fun <reified T : CommandData> Collection<CommandWithParties<CommandData>>.requireSingleCommand() = requireSingleCommand(T::class.java)
/** Ensures that a transaction has only one command that is of the given type, otherwise throws an exception. */
fun <C : CommandData> Collection<CommandWithParties<CommandData>>.requireSingleCommand(klass: Class<C>) = try {
select(klass).single()
} catch (e: NoSuchElementException) {
throw IllegalStateException("Required ${T::class.qualifiedName} command") // Better error message.
throw IllegalStateException("Required ${klass.kotlin.qualifiedName} command") // Better error message.
}
/** Ensures that a transaction has only one command that is of the given type, otherwise throws an exception. */
fun <C : CommandData> Collection<CommandWithParties<CommandData>>.requireSingleCommand(klass: Class<C>) =
mapNotNull { if (klass.isInstance(it.value)) uncheckedCast<CommandWithParties<CommandData>, CommandWithParties<C>>(it) else null }.single()
/**
* Simple functionality for verifying a move command. Verifies that each input has a signature from its owning key.
*

View File

@ -0,0 +1,179 @@
package net.corda.core.contracts
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.testing.TestIdentity
import org.assertj.core.api.Assertions
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.security.PublicKey
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class ContractsDSLTests {
class UnwantedCommand : CommandData
interface TestCommands : CommandData {
class CommandOne : TypeOnlyCommandData(), TestCommands
class CommandTwo : TypeOnlyCommandData(), TestCommands
}
private companion object {
val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
val validCommandOne = CommandWithParties(listOf(megaCorp.publicKey, miniCorp.publicKey), listOf(megaCorp.party, miniCorp.party), TestCommands.CommandOne())
val validCommandTwo = CommandWithParties(listOf(megaCorp.publicKey), listOf(megaCorp.party), TestCommands.CommandTwo())
val invalidCommand = CommandWithParties(emptyList(), emptyList(), UnwantedCommand())
}
@RunWith(Parameterized::class)
class RequireSingleCommandTests(private val testFunction: (Collection<CommandWithParties<CommandData>>) -> CommandWithParties<CommandData>, description: String) {
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{1}")
fun data(): Collection<Array<Any>> = listOf(
arrayOf<Any>({ commands: Collection<CommandWithParties<CommandData>> -> commands.requireSingleCommand<TestCommands>() }, "Inline version"),
arrayOf<Any>({ commands: Collection<CommandWithParties<CommandData>> -> commands.requireSingleCommand(TestCommands::class.java) }, "Interop version")
)
}
@Test
fun `check function returns one value`() {
val commands = listOf(validCommandOne, invalidCommand)
val returnedCommand = testFunction(commands)
assertEquals(returnedCommand, validCommandOne, "they should be the same")
}
@Test(expected = IllegalArgumentException::class)
fun `check error is thrown if more than one valid command`() {
val commands = listOf(validCommandOne, validCommandTwo)
testFunction(commands)
}
@Test
fun `check error is thrown when command is of wrong type`() {
val commands = listOf(invalidCommand)
Assertions.assertThatThrownBy { testFunction(commands) }
.isInstanceOf(IllegalStateException::class.java)
.hasMessage("Required net.corda.core.contracts.ContractsDSLTests.TestCommands command")
}
}
@RunWith(Parameterized::class)
class SelectWithSingleInputsTests(private val testFunction: (Collection<CommandWithParties<CommandData>>, PublicKey?, AbstractParty?) -> Iterable<CommandWithParties<CommandData>>, description: String) {
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{1}")
fun data(): Collection<Array<Any>> = listOf(
arrayOf<Any>({ commands: Collection<CommandWithParties<CommandData>>, signer: PublicKey?, party: AbstractParty? -> commands.select<TestCommands>(signer, party) }, "Inline version"),
arrayOf<Any>({ commands: Collection<CommandWithParties<CommandData>>, signer: PublicKey?, party: AbstractParty? -> commands.select(TestCommands::class.java, signer, party) }, "Interop version")
)
}
@Test
fun `check that function returns all values`() {
val commands = listOf(validCommandOne, validCommandTwo)
testFunction(commands, null, null)
assertEquals(2, commands.size)
assertTrue(commands.contains(validCommandOne))
assertTrue(commands.contains(validCommandTwo))
}
@Test
fun `check that function does not return invalid command types`() {
val commands = listOf(validCommandOne, invalidCommand)
val filteredCommands = testFunction(commands, null, null).toList()
assertEquals(1, filteredCommands.size)
assertTrue(filteredCommands.contains(validCommandOne))
assertFalse(filteredCommands.contains(invalidCommand))
}
@Test
fun `check that function returns commands from valid signers`() {
val commands = listOf(validCommandOne, validCommandTwo)
val filteredCommands = testFunction(commands, miniCorp.publicKey, null).toList()
assertEquals(1, filteredCommands.size)
assertTrue(filteredCommands.contains(validCommandOne))
assertFalse(filteredCommands.contains(validCommandTwo))
}
@Test
fun `check that function returns commands from valid parties`() {
val commands = listOf(validCommandOne, validCommandTwo)
val filteredCommands = testFunction(commands, null, miniCorp.party).toList()
assertEquals(1, filteredCommands.size)
assertTrue(filteredCommands.contains(validCommandOne))
assertFalse(filteredCommands.contains(validCommandTwo))
}
}
@RunWith(Parameterized::class)
class SelectWithMultipleInputsTests(private val testFunction: (Collection<CommandWithParties<CommandData>>, Collection<PublicKey>?, Collection<Party>?) -> Iterable<CommandWithParties<CommandData>>, description: String) {
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{1}")
fun data(): Collection<Array<Any>> = listOf(
arrayOf<Any>({ commands: Collection<CommandWithParties<CommandData>>, signers: Collection<PublicKey>?, party: Collection<Party>? -> commands.select<TestCommands>(signers, party) }, "Inline version"),
arrayOf<Any>({ commands: Collection<CommandWithParties<CommandData>>, signers: Collection<PublicKey>?, party: Collection<Party>? -> commands.select(TestCommands::class.java, signers, party) }, "Interop version")
)
}
@Test
fun `check that function returns all values`() {
val commands = listOf(validCommandOne, validCommandTwo)
testFunction(commands, null, null)
assertEquals(2, commands.size)
assertTrue(commands.contains(validCommandOne))
assertTrue(commands.contains(validCommandTwo))
}
@Test
fun `check that function does not return invalid command types`() {
val commands = listOf(validCommandOne, invalidCommand)
val filteredCommands = testFunction(commands, null, null).toList()
assertEquals(1, filteredCommands.size)
assertTrue(filteredCommands.contains(validCommandOne))
assertFalse(filteredCommands.contains(invalidCommand))
}
@Test
fun `check that function returns commands from valid signers`() {
val commands = listOf(validCommandOne, validCommandTwo)
val filteredCommands = testFunction(commands, listOf(megaCorp.publicKey), null).toList()
assertEquals(2, filteredCommands.size)
assertTrue(filteredCommands.contains(validCommandOne))
assertTrue(filteredCommands.contains(validCommandTwo))
}
@Test
fun `check that function returns commands from all valid signers`() {
val commands = listOf(validCommandOne, validCommandTwo)
val filteredCommands = testFunction(commands, listOf(miniCorp.publicKey, megaCorp.publicKey), null).toList()
assertEquals(1, filteredCommands.size)
assertTrue(filteredCommands.contains(validCommandOne))
assertFalse(filteredCommands.contains(validCommandTwo))
}
@Test
fun `check that function returns commands from valid parties`() {
val commands = listOf(validCommandOne, validCommandTwo)
val filteredCommands = testFunction(commands, null, listOf(megaCorp.party)).toList()
assertEquals(2, filteredCommands.size)
assertTrue(filteredCommands.contains(validCommandOne))
assertTrue(filteredCommands.contains(validCommandTwo))
}
@Test
fun `check that function returns commands from all valid parties`() {
val commands = listOf(validCommandOne, validCommandTwo)
val filteredCommands = testFunction(commands, null, listOf(miniCorp.party, megaCorp.party)).toList()
assertEquals(1, filteredCommands.size)
assertTrue(filteredCommands.contains(validCommandOne))
assertFalse(filteredCommands.contains(validCommandTwo))
}
}
}