Skip to content

Commit

Permalink
chore: add Kotlin example
Browse files Browse the repository at this point in the history
  • Loading branch information
booniepepper committed Dec 27, 2023
1 parent 3b6b7de commit 3a6c611
Show file tree
Hide file tree
Showing 6 changed files with 286 additions and 2 deletions.
4 changes: 4 additions & 0 deletions example/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ run:
cd "${project_name}" && \
./gradlew run

run-kotlin:
cd "${project_name}" && \
./gradlew -P language=kotlin run

run-openfga:
docker pull docker.io/openfga/openfga:${openfga_version} && \
docker run -p 8080:8080 docker.io/openfga/openfga:${openfga_version} run
48 changes: 47 additions & 1 deletion example/example1/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,49 @@
## Examples of using the OpenFGA Java SDK

TODO
A set of Examples on how to call the OpenFGA Java SDK

### Examples
Example 1:
A bare-bones example. It creates a store, and runs a set of calls against it including creating a model, writing tuples and checking for access.
This example is implemented in both Java and Kotlin.


### Running the Examples

Prerequisites:
- `docker`
- `make`
- A Java Runtime Environment (JRE)

#### Run using a published SDK

Steps
1. Clone/Copy the example folder
2. Run `make` to build the project
3. If you have an OpenFGA server running, you can use it, otherwise run `make run-openfga` to spin up an instance (you'll need to switch to a different terminal after - don't forget to close it when done)
4. Run `make run` to run the example

#### Run using a local unpublished SDK build

Steps
1. Build the SDK
2. In the Example project file (e.g. `build.gradle`), comment out the part that specifies the remote SDK, e.g.
```groovy
dependencies {
implementation("dev.openfga:openfga-sdk:0.3.+")
// ...etc
}
```
and replace it with one pointing to the local gradle project, e.g.
```groovy
dependencies {
// implementation("dev.openfga:openfga-sdk:0.3.+")
implementation project(path: ':')
// ...etc
}
```
3. Run `make` to build the project
4. If you have an OpenFGA server running, you can use it, otherwise run `make run-openfga` to spin up an instance (you'll need to switch to a different terminal after - don't forget to close it when done)
5. Run `make run` to run the example
24 changes: 23 additions & 1 deletion example/example1/build.gradle
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
plugins {
id 'application'
id 'com.diffplug.spotless' version '6.23.3'
id 'org.jetbrains.kotlin.jvm' version '2.0.0-Beta2'
}

application {
mainClass = 'dev.openfga.sdk.example.Example1'
switch (language) {
case 'kotlin':
mainClass = 'dev.openfga.sdk.example.KotlinExample1'
break
default:
mainClass = 'dev.openfga.sdk.example.Example1'
}
}

repositories {
Expand All @@ -24,6 +31,9 @@ dependencies {
implementation("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jacksonVersion")
implementation("org.openapitools:jackson-databind-nullable:0.2.+")

// Kotlin
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
}

// Use spotless plugin to automatically format code, remove unused import, etc
Expand Down Expand Up @@ -53,3 +63,15 @@ spotless {
tasks.register('fmt') {
dependsOn 'spotlessApply'
}

compileKotlin {
kotlinOptions {
jvmTarget = "17"
}
}

compileTestKotlin {
kotlinOptions {
jvmTarget = "17"
}
}
1 change: 1 addition & 0 deletions example/example1/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
language=java
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ public void run() throws Exception {
}

public static void main(String[] args) {
System.out.println("=== Example 1 (Java) ===");
try {
new Example1().run();
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
package dev.openfga.sdk.example

import com.fasterxml.jackson.databind.ObjectMapper
import dev.openfga.sdk.api.client.ClientAssertion
import dev.openfga.sdk.api.client.OpenFgaClient
import dev.openfga.sdk.api.client.model.*
import dev.openfga.sdk.api.configuration.ClientConfiguration
import dev.openfga.sdk.api.configuration.ClientCredentials
import dev.openfga.sdk.api.configuration.ClientWriteOptions
import dev.openfga.sdk.api.configuration.Credentials
import dev.openfga.sdk.api.model.CreateStoreRequest
import dev.openfga.sdk.api.model.WriteAuthorizationModelRequest

internal class KotlinExample1 {
@Throws(Exception::class)
fun run() {
var credentials = Credentials()
if (System.getenv("FGA_CLIENT_ID") != null) {
credentials = Credentials(
ClientCredentials()
.apiAudience(System.getenv("FGA_API_AUDIENCE"))
.apiTokenIssuer(System.getenv("FGA_TOKEN_ISSUER"))
.clientId("FGA_CLIENT_ID")
.clientSecret("FGA_CLIENT_SECRET")
)
} else {
println("Proceeding with no credentials (expecting localhost)")
}
val configuration = ClientConfiguration()
.apiUrl(System.getenv("FGA_API_URL")) // required, e.g. https://api.fga.example
.storeId(System.getenv("FGA_STORE_ID")) // not needed when calling `CreateStore` or `ListStores`
.authorizationModelId(
System.getenv("FGA_AUTHORIZATION_MODEL_ID")
) // Optional, can be overridden per request
.credentials(credentials)
val fgaClient = OpenFgaClient(configuration)

// ListStores
println("Listing Stores")
val stores1 = fgaClient.listStores().get()
println("Stores Count: " + stores1.stores.size)

// CreateStore
println("Creating Test Store")
val store = fgaClient
.createStore(CreateStoreRequest().name("Test Store"))
.get()
println("Test Store ID: " + store.id)

// Set the store id
fgaClient.setStoreId(store.id)

// ListStores after Create
println("Listing Stores")
val stores = fgaClient.listStores().get()
println("Stores Count: " + stores.stores.size)

// GetStore
println("Getting Current Store")
val currentStore = fgaClient.store.get()
println("Current Store Name: " + currentStore.name)

// ReadAuthorizationModels
println("Reading Authorization Models")
var models = fgaClient.readAuthorizationModels().get()
println("Models Count: " + models.authorizationModels.size)

// ReadLatestAuthorizationModel
try {
val latestAuthorizationModel = fgaClient
.readLatestAuthorizationModel()
.get() // TODO: Should this really return null? Optional<...Response>?
println(
"Latest Authorization Model ID "
+ latestAuthorizationModel.authorizationModel!!.id
)
} catch (e: Exception) {
println("Latest Authorization Model not found")
}
val mapper = ObjectMapper().findAndRegisterModules()

// WriteAuthorizationModel
val authModelJson = loadResource("example1-auth-model.json")
val authorizationModel = fgaClient
.writeAuthorizationModel(mapper.readValue(authModelJson, WriteAuthorizationModelRequest::class.java))
.get()
println("Authorization Model ID " + authorizationModel.authorizationModelId)

// ReadAuthorizationModels - after Write
println("Reading Authorization Models")
models = fgaClient.readAuthorizationModels().get()
println("Models Count: " + models.authorizationModels.size)

// ReadLatestAuthorizationModel - after Write
val latestAuthorizationModel = fgaClient.readLatestAuthorizationModel().get()
println(
"Latest Authorization Model ID "
+ latestAuthorizationModel.authorizationModel!!.id
)

// Set the model ID
fgaClient.setAuthorizationModelId(
latestAuthorizationModel.authorizationModel!!.id
)

// Write
println("Writing Tuples")
fgaClient
.write(
ClientWriteRequest()
.writes(
listOf(
ClientTupleKey()
.user("user:anne")
.relation("writer")
._object("document:roadmap")
)
),
ClientWriteOptions()
.disableTransactions(true)
.authorizationModelId(authorizationModel.authorizationModelId)
)
.get()
println("Done Writing Tuples")

// Read
println("Reading Tuples")
val readTuples = fgaClient.read(ClientReadRequest()).get()
println("Read Tuples" + mapper.writeValueAsString(readTuples))

// ReadChanges
println("Reading Tuple Changess")
val readChangesTuples = fgaClient.readChanges(ClientReadChangesRequest()).get()
println("Read Changes Tuples" + mapper.writeValueAsString(readChangesTuples))

// Check
println("Checking for access")
try {
val failingCheckResponse = fgaClient
.check(
ClientCheckRequest()
.user("user:anne")
.relation("reader")
._object("document:roadmap")
)
.get()
println("Allowed: " + failingCheckResponse.allowed)
} catch (e: Exception) {
println("Failed due to: " + e.message)
}

// Checking for access with context
// TODO: Add ClientCheckRequest.context
// System.out.println("Checking for access with context");
// var checkResponse = fgaClient
// .check(new ClientCheckRequest()
// .user("user:anne")
// .relation("reader")
// ._object("document:roadmap")
// .context(Map.of("ViewCount", 100)))
// .get();
// System.out.println("Allowed: " + checkResponse.getAllowed());

// WriteAssertions
fgaClient
.writeAssertions(
listOf(
ClientAssertion()
.user("user:carl")
.relation("writer")
._object("document:budget")
.expectation(true),
ClientAssertion()
.user("user:anne")
.relation("reader")
._object("document:roadmap")
.expectation(false)
)
)
.get()
println("Assertions updated")

// ReadAssertions
println("Reading Assertions")
val assertions = fgaClient.readAssertions().get()
println("Assertions " + mapper.writeValueAsString(assertions))

// DeleteStore
println("Deleting Current Store")
fgaClient.deleteStore().get()
println("Deleted Store: " + currentStore.name)
}

// Small helper function to load resource files relative to this class.
private fun loadResource(filename: String): String {
return javaClass.module.classLoader.getResource(filename)?.readText()!!
}

companion object {
@JvmStatic
fun main(args: Array<String>) {
println("=== Example 1 (Kotlin) ===")
try {
KotlinExample1().run()
} catch (e: Exception) {
println("ERROR: $e")
}
}
}
}

0 comments on commit 3a6c611

Please sign in to comment.