Skip to content

Commit 770e8cd

Browse files
authored
adds schema conversion APIs (#325)
Signed-off-by: Raheim Swaby <swabyra@amazon.com>
1 parent 748f28b commit 770e8cd

File tree

3 files changed

+476
-74
lines changed

3 files changed

+476
-74
lines changed

CedarJava/src/main/java/com/cedarpolicy/model/schema/Schema.java

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@
1616

1717
package com.cedarpolicy.model.schema;
1818

19+
import java.util.Optional;
20+
1921
import com.cedarpolicy.loader.LibraryLoader;
2022
import com.cedarpolicy.model.exception.InternalException;
23+
import com.fasterxml.jackson.core.JsonProcessingException;
24+
import com.fasterxml.jackson.databind.JsonMappingException;
2125
import com.fasterxml.jackson.databind.JsonNode;
2226
import com.fasterxml.jackson.databind.ObjectMapper;
2327

24-
import java.util.Optional;
25-
2628
/** Represents a schema. */
2729
public final class Schema {
2830
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@@ -122,21 +124,67 @@ public static Schema parse(JsonOrCedar type, String str) throws InternalExceptio
122124

123125
}
124126

127+
/**
128+
* Converts a schema to Cedar format
129+
*
130+
* @return String representing the schema in Cedar format
131+
* @throws InternalException If conversion from JSON to Cedar format fails
132+
* @throws IllegalStateException If schema content is missing
133+
* @throws NullPointerException
134+
*/
135+
public String toCedarFormat() throws InternalException, IllegalStateException, NullPointerException {
136+
if (type == JsonOrCedar.Cedar && schemaText.isPresent()) {
137+
return schemaText.get();
138+
} else if (type == JsonOrCedar.Json && schemaJson.isPresent()) {
139+
return jsonToCedarJni(schemaJson.get().toString());
140+
} else {
141+
throw new IllegalStateException("No schema found");
142+
}
143+
}
144+
145+
/**
146+
* Converts a Cedar format schema to JSON format
147+
*
148+
* @return JsonNode representing the schema in JSON format
149+
* @throws InternalException If conversion from Cedar to JSON format fails
150+
* @throws IllegalStateException If schema content is missing
151+
* @throws JsonMappingException If invalid JSON
152+
* @throws JsonProcessingException If invalid JSON
153+
* @throws NullPointerException
154+
*/
155+
public JsonNode toJsonFormat()
156+
throws InternalException, JsonMappingException, JsonProcessingException, NullPointerException,
157+
IllegalStateException {
158+
if (type == JsonOrCedar.Json && schemaJson.isPresent()) {
159+
return schemaJson.get();
160+
} else if (type == JsonOrCedar.Cedar && schemaText.isPresent()) {
161+
return OBJECT_MAPPER.readTree(cedarToJsonJni(schemaText.get()));
162+
} else {
163+
throw new IllegalStateException("No schema found");
164+
}
165+
}
166+
125167
/** Specifies the schema format used. */
126168
public enum JsonOrCedar {
127169
/**
128-
* Cedar JSON schema format. See <a href="https://docs.cedarpolicy.com/schema/json-schema.html">
129-
* https://docs.cedarpolicy.com/schema/json-schema.html</a>
170+
* Cedar JSON schema format. See
171+
* <a href="https://docs.cedarpolicy.com/schema/json-schema.html">
172+
* https://docs.cedarpolicy.com/schema/json-schema.html</a>
130173
*/
131174
Json,
132175
/**
133-
* Cedar schema format. See <a href="https://docs.cedarpolicy.com/schema/human-readable-schema.html">
134-
* https://docs.cedarpolicy.com/schema/human-readable-schema.html</a>
176+
* Cedar schema format. See
177+
* <a href="https://docs.cedarpolicy.com/schema/human-readable-schema.html">
178+
* https://docs.cedarpolicy.com/schema/human-readable-schema.html</a>
135179
*/
136180
Cedar
137181
}
138182

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

141185
private static native String parseCedarSchemaJni(String schemaText) throws InternalException, NullPointerException;
186+
187+
private static native String jsonToCedarJni(String json) throws InternalException, NullPointerException;
188+
189+
private static native String cedarToJsonJni(String cedar) throws InternalException, NullPointerException;
142190
}

CedarJava/src/test/java/com/cedarpolicy/SchemaTests.java

Lines changed: 132 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,21 @@
1616

1717
package com.cedarpolicy;
1818

19-
import com.cedarpolicy.model.schema.Schema;
20-
import com.cedarpolicy.model.schema.Schema.JsonOrCedar;
21-
22-
import org.junit.jupiter.api.Test;
19+
import java.util.Optional;
2320

2421
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
22+
import static org.junit.jupiter.api.Assertions.assertEquals;
23+
import static org.junit.jupiter.api.Assertions.assertNotNull;
2524
import static org.junit.jupiter.api.Assertions.assertThrows;
25+
import org.junit.jupiter.api.DisplayName;
26+
import org.junit.jupiter.api.Nested;
27+
import org.junit.jupiter.api.Test;
28+
29+
import com.cedarpolicy.model.exception.InternalException;
30+
import com.cedarpolicy.model.schema.Schema;
31+
import com.cedarpolicy.model.schema.Schema.JsonOrCedar;
32+
import com.fasterxml.jackson.databind.JsonNode;
33+
import com.fasterxml.jackson.databind.ObjectMapper;
2634

2735
public class SchemaTests {
2836
@Test
@@ -107,4 +115,124 @@ public void parseCedarSchema() {
107115
Schema.parse(JsonOrCedar.Cedar, "namspace Foo::Bar;");
108116
});
109117
}
118+
119+
@Nested
120+
@DisplayName("toCedarFormat Tests")
121+
class ToCedarFormatTests {
122+
123+
@Test
124+
@DisplayName("Should return the same Cedar schema text")
125+
void testFromCedar() throws InternalException {
126+
String cedarSchema = "entity User;";
127+
Schema cedarSchemaObj = new Schema(cedarSchema);
128+
String result = cedarSchemaObj.toCedarFormat();
129+
assertNotNull(result, "Result should not be null");
130+
assertEquals(cedarSchema, result, "Should return the original Cedar schema");
131+
}
132+
133+
@Test
134+
@DisplayName("Should convert JSON schema to Cedar format")
135+
void testFromJson() throws InternalException {
136+
String jsonSchema = """
137+
{
138+
"": {
139+
"entityTypes": {
140+
"User": {}
141+
},
142+
"actions": {}
143+
}
144+
}
145+
""";
146+
Schema jsonSchemaObj = Schema.parse(JsonOrCedar.Json, jsonSchema);
147+
String result = jsonSchemaObj.toCedarFormat();
148+
149+
assertNotNull(result, "Result should not be null");
150+
String expectedCedar = "entity User;";
151+
assertEquals(expectedCedar, result.trim(), "Converted Cedar should match expected format");
152+
}
153+
154+
@Test
155+
@DisplayName("Should throw IllegalStateException for empty schema")
156+
void testEmptySchema() {
157+
Schema emptySchema = new Schema(JsonOrCedar.Cedar, Optional.empty(), Optional.empty());
158+
Exception exception = assertThrows(IllegalStateException.class, emptySchema::toCedarFormat);
159+
assertEquals("No schema found", exception.getMessage());
160+
}
161+
162+
@Test
163+
@DisplayName("Should throw exception for malformed JSON schema")
164+
void testMalformedSchema() {
165+
String malformedJson = """
166+
{
167+
"": {
168+
"entityMalformedTypes": {
169+
"User": {}
170+
},
171+
"actions": {}
172+
}
173+
}
174+
""";
175+
Schema malformedSchema = new Schema(JsonOrCedar.Json, Optional.of(malformedJson), Optional.empty());
176+
assertNotNull(malformedSchema.schemaJson);
177+
assertThrows(InternalException.class, malformedSchema::toCedarFormat);
178+
}
179+
}
180+
181+
@Nested
182+
@DisplayName("toJsonFormat Tests")
183+
class ToJsonFormatTests {
184+
185+
@Test
186+
@DisplayName("Should convert Cedar schema to JSON format")
187+
void testFromCedar() throws Exception {
188+
String cedarSchema = "entity User;";
189+
Schema cedarSchemaObj = new Schema(cedarSchema);
190+
JsonNode result = cedarSchemaObj.toJsonFormat();
191+
192+
String expectedJson = "{\"\":{\"entityTypes\":{\"User\":{}},\"actions\":{}}}";
193+
JsonNode expectedNode = new ObjectMapper().readTree(expectedJson);
194+
195+
assertNotNull(result, "Result should not be null");
196+
assertEquals(expectedNode, result, "JSON should match expected structure");
197+
}
198+
199+
@Test
200+
@DisplayName("Should return the same JSON schema object")
201+
void testFromJson() throws Exception {
202+
String jsonSchema = """
203+
{
204+
"": {
205+
"entityTypes": {
206+
"User": {}
207+
},
208+
"actions": {}
209+
}
210+
}
211+
""";
212+
Schema jsonSchemaObj = Schema.parse(JsonOrCedar.Json, jsonSchema);
213+
JsonNode result = jsonSchemaObj.toJsonFormat();
214+
215+
ObjectMapper mapper = new ObjectMapper();
216+
JsonNode expectedNode = mapper.readTree(jsonSchema);
217+
218+
assertNotNull(result, "Result should not be null");
219+
assertEquals(expectedNode, result, "JSON should match the original schema");
220+
}
221+
222+
@Test
223+
@DisplayName("Should throw IllegalStateException for empty schema")
224+
void testEmptySchema() {
225+
Schema emptySchema = new Schema(JsonOrCedar.Cedar, Optional.empty(), Optional.empty());
226+
Exception exception = assertThrows(IllegalStateException.class, emptySchema::toJsonFormat);
227+
assertEquals("No schema found", exception.getMessage());
228+
}
229+
230+
@Test
231+
@DisplayName("Should throw exception for malformed Cedar schema")
232+
void testMalformedSchema() {
233+
String malformedCedar = "entty User";
234+
Schema malformedSchema = new Schema(JsonOrCedar.Cedar, Optional.empty(), Optional.of(malformedCedar));
235+
assertThrows(InternalException.class, malformedSchema::toJsonFormat);
236+
}
237+
}
110238
}

0 commit comments

Comments
 (0)