Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 44 additions & 6 deletions CedarJava/src/main/java/com/cedarpolicy/model/schema/Schema.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@

package com.cedarpolicy.model.schema;

import java.util.Optional;

import com.cedarpolicy.loader.LibraryLoader;
import com.cedarpolicy.model.exception.InternalException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.Optional;

/** Represents a schema. */
public final class Schema {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
Expand Down Expand Up @@ -122,21 +124,57 @@ public static Schema parse(JsonOrCedar type, String str) throws InternalExceptio

}

public String toCedarFormat() throws InternalException {
if (type == JsonOrCedar.Cedar && schemaText.isPresent()) {
return schemaText.get();
} else if (type == JsonOrCedar.Json && schemaJson.isPresent()) {
return jsonToCedarJni(schemaJson.get().toString());
} else {
throw new IllegalStateException("Schema content is missing");
}
}

/**
* Converts a Cedar format schema to JSON format
*
* @return JsonNode representing the schema in JSON format
* @throws InternalException If schema is not in Cedar format
* @throws JsonMappingException If JSON mapping fails
* @throws JsonProcessingException If JSON processing fails
* @throws NullPointerException If schema text is null
*/
public JsonNode toJsonFormat()
throws InternalException, JsonMappingException, JsonProcessingException, NullPointerException {
if (type == JsonOrCedar.Json && schemaJson.isPresent()) {
return schemaJson.get();
} else if (type == JsonOrCedar.Cedar && schemaText.isPresent()) {
return OBJECT_MAPPER.readTree(cedarToJsonJni(schemaText.get()));
} else {
throw new IllegalStateException("Schema content is missing");
}
}

/** Specifies the schema format used. */
public enum JsonOrCedar {
/**
* Cedar JSON schema format. See <a href="https://docs.cedarpolicy.com/schema/json-schema.html">
* https://docs.cedarpolicy.com/schema/json-schema.html</a>
* Cedar JSON schema format. See
* <a href="https://docs.cedarpolicy.com/schema/json-schema.html">
* https://docs.cedarpolicy.com/schema/json-schema.html</a>
*/
Json,
/**
* Cedar schema format. See <a href="https://docs.cedarpolicy.com/schema/human-readable-schema.html">
* https://docs.cedarpolicy.com/schema/human-readable-schema.html</a>
* Cedar schema format. See
* <a href="https://docs.cedarpolicy.com/schema/human-readable-schema.html">
* https://docs.cedarpolicy.com/schema/human-readable-schema.html</a>
*/
Cedar
}

private static native String parseJsonSchemaJni(String schemaJson) throws InternalException, NullPointerException;

private static native String parseCedarSchemaJni(String schemaText) throws InternalException, NullPointerException;

private static native String jsonToCedarJni(String json) throws InternalException, NullPointerException;

private static native String cedarToJsonJni(String cedar) throws InternalException, NullPointerException;
}
135 changes: 131 additions & 4 deletions CedarJava/src/test/java/com/cedarpolicy/SchemaTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,22 @@

package com.cedarpolicy;

import com.cedarpolicy.model.schema.Schema;
import com.cedarpolicy.model.schema.Schema.JsonOrCedar;

import org.junit.jupiter.api.Test;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import com.cedarpolicy.model.exception.InternalException;
import com.cedarpolicy.model.schema.Schema;
import com.cedarpolicy.model.schema.Schema.JsonOrCedar;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

public class SchemaTests {
@Test
Expand Down Expand Up @@ -107,4 +116,122 @@ public void parseCedarSchema() {
Schema.parse(JsonOrCedar.Cedar, "namspace Foo::Bar;");
});
}

@Nested
@DisplayName("toCedarFormat Tests")
class ToCedarFormatTests {

@Test
@DisplayName("Should return the same Cedar schema text")
void testFromCedar() throws InternalException {
String cedarSchema = "entity User;";
Schema cedarSchemaObj = new Schema(cedarSchema);
String result = cedarSchemaObj.toCedarFormat();
assertNotNull(result, "Result should not be null");
assertEquals(cedarSchema, result, "Should return the original Cedar schema");
}

@Test
@DisplayName("Should convert JSON schema to Cedar format")
void testFromJson() throws InternalException {
String jsonSchema = """
{
"": {
"entityTypes": {
"User": {}
},
"actions": {}
}
}
""";
Schema jsonSchemaObj = Schema.parse(JsonOrCedar.Json, jsonSchema);
String result = jsonSchemaObj.toCedarFormat();

assertNotNull(result, "Result should not be null");
assertTrue(result.contains("entity User"), "Converted Cedar should contain the User entity");
}

@Test
@DisplayName("Should throw IllegalStateException for empty schema")
void testEmptySchema() {
Schema emptySchema = new Schema(JsonOrCedar.Cedar, Optional.empty(), Optional.empty());
Exception exception = assertThrows(IllegalStateException.class, emptySchema::toCedarFormat);
assertEquals("Schema content is missing", exception.getMessage());
}

@Test
@DisplayName("Should throw exception for malformed JSON schema")
void testMalformedSchema() {
// Missing closing brace in the JSON structure
String malformedJson = """
{
"": {
"entityTypes": {
"User": {}
},
"actions": {
}
""";
Schema malformedSchema = new Schema(JsonOrCedar.Json, Optional.of(malformedJson), Optional.empty());
assertThrows(IllegalStateException.class, malformedSchema::toCedarFormat);
}
}

@Nested
@DisplayName("toJsonFormat Tests")
class ToJsonFormatTests {

@Test
@DisplayName("Should convert Cedar schema to JSON format")
void testFromCedar() throws Exception {
String cedarSchema = "entity User;";
Schema cedarSchemaObj = new Schema(cedarSchema);
JsonNode result = cedarSchemaObj.toJsonFormat();

String expectedJson = "{\"\":{\"entityTypes\":{\"User\":{}},\"actions\":{}}}";
JsonNode expectedNode = new ObjectMapper().readTree(expectedJson);

assertNotNull(result, "Result should not be null");
assertEquals(expectedNode, result, "JSON should match expected structure");
}

@Test
@DisplayName("Should return the same JSON schema object")
void testFromJson() throws Exception {
String jsonSchema = """
{
"": {
"entityTypes": {
"User": {}
},
"actions": {}
}
}
""";
Schema jsonSchemaObj = Schema.parse(JsonOrCedar.Json, jsonSchema);
JsonNode result = jsonSchemaObj.toJsonFormat();

ObjectMapper mapper = new ObjectMapper();
JsonNode expectedNode = mapper.readTree(jsonSchema);

assertNotNull(result, "Result should not be null");
assertEquals(expectedNode, result, "JSON should match the original schema");
}

@Test
@DisplayName("Should throw IllegalStateException for empty schema")
void testEmptySchema() {
Schema emptySchema = new Schema(JsonOrCedar.Cedar, Optional.empty(), Optional.empty());
Exception exception = assertThrows(IllegalStateException.class, emptySchema::toJsonFormat);
assertEquals("Schema content is missing", exception.getMessage());
}

@Test
@DisplayName("Should throw exception for malformed Cedar schema")
void testMalformedSchema() {
String malformedCedar = "entty User";
Schema malformedSchema = new Schema(JsonOrCedar.Cedar, Optional.empty(), Optional.of(malformedCedar));
assertThrows(InternalException.class, malformedSchema::toJsonFormat);
}
}
}
Loading