From e0f955e671bb197fe6e4d65967ee6226c04152b2 Mon Sep 17 00:00:00 2001 From: sibmaks Date: Fri, 16 May 2025 23:28:37 +0300 Subject: [PATCH 1/8] [Web App Mock] Integrate with kafka #55 --- gradle/libs.versions.toml | 4 + .../src/pages/mock/CodeDocumentation.css | 18 ++ .../src/pages/mock/CodeDocumentation.tsx | 188 +++++++++++++++++- web-app/build.gradle.kts | 2 + .../impl/graalvm/GraalVMConverter.java | 19 ++ .../impl/graalvm/GraalVMRequestHandler.java | 13 +- .../impl/graalvm/dto/GraalVMMocksContext.java | 4 +- ...> WebApplicationMocksGraalVMSessions.java} | 4 +- .../graalvm/dto/kafka/PublishMessageRq.java | 20 ++ .../graalvm/dto/kafka/PublishMessageRs.java | 16 ++ .../dto/kafka/PublishTemplateMessageRq.java | 20 ++ .../WebApplicationMocksGraalVMKafka.java | 83 ++++++++ .../graalvm/js/JavaScriptRequestHandler.java | 8 +- .../graalvm/python/PythonRequestHandler.java | 8 +- 14 files changed, 383 insertions(+), 24 deletions(-) create mode 100644 web-app-frontend/src/pages/mock/CodeDocumentation.css rename web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/{GraalVMSessions.java => WebApplicationMocksGraalVMSessions.java} (95%) create mode 100644 web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishMessageRq.java create mode 100644 web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishMessageRs.java create mode 100644 web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishTemplateMessageRq.java create mode 100644 web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/WebApplicationMocksGraalVMKafka.java diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f8047df..f8cafc9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,6 +22,8 @@ async-embedded = "0.0.10" session-embedded = "0.0.10" storage-embedded = "0.1.16" +kafka-client-service = "0.0.16" + [libraries] common-api = { module = "com.github.sibdevtools:api-common", version.ref = "common-api" } localization-api = { module = "com.github.sibdevtools:api-localization", version.ref = "localization-api" } @@ -35,6 +37,8 @@ async-embedded = { module = "com.github.sibdevtools:service-async-embedded", ver session-embedded = { module = "com.github.sibdevtools:service-session-embedded", version.ref = "session-embedded" } storage-embedded = { module = "com.github.sibdevtools:service-storage-embedded", version.ref = "storage-embedded" } +kafka-client-service = { module = "com.github.sibdevtools:service-kafka-client", version.ref = "kafka-client-service" } + spring-openapi-starter = { module = "org.springdoc:springdoc-openapi-starter-webmvc-ui", version.ref = "spring-openapi-doc" } graalvm-api = { module = "org.graalvm.truffle:truffle-api", version.ref = "graalvm" } diff --git a/web-app-frontend/src/pages/mock/CodeDocumentation.css b/web-app-frontend/src/pages/mock/CodeDocumentation.css new file mode 100644 index 0000000..4b6650f --- /dev/null +++ b/web-app-frontend/src/pages/mock/CodeDocumentation.css @@ -0,0 +1,18 @@ +.code-clipboard-btn { + position: absolute; + top: 0; + right: 0; + z-index: 1; + opacity: 0.25; + filter: alpha(opacity=25); /* For IE8 and earlier */; +} + +.code-clipboard-btn:hover { + opacity: 0.8; + filter: alpha(opacity=80); /* For IE8 and earlier */; +} + +.code-clipboard-btn:active { + opacity: 1; + filter: alpha(opacity=100); /* For IE8 and earlier */; +} diff --git a/web-app-frontend/src/pages/mock/CodeDocumentation.tsx b/web-app-frontend/src/pages/mock/CodeDocumentation.tsx index 7968336..faa8197 100644 --- a/web-app-frontend/src/pages/mock/CodeDocumentation.tsx +++ b/web-app-frontend/src/pages/mock/CodeDocumentation.tsx @@ -1,10 +1,11 @@ -import React from 'react'; -import { Accordion, Table } from 'react-bootstrap'; +import React, { useState } from 'react'; +import { Accordion, Button, Table } from 'react-bootstrap'; import SyntaxHighlighter from 'react-syntax-highlighter'; import { docco } from 'react-syntax-highlighter/dist/esm/styles/hljs'; import 'react-syntax-highlighter/dist/esm/languages/hljs/javascript'; import 'react-syntax-highlighter/dist/esm/languages/hljs/python'; - +import { Task01Icon, TaskDone01Icon } from 'hugeicons-react'; +import './CodeDocumentation.css'; type ExampleCode = { description: string, @@ -528,6 +529,127 @@ session.remove("sectionKey", "attributeKey1")` }, ]; +const kafkaExamples = [ + { + description: 'Get Kafka service', + implementations: { + javascript: `const kafka = wam.kafka();`, + python: `kafka = wam.kafka()` + } + }, +]; + +const kafkaPublishingExamples = [ + { + description: 'Publish message into group', + implementations: { + javascript: `const publishRq = { + groupCode: "kafkaGroupCode", + topic: "topic-to-publish", + partition: 0, // optional + timestamp: 0, // optional + key: [48, 49], // bytes array, optional + value: [48, 49], // bytes array, optional + headers: { + headerKey: [48, 49] // bytes array, optional + }, + maxTimeout: 30000 // optional +}; +const publishRs = kafka.publish(publishRq);`, + python: `publishRq = { + "groupCode": "kafkaGroupCode", + "topic": "topic-to-publish", + "partition": 0, # optional + "timestamp": 0, # optional + "key": [48, 49], # bytes array, optional + "value": [48, 49], # bytes array, optional + "headers": { + "headerKey": [48, 49] # bytes array, optional + }, + "maxTimeout": 30000 # optional +} +publishRs = kafka.publish(publishRq)` + } + }, + { + description: 'Publish template message into group', + implementations: { + javascript: `const publishRq = { + groupCode: "kafkaGroupCode", + topic: "topic-to-publish", + templateCode: "template-code", + partition: 0, // optional + timestamp: 0, // optional + key: [48, 49], // bytes array, optional + input: { // input depends on template + param1: "value", + param2: 123 + }, + headers: { + headerKey: [48, 49] // bytes array, optional + }, + maxTimeout: 30000 // optional +}; +const publishRs = kafka.publishTemplate(publishRq);`, + python: `publishRq = { + "groupCode": "kafkaGroupCode", + "topic": "topic-to-publish", + templateCode: "template-code", + "partition": 0, # optional + "timestamp": 0, # optional + "key": [48, 49], # bytes array, optional + "input": { # input depends on template + "param1": "value", + "param2": 123 + }, + "headers": { + "headerKey": [48, 49] # bytes array, optional + }, + "maxTimeout": 30000 # optional +} +publishRs = kafka.publishTemplate(publishRq)` + } + }, +]; + +const kafkaPublishingResponseExamples = [ + { + description: 'Message offset', + implementations: { + javascript: `const offset = publishRs.offset();`, + python: `offset = publishRs.offset()`, + } + }, + { + description: 'Message timestamp', + implementations: { + javascript: `const timestamp = publishRs.timestamp();`, + python: `timestamp = publishRs.timestamp()`, + } + }, + { + description: 'Message serialized key size', + implementations: { + javascript: `const serializedKeySize = publishRs.serializedKeySize();`, + python: `serializedKeySize = publishRs.serializedKeySize()`, + } + }, + { + description: 'Message serialized value size', + implementations: { + javascript: `const serializedValueSize = publishRs.serializedValueSize();`, + python: `serializedValueSize = publishRs.serializedValueSize()`, + } + }, + { + description: 'Message partition', + implementations: { + javascript: `const partition = publishRs.partition();`, + python: `partition = publishRs.partition()`, + } + }, +]; + const allExamples: ExampleSection = { name: 'Examples', sections: [ @@ -585,6 +707,22 @@ const allExamples: ExampleSection = { ] } ] + }, + { + name: 'Kafka', + examples: kafkaExamples, + sections: [ + { + name: 'Publishing', + examples: kafkaPublishingExamples, + sections: [ + { + name: 'Publish result', + examples: kafkaPublishingResponseExamples + } + ] + } + ] } ] }; @@ -593,6 +731,8 @@ const CodeDocumentation: React.FC<{ mode: 'javascript' | 'python', section?: Exa mode, section = allExamples }) => { + const [copied, setCopied] = useState>([]); + return ( @@ -614,14 +754,44 @@ const CodeDocumentation: React.FC<{ mode: 'javascript' | 'python', section?: Exa {it.description} {'implementation' in it && ( - - {it.implementation} - +
+ + + {it.implementation} + +
)} {'implementations' in it && ( - - {it.implementations[mode]} - +
+ + + {it.implementations[mode]} + +
)} diff --git a/web-app/build.gradle.kts b/web-app/build.gradle.kts index 2acc3bc..c66b227 100644 --- a/web-app/build.gradle.kts +++ b/web-app/build.gradle.kts @@ -43,6 +43,8 @@ dependencies { implementation(libs.bundles.service.api) + implementation(libs.kafka.client.service) + testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.springframework.boot:spring-boot-starter-web") diff --git a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/GraalVMConverter.java b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/GraalVMConverter.java index 77a0eaa..5f24818 100644 --- a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/GraalVMConverter.java +++ b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/GraalVMConverter.java @@ -66,4 +66,23 @@ public static Map> convertSections(Map convertInput(Map input) { + if (input == null) { + return Collections.emptyMap(); + } + var serialized = new LinkedHashMap(); + + for (var attributeEntry : input.entrySet()) { + var key = attributeEntry.getKey(); + var value = attributeEntry.getValue(); + var serializedValue = toSerializable(value); + if (serializedValue == null) { + log.warn("Skipped unsupported input attribute '{}', type: {}", key, value.getClass().getName()); + continue; + } + serialized.put(key, serializedValue); + } + + return serialized; + } } diff --git a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/GraalVMRequestHandler.java b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/GraalVMRequestHandler.java index 01539f1..cd259ba 100644 --- a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/GraalVMRequestHandler.java +++ b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/GraalVMRequestHandler.java @@ -1,15 +1,12 @@ package com.github.sibdevtools.web.app.mocks.service.handler.impl.graalvm; import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.sibdevtools.session.api.service.SessionService; import com.github.sibdevtools.storage.api.service.StorageService; import com.github.sibdevtools.web.app.mocks.entity.HttpMockEntity; import com.github.sibdevtools.web.app.mocks.service.handler.RequestHandler; import com.github.sibdevtools.web.app.mocks.service.handler.impl.CommonResponsePreparer; -import com.github.sibdevtools.web.app.mocks.service.handler.impl.graalvm.dto.GraalVMMocksContext; -import com.github.sibdevtools.web.app.mocks.service.handler.impl.graalvm.dto.GraalVMRequest; -import com.github.sibdevtools.web.app.mocks.service.handler.impl.graalvm.dto.GraalVMResponse; -import com.github.sibdevtools.web.app.mocks.service.handler.impl.graalvm.dto.GraalVMSessions; +import com.github.sibdevtools.web.app.mocks.service.handler.impl.graalvm.dto.*; +import com.github.sibdevtools.web.app.mocks.service.handler.impl.graalvm.dto.kafka.WebApplicationMocksGraalVMKafka; import jakarta.annotation.Nonnull; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -30,7 +27,8 @@ public abstract class GraalVMRequestHandler implements RequestHandler { protected final String language; protected final StorageService storageService; - protected final SessionService sessionService; + protected final WebApplicationMocksGraalVMSessions graalVMSessions; + protected final WebApplicationMocksGraalVMKafka graalVMKafka; protected final ObjectMapper objectMapper; protected final CommonResponsePreparer commonResponsePreparer; @@ -51,7 +49,8 @@ public void handle(@Nonnull String path, var context = GraalVMMocksContext.builder() .request(new GraalVMRequest(objectMapper, path, rq)) .response(new GraalVMResponse(objectMapper, rs)) - .sessions(new GraalVMSessions(sessionService)) + .sessions(graalVMSessions) + .kafka(graalVMKafka) .build(); try (var js = Context.newBuilder(language) diff --git a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/GraalVMMocksContext.java b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/GraalVMMocksContext.java index 11a5402..4efa4e8 100644 --- a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/GraalVMMocksContext.java +++ b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/GraalVMMocksContext.java @@ -1,5 +1,6 @@ package com.github.sibdevtools.web.app.mocks.service.handler.impl.graalvm.dto; +import com.github.sibdevtools.web.app.mocks.service.handler.impl.graalvm.dto.kafka.WebApplicationMocksGraalVMKafka; import lombok.Builder; import org.graalvm.polyglot.HostAccess; @@ -10,5 +11,6 @@ @Builder public record GraalVMMocksContext(@HostAccess.Export GraalVMRequest request, @HostAccess.Export GraalVMResponse response, - @HostAccess.Export GraalVMSessions sessions) { + @HostAccess.Export WebApplicationMocksGraalVMSessions sessions, + @HostAccess.Export WebApplicationMocksGraalVMKafka kafka) { } diff --git a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/GraalVMSessions.java b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/WebApplicationMocksGraalVMSessions.java similarity index 95% rename from web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/GraalVMSessions.java rename to web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/WebApplicationMocksGraalVMSessions.java index e2d092c..36fa41c 100644 --- a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/GraalVMSessions.java +++ b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/WebApplicationMocksGraalVMSessions.java @@ -9,6 +9,7 @@ import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.graalvm.polyglot.HostAccess; +import org.springframework.stereotype.Component; import java.util.List; import java.util.Map; @@ -18,8 +19,9 @@ * @since 0.0.1 */ @Slf4j +@Component @AllArgsConstructor -public class GraalVMSessions { +public class WebApplicationMocksGraalVMSessions { private final SessionService sessionService; @HostAccess.Export diff --git a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishMessageRq.java b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishMessageRq.java new file mode 100644 index 0000000..d2368b4 --- /dev/null +++ b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishMessageRq.java @@ -0,0 +1,20 @@ +package com.github.sibdevtools.web.app.mocks.service.handler.impl.graalvm.dto.kafka; + +import java.util.Map; + +/** + * @author sibmaks + * @since 0.0.22 + */ +public record PublishMessageRq( + String groupCode, + String topic, + //asd + Integer partition, + Long timestamp, + byte[] key, + byte[] value, + Map headers, + Integer maxTimeout +) { +} diff --git a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishMessageRs.java b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishMessageRs.java new file mode 100644 index 0000000..ecb37fa --- /dev/null +++ b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishMessageRs.java @@ -0,0 +1,16 @@ +package com.github.sibdevtools.web.app.mocks.service.handler.impl.graalvm.dto.kafka; + +import org.graalvm.polyglot.HostAccess; + +/** + * @author sibmaks + * @since 0.0.22 + */ +public record PublishMessageRs( + @HostAccess.Export long offset, + @HostAccess.Export long timestamp, + @HostAccess.Export int serializedKeySize, + @HostAccess.Export int serializedValueSize, + @HostAccess.Export int partition +) { +} diff --git a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishTemplateMessageRq.java b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishTemplateMessageRq.java new file mode 100644 index 0000000..5a4eb05 --- /dev/null +++ b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishTemplateMessageRq.java @@ -0,0 +1,20 @@ +package com.github.sibdevtools.web.app.mocks.service.handler.impl.graalvm.dto.kafka; + +import java.util.Map; + +/** + * @author sibmaks + * @since 0.0.22 + */ +public record PublishTemplateMessageRq( + String groupCode, + String topic, + String templateCode, + Integer partition, + Long timestamp, + byte[] key, + Map input, + Map headers, + Integer maxTimeout +) { +} diff --git a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/WebApplicationMocksGraalVMKafka.java b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/WebApplicationMocksGraalVMKafka.java new file mode 100644 index 0000000..a5bdef1 --- /dev/null +++ b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/WebApplicationMocksGraalVMKafka.java @@ -0,0 +1,83 @@ +package com.github.sibdevtools.web.app.mocks.service.handler.impl.graalvm.dto.kafka; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.sibdevtools.service.kafka.client.api.rq.SendTemplateMessageRq; +import com.github.sibdevtools.service.kafka.client.service.BootstrapGroupService; +import com.github.sibdevtools.service.kafka.client.service.MessageConsumerService; +import com.github.sibdevtools.service.kafka.client.service.MessagePublisherService; +import com.github.sibdevtools.service.kafka.client.service.TemplateMessageService; +import com.github.sibdevtools.web.app.mocks.service.handler.impl.graalvm.GraalVMConverter; +import jakarta.annotation.Nonnull; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.graalvm.polyglot.HostAccess; +import org.springframework.stereotype.Component; + +/** + * @author sibmaks + * @since 0.0.22 + */ +@Slf4j +@Component +@AllArgsConstructor +public class WebApplicationMocksGraalVMKafka { + protected final ObjectMapper objectMapper; + protected final BootstrapGroupService bootstrapGroupService; + protected final MessageConsumerService messageConsumerService; + protected final MessagePublisherService messagePublisherService; + protected final TemplateMessageService templateMessageService; + + @HostAccess.Export + public PublishMessageRs publish(@Nonnull Object arg) { + var rq = objectMapper.convertValue(arg, PublishMessageRq.class); + var bootstrapGroup = bootstrapGroupService.getByCode(rq.groupCode()); + return messagePublisherService.sendMessage( + bootstrapGroup.getId(), + rq.topic(), + rq.partition(), + rq.timestamp(), + rq.key(), + rq.value(), + rq.headers(), + rq.maxTimeout() + ) + .map(it -> new PublishMessageRs( + it.getOffset(), + it.getTimestamp(), + it.getSerializedKeySize(), + it.getSerializedValueSize(), + it.getPartition() + )) + .orElse(null); + } + + @HostAccess.Export + public PublishMessageRs publishTemplate(@Nonnull Object arg) { + var rq = objectMapper.convertValue(arg, PublishTemplateMessageRq.class); + var bootstrapGroup = bootstrapGroupService.getByCode(rq.groupCode()); + var templateRsDto = templateMessageService.getByCode(rq.templateCode()); + return templateMessageService.send( + templateRsDto.getId(), + new SendTemplateMessageRq( + bootstrapGroup.getId(), + rq.topic(), + rq.partition(), + rq.timestamp(), + rq.key(), + GraalVMConverter.convertInput(rq.input()), + rq.headers(), + rq.maxTimeout() + ) + ) + .map(it -> new PublishMessageRs( + it.getOffset(), + it.getTimestamp(), + it.getSerializedKeySize(), + it.getSerializedValueSize(), + it.getPartition() + )) + .orElse(null); + } + + +} diff --git a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/js/JavaScriptRequestHandler.java b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/js/JavaScriptRequestHandler.java index f969790..6d5b0da 100644 --- a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/js/JavaScriptRequestHandler.java +++ b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/js/JavaScriptRequestHandler.java @@ -1,10 +1,11 @@ package com.github.sibdevtools.web.app.mocks.service.handler.impl.graalvm.js; import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.sibdevtools.session.api.service.SessionService; import com.github.sibdevtools.storage.api.service.StorageService; import com.github.sibdevtools.web.app.mocks.service.handler.impl.CommonResponsePreparer; import com.github.sibdevtools.web.app.mocks.service.handler.impl.graalvm.GraalVMRequestHandler; +import com.github.sibdevtools.web.app.mocks.service.handler.impl.graalvm.dto.WebApplicationMocksGraalVMSessions; +import com.github.sibdevtools.web.app.mocks.service.handler.impl.graalvm.dto.kafka.WebApplicationMocksGraalVMKafka; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -20,11 +21,12 @@ public class JavaScriptRequestHandler extends GraalVMRequestHandler { @Autowired public JavaScriptRequestHandler(StorageService storageService, - SessionService sessionService, + WebApplicationMocksGraalVMSessions graalVMSessions, + WebApplicationMocksGraalVMKafka graalVMKafka, @Qualifier("webAppMocksObjectMapper") ObjectMapper objectMapper, CommonResponsePreparer commonResponsePreparer) { - super("js", storageService, sessionService, objectMapper, commonResponsePreparer); + super("js", storageService, graalVMSessions, graalVMKafka, objectMapper, commonResponsePreparer); } @Override diff --git a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/python/PythonRequestHandler.java b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/python/PythonRequestHandler.java index 64883a2..de8b06c 100644 --- a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/python/PythonRequestHandler.java +++ b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/python/PythonRequestHandler.java @@ -1,10 +1,11 @@ package com.github.sibdevtools.web.app.mocks.service.handler.impl.graalvm.python; import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.sibdevtools.session.api.service.SessionService; import com.github.sibdevtools.storage.api.service.StorageService; import com.github.sibdevtools.web.app.mocks.service.handler.impl.CommonResponsePreparer; import com.github.sibdevtools.web.app.mocks.service.handler.impl.graalvm.GraalVMRequestHandler; +import com.github.sibdevtools.web.app.mocks.service.handler.impl.graalvm.dto.WebApplicationMocksGraalVMSessions; +import com.github.sibdevtools.web.app.mocks.service.handler.impl.graalvm.dto.kafka.WebApplicationMocksGraalVMKafka; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -20,11 +21,12 @@ public class PythonRequestHandler extends GraalVMRequestHandler { @Autowired public PythonRequestHandler(StorageService storageService, - SessionService sessionService, + WebApplicationMocksGraalVMSessions graalVMSessions, + WebApplicationMocksGraalVMKafka graalVMKafka, @Qualifier("webAppMocksObjectMapper") ObjectMapper objectMapper, CommonResponsePreparer commonResponsePreparer) { - super("python", storageService, sessionService, objectMapper, commonResponsePreparer); + super("python", storageService, graalVMSessions, graalVMKafka, objectMapper, commonResponsePreparer); } @Override From 22b850e6af18926bdd6035d7a40816821418a5f3 Mon Sep 17 00:00:00 2001 From: sibmaks Date: Sat, 17 May 2025 17:52:32 +0300 Subject: [PATCH 2/8] Change path info hint --- web-app-frontend/src/pages/mock/MockForm.tsx | 122 ++++++++++--------- 1 file changed, 63 insertions(+), 59 deletions(-) diff --git a/web-app-frontend/src/pages/mock/MockForm.tsx b/web-app-frontend/src/pages/mock/MockForm.tsx index 4ae8c50..4425daf 100644 --- a/web-app-frontend/src/pages/mock/MockForm.tsx +++ b/web-app-frontend/src/pages/mock/MockForm.tsx @@ -1,11 +1,11 @@ -import React from 'react'; -import { Button, Col, Container, Form, InputGroup, OverlayTrigger, Row, Table, Tooltip } from 'react-bootstrap'; +import React, { useState } from 'react'; +import { Button, Col, Container, Form, InputGroup, Row, Table } from 'react-bootstrap'; import { methods, MockType, mockTypes, statusCodes } from '../../const/common.const'; import HttpHeadersForm from '../../components/HttpHeadersForm'; import StaticMockContent from '../../components/StaticMockContent'; import GraalVMMockContent from '../../components/GraalVMMockContent'; import StaticFileMockContent from '../../components/StaticFileMockContent'; -import { ArrowLeft01Icon, FloppyDiskIcon } from 'hugeicons-react'; +import { ArrowLeft01Icon, FloppyDiskIcon, InformationCircleIcon } from 'hugeicons-react'; import { Loader } from '../../components/Loader'; import './MockForm.css'; import CodeDocumentation from './CodeDocumentation'; @@ -29,42 +29,12 @@ export const MockForm: React.FC = ({ isEditMode, navigateBack }) => { + const [showPathHint, setShowPathHint] = useState(false); const onMockTypeChange = (e: React.ChangeEvent) => { - setModifyingMock({...modifyingMock, type: e.target.value as MockType}) + setModifyingMock({ ...modifyingMock, type: e.target.value as MockType }); }; - const pathTooltip = ( - - - - - - - - - - - - - - - - - - - - - - - - - - -
WildcardDescription
?Matches exactly one character.
*Matches zero or more characters.
**Matches zero or more 'directories' in a path
{spring:[a-z]+}Matches regExp [a-z]+ as a path variable named "spring"
-
- ); - return ( @@ -98,7 +68,7 @@ export const MockForm: React.FC = ({ type="text" id={'nameInput'} value={modifyingMock.name} - onChange={(e) => setModifyingMock({...modifyingMock, name: e.target.value})} + onChange={(e) => setModifyingMock({ ...modifyingMock, name: e.target.value })} required /> @@ -113,7 +83,7 @@ export const MockForm: React.FC = ({ setModifyingMock({...modifyingMock, method: it.value})} + onChange={it => setModifyingMock({ ...modifyingMock, method: it.value })} required={true} suggestions={methods.map(it => { return { key: it, value: it }; @@ -128,22 +98,54 @@ export const MockForm: React.FC = ({ Path / - setModifyingMock({ ...modifyingMock, path: e.target.value })} + placeholder="Ant pattern or path" + required + /> + + + {/* Path Hint */} + {showPathHint && ( + + + + + + + + + + + + + + + + + + + + + + + + + + +
WildcardDescription
?Matches exactly one character
*Matches zero or more characters
**Matches zero or more 'directories' in a path
{spring:[a-z]+}Matches regExp [a-z]+ as a path variable named "spring"
+
+ )}
@@ -152,7 +154,7 @@ export const MockForm: React.FC = ({ Http Headers setModifyingMock({...modifyingMock, meta: it})} + setMeta={it => setModifyingMock({ ...modifyingMock, meta: it })} /> @@ -170,10 +172,12 @@ export const MockForm: React.FC = ({ type={'number'} mode={'free'} value={modifyingMock.meta['STATUS_CODE']} - onChange={it => setModifyingMock({...modifyingMock, meta: { - ...modifyingMock.meta, + onChange={it => setModifyingMock({ + ...modifyingMock, meta: { + ...modifyingMock.meta, STATUS_CODE: `${it.key ?? it.value}` - }})} + } + })} required={true} suggestions={Array.from(statusCodes).map(([key, value]) => { return { key: `${key}`, value: `${key}: ${value}` }; @@ -196,7 +200,7 @@ export const MockForm: React.FC = ({ id={'delayInput'} min={0} value={`${modifyingMock.delay}`} - onChange={e => setModifyingMock({...modifyingMock, delay: +e.target.value})} + onChange={e => setModifyingMock({ ...modifyingMock, delay: +e.target.value })} required /> ms @@ -235,15 +239,15 @@ export const MockForm: React.FC = ({ {modifyingMock.type === 'STATIC' && ( setModifyingMock({...modifyingMock, content: it})} + setContent={it => setModifyingMock({ ...modifyingMock, content: it })} meta={modifyingMock.meta} - setMeta={it => setModifyingMock({...modifyingMock, meta: it})} + setMeta={it => setModifyingMock({ ...modifyingMock, meta: it })} creation={!isEditMode} /> )} {modifyingMock.type === 'STATIC_FILE' && setModifyingMock({...modifyingMock, content: it})} + setContent={it => setModifyingMock({ ...modifyingMock, content: it })} isEditMode={isEditMode} />} @@ -251,7 +255,7 @@ export const MockForm: React.FC = ({ setModifyingMock({...modifyingMock, content: it})} + setContent={it => setModifyingMock({ ...modifyingMock, content: it })} /> )} From 218e2a44213abc37c0a1852233fb15d808f3bbab Mon Sep 17 00:00:00 2001 From: sibmaks Date: Sat, 17 May 2025 18:19:31 +0300 Subject: [PATCH 3/8] [Web App Mock] Integrate with kafka #55 - add ability publish without group creation --- gradle/libs.versions.toml | 2 +- .../src/pages/mock/CodeDocumentation.tsx | 33 ++++++++++++- .../graalvm/dto/kafka/PublishMessageRq.java | 2 + .../WebApplicationMocksGraalVMKafka.java | 46 ++++++++++++------- 4 files changed, 65 insertions(+), 18 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f8cafc9..e64b238 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,7 +22,7 @@ async-embedded = "0.0.10" session-embedded = "0.0.10" storage-embedded = "0.1.16" -kafka-client-service = "0.0.16" +kafka-client-service = "0.0.17" [libraries] common-api = { module = "com.github.sibdevtools:api-common", version.ref = "common-api" } diff --git a/web-app-frontend/src/pages/mock/CodeDocumentation.tsx b/web-app-frontend/src/pages/mock/CodeDocumentation.tsx index faa8197..76d92cd 100644 --- a/web-app-frontend/src/pages/mock/CodeDocumentation.tsx +++ b/web-app-frontend/src/pages/mock/CodeDocumentation.tsx @@ -541,7 +541,38 @@ const kafkaExamples = [ const kafkaPublishingExamples = [ { - description: 'Publish message into group', + description: 'Publish message', + implementations: { + javascript: `const publishRq = { + bootstrapServers: ["localhost:9092"], + topic: "topic-to-publish", + maxTimeout: 30000, + partition: 0, // optional + timestamp: 0, // optional + key: [48, 49], // bytes array, optional + value: [48, 49], // bytes array, optional + headers: { + headerKey: [48, 49] // bytes array, optional + } +}; +const publishRs = kafka.publish(publishRq);`, + python: `publishRq = { + "bootstrapServers": ["localhost:9092"], + "topic": "topic-to-publish", + "maxTimeout": 30000, + "partition": 0, # optional + "timestamp": 0, # optional + "key": [48, 49], # bytes array, optional + "value": [48, 49], # bytes array, optional + "headers": { + "headerKey": [48, 49] # bytes array, optional + } +} +publishRs = kafka.publish(publishRq)` + } + }, + { + description: 'Publish message into existed bootstrap group', implementations: { javascript: `const publishRq = { groupCode: "kafkaGroupCode", diff --git a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishMessageRq.java b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishMessageRq.java index d2368b4..4037bbc 100644 --- a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishMessageRq.java +++ b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishMessageRq.java @@ -1,5 +1,6 @@ package com.github.sibdevtools.web.app.mocks.service.handler.impl.graalvm.dto.kafka; +import java.util.List; import java.util.Map; /** @@ -8,6 +9,7 @@ */ public record PublishMessageRq( String groupCode, + List bootstrapServers, String topic, //asd Integer partition, diff --git a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/WebApplicationMocksGraalVMKafka.java b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/WebApplicationMocksGraalVMKafka.java index a5bdef1..2c48ac3 100644 --- a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/WebApplicationMocksGraalVMKafka.java +++ b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/WebApplicationMocksGraalVMKafka.java @@ -1,6 +1,7 @@ package com.github.sibdevtools.web.app.mocks.service.handler.impl.graalvm.dto.kafka; import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.sibdevtools.service.kafka.client.api.dto.RecordMetadataDto; import com.github.sibdevtools.service.kafka.client.api.rq.SendTemplateMessageRq; import com.github.sibdevtools.service.kafka.client.service.BootstrapGroupService; import com.github.sibdevtools.service.kafka.client.service.MessageConsumerService; @@ -27,12 +28,37 @@ public class WebApplicationMocksGraalVMKafka { protected final MessagePublisherService messagePublisherService; protected final TemplateMessageService templateMessageService; + private static PublishMessageRs getPublishMessageRs(RecordMetadataDto it) { + return new PublishMessageRs( + it.getOffset(), + it.getTimestamp(), + it.getSerializedKeySize(), + it.getSerializedValueSize(), + it.getPartition() + ); + } + @HostAccess.Export public PublishMessageRs publish(@Nonnull Object arg) { var rq = objectMapper.convertValue(arg, PublishMessageRq.class); - var bootstrapGroup = bootstrapGroupService.getByCode(rq.groupCode()); + var groupCode = rq.groupCode(); + if (groupCode != null) { + var bootstrapGroup = bootstrapGroupService.getByCode(groupCode); + return messagePublisherService.sendMessage( + bootstrapGroup.getId(), + rq.topic(), + rq.partition(), + rq.timestamp(), + rq.key(), + rq.value(), + rq.headers(), + rq.maxTimeout() + ) + .map(WebApplicationMocksGraalVMKafka::getPublishMessageRs) + .orElse(null); + } return messagePublisherService.sendMessage( - bootstrapGroup.getId(), + rq.bootstrapServers(), rq.topic(), rq.partition(), rq.timestamp(), @@ -41,13 +67,7 @@ public PublishMessageRs publish(@Nonnull Object arg) { rq.headers(), rq.maxTimeout() ) - .map(it -> new PublishMessageRs( - it.getOffset(), - it.getTimestamp(), - it.getSerializedKeySize(), - it.getSerializedValueSize(), - it.getPartition() - )) + .map(WebApplicationMocksGraalVMKafka::getPublishMessageRs) .orElse(null); } @@ -69,13 +89,7 @@ public PublishMessageRs publishTemplate(@Nonnull Object arg) { rq.maxTimeout() ) ) - .map(it -> new PublishMessageRs( - it.getOffset(), - it.getTimestamp(), - it.getSerializedKeySize(), - it.getSerializedValueSize(), - it.getPartition() - )) + .map(WebApplicationMocksGraalVMKafka::getPublishMessageRs) .orElse(null); } From 1afd0775532ed3d39d5a3682abcf4158671f5d1d Mon Sep 17 00:00:00 2001 From: sibmaks Date: Tue, 24 Jun 2025 15:00:23 +0300 Subject: [PATCH 4/8] Add disabling on saving mock --- .../src/components/GraalVMMockContent.tsx | 6 ++-- .../src/components/HttpHeadersForm.tsx | 13 ++++++-- .../src/components/StaticFileMockContent.tsx | 6 +++- .../src/components/StaticMockContent.tsx | 9 +++++- .../src/pages/mock/AddEditMockPage.tsx | 15 +++++++-- web-app-frontend/src/pages/mock/MockForm.tsx | 31 ++++++++++++++++--- 6 files changed, 66 insertions(+), 14 deletions(-) diff --git a/web-app-frontend/src/components/GraalVMMockContent.tsx b/web-app-frontend/src/components/GraalVMMockContent.tsx index ce99788..f20c1e7 100644 --- a/web-app-frontend/src/components/GraalVMMockContent.tsx +++ b/web-app-frontend/src/components/GraalVMMockContent.tsx @@ -4,13 +4,13 @@ import AceEditor from 'react-ace'; import '../const/ace.imports'; import { loadSettings } from '../settings/utils'; -import { Button, ButtonGroup, Form, Table } from 'react-bootstrap'; -import CodeDocumentation from '../pages/mock/CodeDocumentation'; +import { Button, ButtonGroup, Form } from 'react-bootstrap'; export interface GraalVMMockContentProps { mode: 'javascript' | 'python'; content: ArrayBuffer; setContent: (content: ArrayBuffer) => void; + disabled?: boolean; } const textEncoder = new TextEncoder(); @@ -18,6 +18,7 @@ const GraalVMMockContent: React.FC = ({ mode, content, setContent, + disabled, }) => { const settings = loadSettings(); const [isWordWrapEnabled, setIsWordWrapEnabled] = useState(true); @@ -56,6 +57,7 @@ const GraalVMMockContent: React.FC = ({ fontSize={14} width="100%" height="480px" + readOnly={disabled} showPrintMargin={true} showGutter={true} highlightActiveLine={true} diff --git a/web-app-frontend/src/components/HttpHeadersForm.tsx b/web-app-frontend/src/components/HttpHeadersForm.tsx index 4703dbe..3938a53 100644 --- a/web-app-frontend/src/components/HttpHeadersForm.tsx +++ b/web-app-frontend/src/components/HttpHeadersForm.tsx @@ -11,6 +11,7 @@ interface Header { interface HttpHeadersFormProps { meta: { [key: string]: string }; setMeta: (newMeta: { [key: string]: string }) => void; + disabled?: boolean; } const commonHeaders = [ @@ -31,7 +32,11 @@ const headerValueSuggestions: { [key: string]: string[] } = { 'Cache-Control': ['no-cache', 'no-store', 'must-revalidate', 'public', 'private'], }; -const HttpHeadersForm: React.FC = ({ meta, setMeta }) => { +const HttpHeadersForm: React.FC = ({ + meta, + setMeta, + disabled + }) => { const [notFilledExist, setNotFilledExist] = useState(false); const initialHeaders = meta['HTTP_HEADERS'] @@ -101,6 +106,7 @@ const HttpHeadersForm: React.FC = ({ meta, setMeta }) => { placeholder="Header Key" value={header.key} onChange={(e) => handleHeaderChange(index, 'key', e.target.value)} + disabled={disabled} /> {commonHeaders.map((headerName, i) => ( @@ -113,6 +119,7 @@ const HttpHeadersForm: React.FC = ({ meta, setMeta }) => { placeholder="Header Value" value={header.value} onChange={(e) => handleHeaderChange(index, 'value', e.target.value)} + disabled={disabled} /> {(headerValueSuggestions[header.key] || []).map((valueSuggestion, i) => ( @@ -122,7 +129,7 @@ const HttpHeadersForm: React.FC = ({ meta, setMeta }) => { + {saved && ( + + Saved successfully. + + )} + {(modifyingMock.type === 'JS' || modifyingMock.type === 'PYTHON') && ( )} From f774abcadbb6345effa29e10d163673ee949276c Mon Sep 17 00:00:00 2001 From: sibmaks Date: Tue, 24 Jun 2025 16:16:10 +0300 Subject: [PATCH 5/8] Add Kafka message consuming --- gradle/libs.versions.toml | 2 +- .../src/pages/mock/CodeDocumentation.tsx | 233 ++++++++++++++++++ web-app/build.gradle.kts | 2 + .../graalvm/dto/kafka/ConsumeMessagesRq.java | 21 ++ .../graalvm/dto/kafka/ConsumeMessagesRs.java | 17 ++ .../graalvm/dto/kafka/ConsumedMessage.java | 141 +++++++++++ .../WebApplicationMocksGraalVMKafka.java | 76 ++++++ 7 files changed, 491 insertions(+), 1 deletion(-) create mode 100644 web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/ConsumeMessagesRq.java create mode 100644 web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/ConsumeMessagesRs.java create mode 100644 web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/ConsumedMessage.java diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e64b238..c53d890 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,7 +22,7 @@ async-embedded = "0.0.10" session-embedded = "0.0.10" storage-embedded = "0.1.16" -kafka-client-service = "0.0.17" +kafka-client-service = "0.0.18" [libraries] common-api = { module = "com.github.sibdevtools:api-common", version.ref = "common-api" } diff --git a/web-app-frontend/src/pages/mock/CodeDocumentation.tsx b/web-app-frontend/src/pages/mock/CodeDocumentation.tsx index 76d92cd..4f05ea4 100644 --- a/web-app-frontend/src/pages/mock/CodeDocumentation.tsx +++ b/web-app-frontend/src/pages/mock/CodeDocumentation.tsx @@ -643,6 +643,209 @@ publishRs = kafka.publishTemplate(publishRq)` }, ]; +const kafkaConsumingExamples = [ + { + description: 'Consume at most 5 messages from the beginning', + implementations: { + javascript: `const consumeRq = { + groupCode: "kafkaGroupCode", + topic: "topic-to-publish", + maxMessages: 5, + maxTimeout: 30000, +}; +const consumeRs = kafka.getMessages(consumeRq);`, + python: `consumeRq = { + "groupCode": "kafkaGroupCode", + "topic": "topic-to-publish", + "maxMessages": 5, + "maxTimeout": 30000, +} +consumeRs = kafka.getMessages(consumeRq)` + } + }, + { + description: 'Consume at most 5 messages from group from the end', + implementations: { + javascript: `const consumeRq = { + groupCode: "kafkaGroupCode", + topic: "topic-to-publish", + maxMessages: 5, + maxTimeout: 30000, + direction: "LATEST", +}; +const consumeRs = kafka.getMessages(consumeRq);`, + python: `consumeRq = { + "groupCode": "kafkaGroupCode", + "topic": "topic-to-publish", + "maxMessages": 5, + "maxTimeout": 30000, + "direction": "LATEST", +} +consumeRs = kafka.getMessages(consumeRq)` + } + }, + { + description: 'Consume messages with fixed bootstrap servers', + implementations: { + javascript: `const consumeRq = { + bootstrapServers: ["localhost:9092"], + topic: "topic-to-publish", + maxMessages: 5, + maxTimeout: 30000, +}; +const consumeRs = kafka.getMessages(consumeRq);`, + python: `consumeRq = { + "bootstrapServers": ["localhost:9092"], + "topic": "topic-to-publish", + "maxMessages": 5, + "maxTimeout": 30000, +} +consumeRs = kafka.getMessages(consumeRq)` + } + }, +]; + +const kafkaConsumingResponseExamples = [ + { + description: 'Topic name', + implementations: { + javascript: `const topic = consumeRs.topic();`, + python: `topic = consumeRs.topic()` + } + }, + { + description: 'Messages list', + implementations: { + javascript: `const messages = consumeRs.messages();`, + python: `messages = consumeRs.messages()` + } + }, +]; + +const kafkaConsumedMessageResponseExamples = [ + { + description: 'Partition', + implementations: { + javascript: `const partition = messages[0].partition();`, + python: `partition = messages[0].partition()` + } + }, + { + description: 'Offset', + implementations: { + javascript: `const offset = messages[0].offset();`, + python: `offset = messages[0].offset()` + } + }, + { + description: 'Timestamp', + implementations: { + javascript: `const timestamp = messages[0].timestamp();`, + python: `timestamp = messages[0].timestamp()` + } + }, + { + description: 'Timestamp Type', + implementations: { + javascript: `const timestampType = messages[0].timestampType();`, + python: `timestampType = messages[0].timestampType()` + } + }, +]; + +const kafkaConsumedMessageHeadersResponseExamples = [ + { + description: 'Headers dictionary', + implementations: { + javascript: `const headers = messages[0].headers();`, + python: `headers = messages[0].headers()` + } + }, + { + description: 'Header binary value', + implementations: { + javascript: `const header = messages[0].header("header-key");`, + python: `header = messages[0].header("header-key")` + } + }, + { + description: 'Header text value', + implementations: { + javascript: `const header = messages[0].headerText("header-key");`, + python: `header = messages[0].headerText("header-key")` + } + }, + { + description: 'Header JSON value', + implementations: { + javascript: `const header = messages[0].headerJson("header-key");`, + python: `header = messages[0].headerJson("header-key")` + } + }, +]; + +const kafkaConsumedMessageKeyResponseExamples = [ + { + description: 'Serialized Key Size', + implementations: { + javascript: `const serializedKeySize = messages[0].serializedKeySize();`, + python: `serializedKeySize = messages[0].serializedKeySize()` + } + }, + { + description: 'Message binary key', + implementations: { + javascript: `const key = messages[0].key();`, + python: `key = messages[0].key()` + } + }, + { + description: 'Message text key', + implementations: { + javascript: `const key = messages[0].textKey();`, + python: `key = messages[0].textKey()` + } + }, + { + description: 'Message JSON key', + implementations: { + javascript: `const key = messages[0].jsonKey();`, + python: `key = messages[0].jsonKey()` + } + }, +]; + +const kafkaConsumedMessageValueResponseExamples = [ + { + description: 'Serialized Value Size', + implementations: { + javascript: `const serializedValueSize = messages[0].serializedValueSize();`, + python: `serializedValueSize = messages[0].serializedValueSize()` + } + }, + { + description: 'Message binary value', + implementations: { + javascript: `const value = messages[0].value(); // [72, 105, 33]`, + python: `value = messages[0].value() # [72, 105, 33]` + } + }, + { + description: 'Message text value', + implementations: { + javascript: `const value = messages[0].textValue(); // 'Hi!'`, + python: `value = messages[0].textValue() # "Hi!"` + } + }, + { + description: 'Message JSON value', + implementations: { + javascript: `const value = messages[0].jsonValue(); // '"Hi!"'`, + python: `value = messages[0].jsonValue() # '"Hi!"'` + } + }, +]; + const kafkaPublishingResponseExamples = [ { description: 'Message offset', @@ -752,6 +955,36 @@ const allExamples: ExampleSection = { examples: kafkaPublishingResponseExamples } ] + }, + { + name: 'Consuming', + examples: kafkaConsumingExamples, + sections: [ + { + name: 'Consuming result', + examples: kafkaConsumingResponseExamples, + sections: [ + { + name: 'Message', + examples: kafkaConsumedMessageResponseExamples, + sections: [ + { + name: 'Headers', + examples: kafkaConsumedMessageHeadersResponseExamples + }, + { + name: 'Key', + examples: kafkaConsumedMessageKeyResponseExamples + }, + { + name: 'Value', + examples: kafkaConsumedMessageValueResponseExamples + } + ] + }, + ] + } + ] } ] } diff --git a/web-app/build.gradle.kts b/web-app/build.gradle.kts index c66b227..7539cf1 100644 --- a/web-app/build.gradle.kts +++ b/web-app/build.gradle.kts @@ -36,6 +36,8 @@ dependencies { implementation("jakarta.annotation:jakarta.annotation-api") implementation("jakarta.persistence:jakarta.persistence-api") + implementation("org.apache.kafka:kafka-clients") + implementation(libs.bundles.graalvm) implementation(libs.mapstruct) diff --git a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/ConsumeMessagesRq.java b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/ConsumeMessagesRq.java new file mode 100644 index 0000000..df47d36 --- /dev/null +++ b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/ConsumeMessagesRq.java @@ -0,0 +1,21 @@ +package com.github.sibdevtools.web.app.mocks.service.handler.impl.graalvm.dto.kafka; + +import java.util.List; + +/** + * @author sibmaks + * @since 0.0.22 + */ +public record ConsumeMessagesRq( + String groupCode, + List bootstrapServers, + String topic, + int maxMessages, + Integer maxTimeout, + Direction direction +) { + enum Direction { + EARLIEST, + LATEST + } +} diff --git a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/ConsumeMessagesRs.java b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/ConsumeMessagesRs.java new file mode 100644 index 0000000..e9c1302 --- /dev/null +++ b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/ConsumeMessagesRs.java @@ -0,0 +1,17 @@ +package com.github.sibdevtools.web.app.mocks.service.handler.impl.graalvm.dto.kafka; + +import lombok.Builder; +import org.graalvm.polyglot.HostAccess; + +import java.util.List; + +/** + * @author sibmaks + * @since 0.0.22 + */ +@Builder +public record ConsumeMessagesRs( + @HostAccess.Export String topic, + @HostAccess.Export List messages +) { +} diff --git a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/ConsumedMessage.java b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/ConsumedMessage.java new file mode 100644 index 0000000..8714b6f --- /dev/null +++ b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/ConsumedMessage.java @@ -0,0 +1,141 @@ +package com.github.sibdevtools.web.app.mocks.service.handler.impl.graalvm.dto.kafka; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.sibdevtools.web.app.mocks.exception.UnexpectedErrorException; +import jakarta.annotation.Nullable; +import lombok.Builder; +import lombok.val; +import org.graalvm.polyglot.HostAccess; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +/** + * @author sibmaks + * @since 0.0.22 + */ +@Builder +public record ConsumedMessage( + ObjectMapper objectMapper, + @HostAccess.Export int partition, + @HostAccess.Export long offset, + @HostAccess.Export long timestamp, + @HostAccess.Export String timestampType, + @HostAccess.Export int serializedKeySize, + @HostAccess.Export int serializedValueSize, + @HostAccess.Export Map headers, + @HostAccess.Export byte[] key, + @HostAccess.Export byte[] value +) { + /** + * Get key as string + * + * @return key + */ + @HostAccess.Export + public String textKey() { + return new String(key, StandardCharsets.UTF_8); + } + + /** + * Get key as JSON + * + * @return key + */ + @HostAccess.Export + public Object jsonKey() { + try { + var text = textKey(); + var jsonNode = objectMapper.readTree(text); + + if (jsonNode.isArray()) { + return objectMapper.convertValue(jsonNode, Object[].class); + } else if (jsonNode.isObject()) { + return objectMapper.convertValue(jsonNode, Map.class); + } + return jsonNode; + } catch (IOException e) { + throw new UnexpectedErrorException("Can't write to response", e); + } + } + + /** + * Get value as string + * + * @return value + */ + @HostAccess.Export + public String textValue() { + return new String(value, StandardCharsets.UTF_8); + } + + /** + * Get value as JSON + * + * @return value + */ + @HostAccess.Export + public Object jsonValue() { + try { + var text = textValue(); + var jsonNode = objectMapper.readTree(text); + + if (jsonNode.isArray()) { + return objectMapper.convertValue(jsonNode, Object[].class); + } else if (jsonNode.isObject()) { + return objectMapper.convertValue(jsonNode, Map.class); + } + return jsonNode; + } catch (IOException e) { + throw new UnexpectedErrorException("Can't write to response", e); + } + } + + /** + * Get header value as byte array + * + * @param key header key + * @return header value + */ + @Nullable + @HostAccess.Export + public byte[] header(String key) { + return headers.get(key); + } + + /** + * Get header value as UTF-8 string + * + * @param key header key + * @return header value + */ + @HostAccess.Export + public String headerText(String key) { + val value = header(key); + if (value == null) return null; + return new String(value, StandardCharsets.UTF_8); + } + + /** + * Get value as JSON + * + * @return value + */ + @HostAccess.Export + public Object headerJson(String key) { + try { + var text = headerText(key); + var jsonNode = objectMapper.readTree(text); + + if (jsonNode.isArray()) { + return objectMapper.convertValue(jsonNode, Object[].class); + } else if (jsonNode.isObject()) { + return objectMapper.convertValue(jsonNode, Map.class); + } + return jsonNode; + } catch (IOException e) { + throw new UnexpectedErrorException("Can't write to response", e); + } + } +} diff --git a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/WebApplicationMocksGraalVMKafka.java b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/WebApplicationMocksGraalVMKafka.java index 2c48ac3..f9df3a1 100644 --- a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/WebApplicationMocksGraalVMKafka.java +++ b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/WebApplicationMocksGraalVMKafka.java @@ -11,9 +11,17 @@ import jakarta.annotation.Nonnull; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; +import lombok.val; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.common.header.Headers; import org.graalvm.polyglot.HostAccess; import org.springframework.stereotype.Component; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + /** * @author sibmaks * @since 0.0.22 @@ -93,5 +101,73 @@ public PublishMessageRs publishTemplate(@Nonnull Object arg) { .orElse(null); } + @HostAccess.Export + public ConsumeMessagesRs getMessages(@Nonnull Object arg) { + val rq = objectMapper.convertValue(arg, ConsumeMessagesRq.class); + val bootstrapServers = getBootstrapServers(rq); + val messages = getConsumerRecords(rq, bootstrapServers); + + val consumed = messages.stream() + .map(it -> ConsumedMessage.builder() + .objectMapper(objectMapper) + .partition(it.partition()) + .offset(it.offset()) + .timestamp(it.timestamp()) + .timestampType(it.timestampType().name) + .serializedKeySize(it.serializedKeySize()) + .serializedValueSize(it.serializedValueSize()) + .headers(buildHeadersMap(it.headers())) + .key(it.key()) + .value(it.value()) + .build()) + .toList(); + + return ConsumeMessagesRs.builder() + .topic(rq.topic()) + .messages(consumed) + .build(); + } + + private List> getConsumerRecords( + ConsumeMessagesRq rq, + List bootstrapServers + ) { + if (rq.direction() == ConsumeMessagesRq.Direction.EARLIEST) { + return messageConsumerService.getMessages( + bootstrapServers, + rq.topic(), + rq.maxMessages(), + rq.maxTimeout() + ) + .orElseGet(Collections::emptyList); + } + return messageConsumerService.getLastNMessages( + bootstrapServers, + rq.topic(), + rq.maxMessages(), + rq.maxTimeout() + ) + .orElseGet(Collections::emptyList); + + } + + private List getBootstrapServers(ConsumeMessagesRq rq) { + val groupCode = rq.groupCode(); + if (groupCode != null) { + val bootstrapGroup = bootstrapGroupService.getByCode(groupCode); + return bootstrapGroup.getBootstrapServers(); + } + return rq.bootstrapServers(); + } + + private Map buildHeadersMap(Headers headers) { + val headersMap = new HashMap(); + + for (val header : headers) { + headersMap.put(header.key(), header.value()); + } + + return headersMap; + } } From e8cff72ee856db12c9f9cfdc073d60786b169417 Mon Sep 17 00:00:00 2001 From: sibmaks Date: Tue, 24 Jun 2025 16:21:50 +0300 Subject: [PATCH 6/8] Deduplication --- .../graalvm/dto/kafka/ConsumedMessage.java | 43 ++++++------------- 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/ConsumedMessage.java b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/ConsumedMessage.java index 8714b6f..fef3203 100644 --- a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/ConsumedMessage.java +++ b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/ConsumedMessage.java @@ -35,7 +35,7 @@ public record ConsumedMessage( */ @HostAccess.Export public String textKey() { - return new String(key, StandardCharsets.UTF_8); + return asText(key); } /** @@ -45,8 +45,11 @@ public String textKey() { */ @HostAccess.Export public Object jsonKey() { + return asJson(textKey()); + } + + private Object asJson(String text) { try { - var text = textKey(); var jsonNode = objectMapper.readTree(text); if (jsonNode.isArray()) { @@ -67,6 +70,11 @@ public Object jsonKey() { */ @HostAccess.Export public String textValue() { + return asText(value); + } + + private String asText(byte[] value) { + if (value == null) return null; return new String(value, StandardCharsets.UTF_8); } @@ -77,19 +85,7 @@ public String textValue() { */ @HostAccess.Export public Object jsonValue() { - try { - var text = textValue(); - var jsonNode = objectMapper.readTree(text); - - if (jsonNode.isArray()) { - return objectMapper.convertValue(jsonNode, Object[].class); - } else if (jsonNode.isObject()) { - return objectMapper.convertValue(jsonNode, Map.class); - } - return jsonNode; - } catch (IOException e) { - throw new UnexpectedErrorException("Can't write to response", e); - } + return asJson(textValue()); } /** @@ -113,8 +109,7 @@ public byte[] header(String key) { @HostAccess.Export public String headerText(String key) { val value = header(key); - if (value == null) return null; - return new String(value, StandardCharsets.UTF_8); + return asText(value); } /** @@ -124,18 +119,6 @@ public String headerText(String key) { */ @HostAccess.Export public Object headerJson(String key) { - try { - var text = headerText(key); - var jsonNode = objectMapper.readTree(text); - - if (jsonNode.isArray()) { - return objectMapper.convertValue(jsonNode, Object[].class); - } else if (jsonNode.isObject()) { - return objectMapper.convertValue(jsonNode, Map.class); - } - return jsonNode; - } catch (IOException e) { - throw new UnexpectedErrorException("Can't write to response", e); - } + return asJson(headerText(key)); } } From 2eb23043372fb99d0d39f6696b7c65ccec4ad5fb Mon Sep 17 00:00:00 2001 From: sibmaks Date: Tue, 24 Jun 2025 16:26:05 +0300 Subject: [PATCH 7/8] Implement equals and hash code to dtos --- .../graalvm/dto/kafka/ConsumedMessage.java | 32 +++++++++++++++++++ .../graalvm/dto/kafka/PublishMessageRq.java | 31 ++++++++++++++++++ .../dto/kafka/PublishTemplateMessageRq.java | 31 ++++++++++++++++++ 3 files changed, 94 insertions(+) diff --git a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/ConsumedMessage.java b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/ConsumedMessage.java index fef3203..9df0190 100644 --- a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/ConsumedMessage.java +++ b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/ConsumedMessage.java @@ -9,7 +9,9 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.Map; +import java.util.Objects; /** * @author sibmaks @@ -28,6 +30,36 @@ public record ConsumedMessage( @HostAccess.Export byte[] key, @HostAccess.Export byte[] value ) { + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + + val that = (ConsumedMessage) o; + return offset == that.offset && + partition == that.partition && + timestamp == that.timestamp && + serializedKeySize == that.serializedKeySize && + serializedValueSize == that.serializedValueSize && + Arrays.equals(key, that.key) && + Arrays.equals(value, that.value) && + Objects.equals(timestampType, that.timestampType) && + Objects.equals(headers, that.headers); + } + + @Override + public int hashCode() { + int result = partition; + result = 31 * result + Long.hashCode(offset); + result = 31 * result + Long.hashCode(timestamp); + result = 31 * result + Objects.hashCode(timestampType); + result = 31 * result + serializedKeySize; + result = 31 * result + serializedValueSize; + result = 31 * result + Objects.hashCode(headers); + result = 31 * result + Arrays.hashCode(key); + result = 31 * result + Arrays.hashCode(value); + return result; + } + /** * Get key as string * diff --git a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishMessageRq.java b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishMessageRq.java index 4037bbc..dbfea57 100644 --- a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishMessageRq.java +++ b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishMessageRq.java @@ -1,7 +1,9 @@ package com.github.sibdevtools.web.app.mocks.service.handler.impl.graalvm.dto.kafka; +import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Objects; /** * @author sibmaks @@ -19,4 +21,33 @@ public record PublishMessageRq( Map headers, Integer maxTimeout ) { + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + + PublishMessageRq that = (PublishMessageRq) o; + return Arrays.equals(key, that.key) && + topic.equals(that.topic) && + Arrays.equals(value, that.value) && + Objects.equals(timestamp, that.timestamp) && + Objects.equals(groupCode, that.groupCode) && + Objects.equals(partition, that.partition) && + Objects.equals(maxTimeout, that.maxTimeout) && + Objects.equals(headers, that.headers) && + Objects.equals(bootstrapServers, that.bootstrapServers); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(groupCode); + result = 31 * result + Objects.hashCode(bootstrapServers); + result = 31 * result + topic.hashCode(); + result = 31 * result + Objects.hashCode(partition); + result = 31 * result + Objects.hashCode(timestamp); + result = 31 * result + Arrays.hashCode(key); + result = 31 * result + Arrays.hashCode(value); + result = 31 * result + Objects.hashCode(headers); + result = 31 * result + Objects.hashCode(maxTimeout); + return result; + } } diff --git a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishTemplateMessageRq.java b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishTemplateMessageRq.java index 5a4eb05..235ddb0 100644 --- a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishTemplateMessageRq.java +++ b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishTemplateMessageRq.java @@ -1,6 +1,8 @@ package com.github.sibdevtools.web.app.mocks.service.handler.impl.graalvm.dto.kafka; +import java.util.Arrays; import java.util.Map; +import java.util.Objects; /** * @author sibmaks @@ -17,4 +19,33 @@ public record PublishTemplateMessageRq( Map headers, Integer maxTimeout ) { + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + + PublishTemplateMessageRq that = (PublishTemplateMessageRq) o; + return Arrays.equals(key, that.key) && + topic.equals(that.topic) && + Objects.equals(timestamp, that.timestamp) && + Objects.equals(groupCode, that.groupCode) && + Objects.equals(partition, that.partition) && + Objects.equals(maxTimeout, that.maxTimeout) && + Objects.equals(templateCode, that.templateCode) && + Objects.equals(input, that.input) && + Objects.equals(headers, that.headers); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(groupCode); + result = 31 * result + topic.hashCode(); + result = 31 * result + Objects.hashCode(templateCode); + result = 31 * result + Objects.hashCode(partition); + result = 31 * result + Objects.hashCode(timestamp); + result = 31 * result + Arrays.hashCode(key); + result = 31 * result + Objects.hashCode(input); + result = 31 * result + Objects.hashCode(headers); + result = 31 * result + Objects.hashCode(maxTimeout); + return result; + } } From b73a3c401593afe42b5349b835c2d09fbf8defda Mon Sep 17 00:00:00 2001 From: sibmaks Date: Tue, 24 Jun 2025 16:28:12 +0300 Subject: [PATCH 8/8] Implement toString to dtos --- .../impl/graalvm/dto/kafka/ConsumedMessage.java | 16 ++++++++++++++++ .../graalvm/dto/kafka/PublishMessageRq.java | 15 +++++++++++++++ .../dto/kafka/PublishTemplateMessageRq.java | 17 +++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/ConsumedMessage.java b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/ConsumedMessage.java index 9df0190..9e62d90 100644 --- a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/ConsumedMessage.java +++ b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/ConsumedMessage.java @@ -60,6 +60,22 @@ public int hashCode() { return result; } + @Override + public String toString() { + return "ConsumedMessage{" + + "objectMapper=" + objectMapper + + ", partition=" + partition + + ", offset=" + offset + + ", timestamp=" + timestamp + + ", timestampType='" + timestampType + '\'' + + ", serializedKeySize=" + serializedKeySize + + ", serializedValueSize=" + serializedValueSize + + ", headers=" + headers + + ", key=" + Arrays.toString(key) + + ", value=" + Arrays.toString(value) + + '}'; + } + /** * Get key as string * diff --git a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishMessageRq.java b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishMessageRq.java index dbfea57..11b827d 100644 --- a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishMessageRq.java +++ b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishMessageRq.java @@ -50,4 +50,19 @@ public int hashCode() { result = 31 * result + Objects.hashCode(maxTimeout); return result; } + + @Override + public String toString() { + return "PublishMessageRq{" + + "groupCode='" + groupCode + '\'' + + ", bootstrapServers=" + bootstrapServers + + ", topic='" + topic + '\'' + + ", partition=" + partition + + ", timestamp=" + timestamp + + ", key=" + Arrays.toString(key) + + ", value=" + Arrays.toString(value) + + ", headers=" + headers + + ", maxTimeout=" + maxTimeout + + '}'; + } } diff --git a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishTemplateMessageRq.java b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishTemplateMessageRq.java index 235ddb0..2645a67 100644 --- a/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishTemplateMessageRq.java +++ b/web-app/src/main/java/com/github/sibdevtools/web/app/mocks/service/handler/impl/graalvm/dto/kafka/PublishTemplateMessageRq.java @@ -1,5 +1,7 @@ package com.github.sibdevtools.web.app.mocks.service.handler.impl.graalvm.dto.kafka; +import jakarta.validation.constraints.NotNull; + import java.util.Arrays; import java.util.Map; import java.util.Objects; @@ -48,4 +50,19 @@ public int hashCode() { result = 31 * result + Objects.hashCode(maxTimeout); return result; } + + @Override + public String toString() { + return "PublishTemplateMessageRq{" + + "groupCode='" + groupCode + '\'' + + ", topic='" + topic + '\'' + + ", templateCode='" + templateCode + '\'' + + ", partition=" + partition + + ", timestamp=" + timestamp + + ", key=" + Arrays.toString(key) + + ", input=" + input + + ", headers=" + headers + + ", maxTimeout=" + maxTimeout + + '}'; + } }