diff --git a/js/src/lib/api/utils/index.ts b/js/src/lib/api/utils/index.ts index 09f7270e9..684933154 100644 --- a/js/src/lib/api/utils/index.ts +++ b/js/src/lib/api/utils/index.ts @@ -1,13 +1,15 @@ import type { Api } from "@/lib/api/types"; -import { PARENT_TAGS_TO_CHILD_TAGS } from "@/lib/api/types/complex"; +import { + PARENT_TAGS_TO_CHILD_TAGS, + TOPIC_METADATA_LIST, +} from "@/lib/api/types/complex"; import { LeetcodeTopicEnum, Tag } from "@/lib/api/types/schema"; import { TAG_METADATA_LIST, UNUSED_TAGS, NON_SCHOOL_TAGS, } from "@/lib/api/utils/metadata/tag"; -import { TOPIC_METADATA_LIST } from "@/lib/api/utils/metadata/topic"; import { ApiTypeUtils } from "@/lib/api/utils/types"; /** diff --git a/js/src/lib/api/utils/metadata/topic/index.ts b/js/src/lib/api/utils/metadata/topic/index.ts deleted file mode 100644 index 29cd75625..000000000 --- a/js/src/lib/api/utils/metadata/topic/index.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { LeetcodeTopicEnum } from "@/lib/api/types/schema"; -import { ApiTypeUtils } from "@/lib/api/utils/types"; - -export const TOPIC_METADATA_LIST: Record< - LeetcodeTopicEnum, - ApiTypeUtils.QuestionTopicTopicMetadata -> = { - STACK: { name: "Stack" }, - DATA_STREAM: { name: "Data Stream" }, - REJECTION_SAMPLING: { name: "Rejection Sampling" }, - GEOMETRY: { name: "Geometry" }, - COUNTING: { name: "Counting" }, - DESIGN: { name: "Design" }, - PROBABILITY_AND_STATISTICS: { name: "Probability and Statistics" }, - MINIMUM_SPANNING_TREE: { - name: "Minimum Spanning Tree", - aliases: ["MST"], - }, - LINE_SWEEP: { name: "Line Sweep" }, - NUMBER_THEORY: { name: "Number Theory" }, - ROLLING_HASH: { name: "Rolling Hash" }, - SEGMENT_TREE: { name: "Segment Tree" }, - BICONNECTED_COMPONENT: { name: "Biconnected Component" }, - MONOTONIC_STACK: { name: "Monotonic Stack" }, - ITERATOR: { name: "Iterator" }, - QUEUE: { name: "Queue" }, - RADIX_SORT: { name: "Radix Sort" }, - BUCKET_SORT: { name: "Bucket Sort" }, - SHELL: { name: "Shell Sort" }, - MEMOIZATION: { name: "Memoization" }, - STRING: { name: "String" }, - PREFIX_SUM: { name: "Prefix Sum" }, - CONCURRENCY: { name: "Concurrency" }, - DATABASE: { name: "Database", aliases: ["DB"] }, - SHORTEST_PATH: { name: "Shortest Path" }, - SORTING: { name: "Sorting" }, - LINKED_LIST: { name: "Linked List" }, - SLIDING_WINDOW: { name: "Sliding Window" }, - SUFFIX_ARRAY: { name: "Suffix Array" }, - DOUBLY_LINKED_LIST: { name: "Doubly Linked List" }, - SIMULATION: { name: "Simulation" }, - ORDERED_SET: { name: "Ordered Set" }, - GRAPH: { name: "Graph" }, - MATH: { name: "Math" }, - ORDERED_MAP: { name: "Ordered Map" }, - GAME_THEORY: { name: "Game Theory" }, - DYNAMIC_PROGRAMMING: { name: "Dynamic Programming", aliases: ["DP"] }, - RECURSION: { name: "Recursion" }, - MONOTONIC_QUEUE: { name: "Monotonic Queue" }, - MATRIX: { name: "Matrix" }, - RESERVOIR_SAMPLING: { name: "Reservoir Sampling" }, - MERGE_SORT: { name: "Merge Sort" }, - COMBINATORICS: { name: "Combinatorics" }, - INTERACTIVE: { name: "Interactive" }, - BINARY_TREE: { name: "Binary Tree" }, - RANDOMIZED: { name: "Randomized" }, - BITMASK: { name: "Bitmask" }, - BREADTH_FIRST_SEARCH: { - name: "Breadth-First Search", - aliases: ["BFS"], - }, - STRING_MATCHING: { name: "String Matching" }, - GREEDY: { name: "Greedy" }, - BRAINTEASER: { name: "Brainteaser" }, - BACKTRACKING: { name: "Backtracking" }, - BIT_MANIPULATION: { name: "Bit Manipulation" }, - UNION_FIND: { name: "Union-Find" }, - BINARY_SEARCH_TREE: { name: "Binary Search Tree", aliases: ["BST"] }, - TWO_POINTERS: { name: "Two Pointers" }, - ARRAY: { name: "Array" }, - DEPTH_FIRST_SEARCH: { name: "Depth-First Search", aliases: ["DFS"] }, - EULERIAN_CIRCUIT: { name: "Eulerian Circuit" }, - TREE: { name: "Tree" }, - BINARY_SEARCH: { name: "Binary Search", aliases: ["BS"] }, - STRONGLY_CONNECTED_COMPONENT: { name: "Strongly Connected Component" }, - ENUMERATION: { name: "Enumeration" }, - HEAP_PRIORITY_QUEUE: { name: "Heap / Priority Queue" }, - DIVIDE_AND_CONQUER: { name: "Divide and Conquer" }, - HASH_FUNCTION: { name: "Hash Function" }, - HASH_TABLE: { name: "Hash Table" }, - TRIE: { name: "Trie" }, - TOPOLOGICAL_SORT: { name: "Topological Sort" }, - QUICKSELECT: { name: "Quickselect" }, - BINARY_INDEXED_TREE: { name: "Binary Indexed Tree" }, - COUNTING_SORT: { name: "Counting Sort" }, - UNKNOWN: { name: "Unknown" }, -}; diff --git a/js/src/lib/api/utils/types.ts b/js/src/lib/api/utils/types.ts index 60fade26c..63fbe4d0a 100644 --- a/js/src/lib/api/utils/types.ts +++ b/js/src/lib/api/utils/types.ts @@ -1,6 +1,9 @@ import type { Api } from "@/lib/api/types"; -import { TagMetadataObject } from "@/lib/api/types/complex"; +import { + TagMetadataObject, + TopicMetadataObject, +} from "@/lib/api/types/complex"; import { Tag } from "@/lib/api/types/schema"; import { UNUSED_TAGS } from "@/lib/api/utils/metadata/tag"; @@ -42,8 +45,5 @@ export namespace ApiTypeUtils { /** * Pretty name for the given topic. */ - export type QuestionTopicTopicMetadata = { - name: string; - aliases?: string[]; - }; + export type QuestionTopicTopicMetadata = TopicMetadataObject; } diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/models/question/topic/TopicMetadataList.java b/src/main/java/org/patinanetwork/codebloom/common/db/models/question/topic/TopicMetadataList.java new file mode 100644 index 000000000..747d3d49e --- /dev/null +++ b/src/main/java/org/patinanetwork/codebloom/common/db/models/question/topic/TopicMetadataList.java @@ -0,0 +1,168 @@ +package org.patinanetwork.codebloom.common.db.models.question.topic; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public final class TopicMetadataList { + private TopicMetadataList() {} + + public static final Map ENUM_TO_TOPIC_METADATA = + Collections.unmodifiableMap(generate()); + + private static Map generate() { + return Arrays.stream(LeetcodeTopicEnum.values()) + .collect(Collectors.toMap( + topic -> topic, TopicMetadataList::buildMetadata, (a, b) -> a, LinkedHashMap::new)); + } + + private static TopicMetadataObject buildMetadata(LeetcodeTopicEnum topic) { + return switch (topic) { + case STACK -> TopicMetadataObject.builder().name("Stack").build(); + case DATA_STREAM -> + TopicMetadataObject.builder().name("Data Stream").build(); + case REJECTION_SAMPLING -> + TopicMetadataObject.builder().name("Rejection Sampling").build(); + case GEOMETRY -> TopicMetadataObject.builder().name("Geometry").build(); + case COUNTING -> TopicMetadataObject.builder().name("Counting").build(); + case DESIGN -> TopicMetadataObject.builder().name("Design").build(); + case PROBABILITY_AND_STATISTICS -> + TopicMetadataObject.builder().name("Probability and Statistics").build(); + case MINIMUM_SPANNING_TREE -> + TopicMetadataObject.builder() + .name("Minimum Spanning Tree") + .aliases(List.of("MST")) + .build(); + case LINE_SWEEP -> TopicMetadataObject.builder().name("Line Sweep").build(); + case NUMBER_THEORY -> + TopicMetadataObject.builder().name("Number Theory").build(); + case ROLLING_HASH -> + TopicMetadataObject.builder().name("Rolling Hash").build(); + case SEGMENT_TREE -> + TopicMetadataObject.builder().name("Segment Tree").build(); + case BICONNECTED_COMPONENT -> + TopicMetadataObject.builder().name("Biconnected Component").build(); + case MONOTONIC_STACK -> + TopicMetadataObject.builder().name("Monotonic Stack").build(); + case ITERATOR -> TopicMetadataObject.builder().name("Iterator").build(); + case QUEUE -> TopicMetadataObject.builder().name("Queue").build(); + case RADIX_SORT -> TopicMetadataObject.builder().name("Radix Sort").build(); + case BUCKET_SORT -> + TopicMetadataObject.builder().name("Bucket Sort").build(); + case SHELL -> TopicMetadataObject.builder().name("Shell Sort").build(); + case MEMOIZATION -> + TopicMetadataObject.builder().name("Memoization").build(); + case STRING -> TopicMetadataObject.builder().name("String").build(); + case PREFIX_SUM -> TopicMetadataObject.builder().name("Prefix Sum").build(); + case CONCURRENCY -> + TopicMetadataObject.builder().name("Concurrency").build(); + case DATABASE -> + TopicMetadataObject.builder() + .name("Database") + .aliases(List.of("DB")) + .build(); + case SHORTEST_PATH -> + TopicMetadataObject.builder().name("Shortest Path").build(); + case SORTING -> TopicMetadataObject.builder().name("Sorting").build(); + case LINKED_LIST -> + TopicMetadataObject.builder().name("Linked List").build(); + case SLIDING_WINDOW -> + TopicMetadataObject.builder().name("Sliding Window").build(); + case SUFFIX_ARRAY -> + TopicMetadataObject.builder().name("Suffix Array").build(); + case DOUBLY_LINKED_LIST -> + TopicMetadataObject.builder().name("Doubly Linked List").build(); + case SIMULATION -> TopicMetadataObject.builder().name("Simulation").build(); + case ORDERED_SET -> + TopicMetadataObject.builder().name("Ordered Set").build(); + case GRAPH -> TopicMetadataObject.builder().name("Graph").build(); + case MATH -> TopicMetadataObject.builder().name("Math").build(); + case ORDERED_MAP -> + TopicMetadataObject.builder().name("Ordered Map").build(); + case GAME_THEORY -> + TopicMetadataObject.builder().name("Game Theory").build(); + case DYNAMIC_PROGRAMMING -> + TopicMetadataObject.builder() + .name("Dynamic Programming") + .aliases(List.of("DP")) + .build(); + case RECURSION -> TopicMetadataObject.builder().name("Recursion").build(); + case MONOTONIC_QUEUE -> + TopicMetadataObject.builder().name("Monotonic Queue").build(); + case MATRIX -> TopicMetadataObject.builder().name("Matrix").build(); + case RESERVOIR_SAMPLING -> + TopicMetadataObject.builder().name("Reservoir Sampling").build(); + case MERGE_SORT -> TopicMetadataObject.builder().name("Merge Sort").build(); + case COMBINATORICS -> + TopicMetadataObject.builder().name("Combinatorics").build(); + case INTERACTIVE -> + TopicMetadataObject.builder().name("Interactive").build(); + case BINARY_TREE -> + TopicMetadataObject.builder().name("Binary Tree").build(); + case RANDOMIZED -> TopicMetadataObject.builder().name("Randomized").build(); + case BITMASK -> TopicMetadataObject.builder().name("Bitmask").build(); + case BREADTH_FIRST_SEARCH -> + TopicMetadataObject.builder() + .name("Breadth-First Search") + .aliases(List.of("BFS")) + .build(); + case STRING_MATCHING -> + TopicMetadataObject.builder().name("String Matching").build(); + case GREEDY -> TopicMetadataObject.builder().name("Greedy").build(); + case BRAINTEASER -> + TopicMetadataObject.builder().name("Brainteaser").build(); + case BACKTRACKING -> + TopicMetadataObject.builder().name("Backtracking").build(); + case BIT_MANIPULATION -> + TopicMetadataObject.builder().name("Bit Manipulation").build(); + case UNION_FIND -> TopicMetadataObject.builder().name("Union-Find").build(); + case BINARY_SEARCH_TREE -> + TopicMetadataObject.builder() + .name("Binary Search Tree") + .aliases(List.of("BST")) + .build(); + case TWO_POINTERS -> + TopicMetadataObject.builder().name("Two Pointers").build(); + case ARRAY -> TopicMetadataObject.builder().name("Array").build(); + case DEPTH_FIRST_SEARCH -> + TopicMetadataObject.builder() + .name("Depth-First Search") + .aliases(List.of("DFS")) + .build(); + case EULERIAN_CIRCUIT -> + TopicMetadataObject.builder().name("Eulerian Circuit").build(); + case TREE -> TopicMetadataObject.builder().name("Tree").build(); + case BINARY_SEARCH -> + TopicMetadataObject.builder() + .name("Binary Search") + .aliases(List.of("BS")) + .build(); + case STRONGLY_CONNECTED_COMPONENT -> + TopicMetadataObject.builder() + .name("Strongly Connected Component") + .build(); + case ENUMERATION -> + TopicMetadataObject.builder().name("Enumeration").build(); + case HEAP_PRIORITY_QUEUE -> + TopicMetadataObject.builder().name("Heap / Priority Queue").build(); + case DIVIDE_AND_CONQUER -> + TopicMetadataObject.builder().name("Divide and Conquer").build(); + case HASH_FUNCTION -> + TopicMetadataObject.builder().name("Hash Function").build(); + case HASH_TABLE -> TopicMetadataObject.builder().name("Hash Table").build(); + case TRIE -> TopicMetadataObject.builder().name("Trie").build(); + case TOPOLOGICAL_SORT -> + TopicMetadataObject.builder().name("Topological Sort").build(); + case QUICKSELECT -> + TopicMetadataObject.builder().name("Quickselect").build(); + case BINARY_INDEXED_TREE -> + TopicMetadataObject.builder().name("Binary Indexed Tree").build(); + case COUNTING_SORT -> + TopicMetadataObject.builder().name("Counting Sort").build(); + case UNKNOWN -> TopicMetadataObject.builder().name("Unknown").build(); + }; + } +} diff --git a/src/main/java/org/patinanetwork/codebloom/common/db/models/question/topic/TopicMetadataObject.java b/src/main/java/org/patinanetwork/codebloom/common/db/models/question/topic/TopicMetadataObject.java new file mode 100644 index 000000000..f96af7bd5 --- /dev/null +++ b/src/main/java/org/patinanetwork/codebloom/common/db/models/question/topic/TopicMetadataObject.java @@ -0,0 +1,17 @@ +package org.patinanetwork.codebloom.common.db.models.question.topic; + +import com.fasterxml.jackson.annotation.JsonInclude; +import java.util.List; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +public class TopicMetadataObject { + public static final String TS_TYPE = + "export type TopicMetadataObject = {\n" + " name: string;\n" + " aliases?: string[];\n" + "};\n\n"; + + private final String name; + private final List aliases; +} diff --git a/src/main/java/org/patinanetwork/codebloom/shared/tag/TagMetadataList.java b/src/main/java/org/patinanetwork/codebloom/shared/tag/TagMetadataList.java index 52eef7d8f..849fdd9f7 100644 --- a/src/main/java/org/patinanetwork/codebloom/shared/tag/TagMetadataList.java +++ b/src/main/java/org/patinanetwork/codebloom/shared/tag/TagMetadataList.java @@ -1,13 +1,22 @@ package org.patinanetwork.codebloom.shared.tag; import java.util.Arrays; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import java.util.stream.Collectors; import org.patinanetwork.codebloom.common.db.models.usertag.Tag; public class TagMetadataList { - public static final Map> ENUM_TO_STRING_VALUE_MAP = generate(); + public static final String TS_TYPE = "export type TagMetadataObject = {\n" + + " shortName: string;\n" + + " name: string;\n" + + " apiKey: string;\n" + + " alt: string;\n" + + "};\n\n"; + + public static final Map> ENUM_TO_STRING_VALUE_MAP = + Collections.unmodifiableMap(generate()); private static Map> generate() { return Arrays.stream(Tag.values()) diff --git a/src/main/java/org/patinanetwork/codebloom/utilities/generator/complex/ComplexJSTypesGenerator.java b/src/main/java/org/patinanetwork/codebloom/utilities/generator/complex/ComplexJSTypesGenerator.java index a85e4a72c..f92ddbc8f 100644 --- a/src/main/java/org/patinanetwork/codebloom/utilities/generator/complex/ComplexJSTypesGenerator.java +++ b/src/main/java/org/patinanetwork/codebloom/utilities/generator/complex/ComplexJSTypesGenerator.java @@ -1,5 +1,7 @@ package org.patinanetwork.codebloom.utilities.generator.complex; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.annotations.VisibleForTesting; import java.io.IOException; import java.nio.file.Files; @@ -10,6 +12,8 @@ import java.util.Map; import java.util.Set; import lombok.extern.slf4j.Slf4j; +import org.patinanetwork.codebloom.common.db.models.question.topic.TopicMetadataList; +import org.patinanetwork.codebloom.common.db.models.question.topic.TopicMetadataObject; import org.patinanetwork.codebloom.shared.tag.ParentTags; import org.patinanetwork.codebloom.shared.tag.TagMetadataList; import org.springframework.beans.factory.annotation.Autowired; @@ -23,6 +27,11 @@ @Component @Slf4j public class ComplexJSTypesGenerator implements CommandLineRunner { + private static final String EXPORT_TYPE_PREFIX = "export type "; + private static final String TYPE_BODY_OPEN = " = {\n"; + private static final String CLOSE_BLOCK = "};\n\n"; + private static final String RECORD_BODY_OPEN = "> = {\n"; + @Autowired private ApplicationContext applicationContext; @@ -43,7 +52,10 @@ public void run(final String... args) throws Exception { } } - private void generateAll() throws IOException { + private final ObjectMapper objectMapper = + new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL); + + private void generateAll() throws Exception { generateTypes(); generate(Generator.builder() .name("PARENT_TAGS_TO_CHILD_TAGS") @@ -55,30 +67,38 @@ private void generateAll() throws IOException { .data(TagMetadataList.ENUM_TO_STRING_VALUE_MAP) .dataShape(DataShape.ENUM_TO_TAG_METADATA) .build()); + generate(Generator.builder() + .name("TOPIC_METADATA_LIST") + .data(TopicMetadataList.ENUM_TO_TOPIC_METADATA) + .dataShape(DataShape.ENUM_TO_OBJECT) + .objectClass("TopicMetadataObject") + .build()); } private void generateTypes() { generateTagMetadataObjectType(); + generateTopicMetadataObjectType(); } private void generateTagMetadataObjectType() { - String typeName = "TagMetadataObject"; - tsContent.append("export type ").append(typeName).append(" = {\n"); - tsContent.append(" shortName: string;\n"); - tsContent.append(" name: string;\n"); - tsContent.append(" apiKey: string;\n"); - tsContent.append(" alt: string;\n"); - tsContent.append("};\n\n"); - log.info("Generated type: {}", typeName); + tsContent.append(TagMetadataList.TS_TYPE); + log.info("Generated type: TagMetadataObject"); } - private void generate(Generator generator) throws IOException { + private void generateTopicMetadataObjectType() { + tsContent.append(TopicMetadataObject.TS_TYPE); + log.info("Generated type: TopicMetadataObject"); + } + + private void generate(Generator generator) throws Exception { if (generator.getDataShape() == DataShape.ENUM_TO_ENUM_LIST) { generateEnumToListOfEnums(generator); } else if (generator.getDataShape() == DataShape.ENUM_TO_STRING_VALUE_MAP) { generateEnumToStringValueMap(generator); } else if (generator.getDataShape() == DataShape.ENUM_TO_TAG_METADATA) { generateEnumToTagMetadata(generator); + } else if (generator.getDataShape() == DataShape.ENUM_TO_OBJECT) { + generateEnumToObject(generator); } } @@ -163,11 +183,11 @@ void generateEnumToStringValueMap(Generator generator) { boolean hasTypeName = typeName != null && !typeName.isEmpty(); if (hasTypeName) { - tsContent.append("export type ").append(typeName).append(" = {\n"); + tsContent.append(EXPORT_TYPE_PREFIX).append(typeName).append(TYPE_BODY_OPEN); for (String fieldName : fieldNames) { tsContent.append(" ").append(fieldName).append(": string;\n"); } - tsContent.append("};\n\n"); + tsContent.append(CLOSE_BLOCK); } String valueType = hasTypeName ? typeName : "{ [key: string]: string }"; @@ -179,7 +199,7 @@ void generateEnumToStringValueMap(Generator generator) { .append(enumClassName) .append(", ") .append(valueType) - .append("> = {\n"); + .append(RECORD_BODY_OPEN); for (Map.Entry> entry : data.entrySet()) { Enum key = (Enum) entry.getKey(); @@ -237,7 +257,7 @@ void generateEnumToTagMetadata(Generator generator) { .append(enumClassName) .append(", ") .append(typeName) - .append("> = {\n"); + .append(RECORD_BODY_OPEN); for (Map.Entry> entry : data.entrySet()) { Enum key = (Enum) entry.getKey(); @@ -267,6 +287,62 @@ void generateEnumToTagMetadata(Generator generator) { log.info("Generated constant: {}", generator.getName()); } + @VisibleForTesting + void generateEnumToObject(Generator generator) throws Exception { + Map data = (Map) generator.getData(); + + if (data.isEmpty()) { + log.warn("Empty map provided for object generation"); + return; + } + + Object firstKey = data.keySet().iterator().next(); + if (!(firstKey instanceof Enum)) { + throw new IllegalArgumentException("Expected Enum key, got: " + firstKey.getClass()); + } + + Enum enumKey = (Enum) firstKey; + String enumClassName = enumKey.getDeclaringClass().getSimpleName(); + imports.add(enumClassName); + + String objectClass = generator.getObjectClass(); + + tsContent + .append("export const ") + .append(generator.getName()) + .append(": Record<") + .append(enumClassName) + .append(", ") + .append(objectClass) + .append(RECORD_BODY_OPEN); + + for (Map.Entry entry : data.entrySet()) { + Enum key = (Enum) entry.getKey(); + String jsonValue = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(entry.getValue()); + String tsValue = jsonToTypeScript(jsonValue); + + String[] lines = tsValue.split("\n"); + tsContent.append(" ").append(key.name()).append(": ").append(lines[0]); + + for (int i = 1; i < lines.length; i++) { + tsContent.append("\n ").append(lines[i]); + } + + tsContent.append(",\n"); + } + + tsContent.append(CLOSE_BLOCK); + + log.info("Generated constant: {}", generator.getName()); + } + + /** + * Converts Jackson pretty-printed JSON to TypeScript object literal style (unquoted keys, no space before colon). + */ + private String jsonToTypeScript(String json) { + return json.replaceAll("\"(\\w+)\" : ", "$1: "); + } + private void writeToFile() throws IOException { StringBuilder finalOutput = new StringBuilder(); diff --git a/src/main/java/org/patinanetwork/codebloom/utilities/generator/complex/DataShape.java b/src/main/java/org/patinanetwork/codebloom/utilities/generator/complex/DataShape.java index eb76e378c..b89e2eb0b 100644 --- a/src/main/java/org/patinanetwork/codebloom/utilities/generator/complex/DataShape.java +++ b/src/main/java/org/patinanetwork/codebloom/utilities/generator/complex/DataShape.java @@ -9,4 +9,7 @@ public enum DataShape { /** Expects {@code Map>} where T is an enum class, specifically for TagMetadataObject. */ ENUM_TO_TAG_METADATA, + + /** Expects {@code Map} where T is an enum class. Uses Jackson to serialize values to JSON. */ + ENUM_TO_OBJECT, } diff --git a/src/main/java/org/patinanetwork/codebloom/utilities/generator/complex/Generator.java b/src/main/java/org/patinanetwork/codebloom/utilities/generator/complex/Generator.java index aa70e8998..18a3b4da3 100644 --- a/src/main/java/org/patinanetwork/codebloom/utilities/generator/complex/Generator.java +++ b/src/main/java/org/patinanetwork/codebloom/utilities/generator/complex/Generator.java @@ -15,4 +15,7 @@ public class Generator { private final DataShape dataShape; /** Optional name for the generated TypeScript value type (used by some DataShapes). */ private final String typeName; + + /** Optional TypeScript type name for the value type (used by ENUM_TO_OBJECT). */ + private final String objectClass; } diff --git a/src/test/java/org/patinanetwork/codebloom/utilities/generator/complex/ComplexJSTypesGeneratorTest.java b/src/test/java/org/patinanetwork/codebloom/utilities/generator/complex/ComplexJSTypesGeneratorTest.java index a0ca93354..36e3b04d8 100644 --- a/src/test/java/org/patinanetwork/codebloom/utilities/generator/complex/ComplexJSTypesGeneratorTest.java +++ b/src/test/java/org/patinanetwork/codebloom/utilities/generator/complex/ComplexJSTypesGeneratorTest.java @@ -5,6 +5,8 @@ import java.util.LinkedHashMap; import java.util.Map; import org.junit.jupiter.api.Test; +import org.patinanetwork.codebloom.common.db.models.question.topic.LeetcodeTopicEnum; +import org.patinanetwork.codebloom.common.db.models.question.topic.TopicMetadataList; import org.patinanetwork.codebloom.common.db.models.usertag.Tag; import org.patinanetwork.codebloom.shared.tag.TagMetadataList; @@ -131,4 +133,54 @@ void generateEnumToTagMetadataThrowsOnNonEnumKey() { .dataShape(DataShape.ENUM_TO_TAG_METADATA) .build())); } + + @Test + void generateEnumToObjectRunsWithoutException() { + var generator = new ComplexJSTypesGenerator(); + assertDoesNotThrow(() -> generator.generateEnumToObject(Generator.builder() + .name("TOPIC_METADATA_LIST") + .data(TopicMetadataList.ENUM_TO_TOPIC_METADATA) + .dataShape(DataShape.ENUM_TO_OBJECT) + .objectClass("TopicMetadataObject") + .build())); + } + + @Test + void generateEnumToObjectEmptyMapDoesNotThrow() { + var generator = new ComplexJSTypesGenerator(); + assertDoesNotThrow(() -> generator.generateEnumToObject(Generator.builder() + .name("TOPIC_METADATA_LIST") + .data(new LinkedHashMap<>()) + .dataShape(DataShape.ENUM_TO_OBJECT) + .objectClass("TopicMetadataObject") + .build())); + } + + @Test + void generateEnumToObjectThrowsOnNonEnumKey() { + var invalidData = new LinkedHashMap(); + invalidData.put("not-an-enum", Map.of()); + var generator = new ComplexJSTypesGenerator(); + assertThrows( + IllegalArgumentException.class, + () -> generator.generateEnumToObject(Generator.builder() + .name("TEST") + .data(invalidData) + .dataShape(DataShape.ENUM_TO_OBJECT) + .objectClass("TopicMetadataObject") + .build())); + } + + @Test + void topicMetadataListContainsAllTopics() { + assertEquals(LeetcodeTopicEnum.values().length, TopicMetadataList.ENUM_TO_TOPIC_METADATA.size()); + } + + @Test + void topicMetadataListAllHaveNonEmptyName() { + for (var metadata : TopicMetadataList.ENUM_TO_TOPIC_METADATA.values()) { + assertNotNull(metadata.getName()); + assertFalse(metadata.getName().isBlank()); + } + } }